Skip to main content

cranpose_render_wgpu/
gpu_stats.rs

1//! Per-frame GPU render counters.
2//!
3//! Counters are always collected so tests and perf harnesses can assert them.
4//! Setting `CRANPOSE_GPU_STATS=1` prints a summary line every 60 frames to stderr.
5
6use crate::surface_requirements::{SurfaceRequirement, SurfaceRequirementSet};
7use std::cell::{Cell, RefCell};
8
9use cranpose_core::NodeId;
10use cranpose_ui_graphics::Rect;
11
12const TOP_ISOLATED_LAYER_LIMIT: usize = 8;
13
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
15pub struct LayerSurfaceReasons {
16    pub explicit_offscreen: bool,
17    pub effect: bool,
18    pub backdrop: bool,
19    pub group_opacity: bool,
20    pub blend_mode: bool,
21    pub immediate_shadow: bool,
22    pub text_local_surface: bool,
23    pub motion_stable_capture: bool,
24    pub mixed_direct_content: bool,
25    pub non_translation_transform: bool,
26}
27
28impl LayerSurfaceReasons {
29    /// Returns true if any isolating requirement is set.
30    /// Delegates to `SurfaceRequirementSet::has_isolating_requirement` to
31    /// keep the semantics in sync (e.g. `mixed_direct_content` alone does
32    /// not count as isolating).
33    pub fn has_any(self) -> bool {
34        self.explicit_offscreen
35            || self.effect
36            || self.backdrop
37            || self.group_opacity
38            || self.blend_mode
39            || self.immediate_shadow
40            || self.text_local_surface
41            || self.motion_stable_capture
42            || self.non_translation_transform
43    }
44
45    pub fn labels(self) -> impl Iterator<Item = &'static str> {
46        let mut labels = [None; 10];
47        let mut len = 0usize;
48
49        if self.explicit_offscreen {
50            labels[len] = Some("explicit_offscreen");
51            len += 1;
52        }
53        if self.effect {
54            labels[len] = Some("effect");
55            len += 1;
56        }
57        if self.backdrop {
58            labels[len] = Some("backdrop");
59            len += 1;
60        }
61        if self.group_opacity {
62            labels[len] = Some("group_opacity");
63            len += 1;
64        }
65        if self.blend_mode {
66            labels[len] = Some("blend_mode");
67            len += 1;
68        }
69        if self.immediate_shadow {
70            labels[len] = Some("immediate_shadow");
71            len += 1;
72        }
73        if self.text_local_surface {
74            labels[len] = Some("text_local_surface");
75            len += 1;
76        }
77        if self.motion_stable_capture {
78            labels[len] = Some("motion_stable_capture");
79            len += 1;
80        }
81        if self.mixed_direct_content {
82            labels[len] = Some("mixed_direct_content");
83            len += 1;
84        }
85        if self.non_translation_transform {
86            labels[len] = Some("non_translation_transform");
87            len += 1;
88        }
89
90        labels.into_iter().flatten().take(len)
91    }
92
93    pub fn display(self) -> String {
94        let mut joined = String::new();
95        for (index, label) in self.labels().enumerate() {
96            if index > 0 {
97                joined.push('+');
98            }
99            joined.push_str(label);
100        }
101        if joined.is_empty() {
102            joined.push_str("none");
103        }
104        joined
105    }
106
107    pub fn has_renderer_forced_surface(self) -> bool {
108        self.immediate_shadow || self.text_local_surface || self.non_translation_transform
109    }
110}
111
112impl From<SurfaceRequirementSet> for LayerSurfaceReasons {
113    fn from(requirements: SurfaceRequirementSet) -> Self {
114        Self {
115            explicit_offscreen: requirements.contains(SurfaceRequirement::ExplicitOffscreen),
116            effect: requirements.contains(SurfaceRequirement::RenderEffect),
117            backdrop: requirements.contains(SurfaceRequirement::Backdrop),
118            group_opacity: requirements.contains(SurfaceRequirement::GroupOpacity),
119            blend_mode: requirements.contains(SurfaceRequirement::BlendMode),
120            immediate_shadow: requirements.contains(SurfaceRequirement::ImmediateShadow),
121            text_local_surface: requirements.contains(SurfaceRequirement::TextMaterialMask),
122            motion_stable_capture: requirements.contains(SurfaceRequirement::MotionStableCapture),
123            mixed_direct_content: requirements.contains(SurfaceRequirement::MixedDirectContent),
124            non_translation_transform: requirements
125                .contains(SurfaceRequirement::NonTranslationTransform),
126        }
127    }
128}
129
130#[derive(Clone, Copy, Debug, PartialEq)]
131pub struct IsolatedLayerStat {
132    pub node_id: Option<NodeId>,
133    pub logical_rect: Rect,
134    pub width: u32,
135    pub height: u32,
136    pub reasons: LayerSurfaceReasons,
137}
138
139impl IsolatedLayerStat {
140    fn pixel_area(self) -> u64 {
141        (self.width as u64) * (self.height as u64)
142    }
143}
144
145impl Default for IsolatedLayerStat {
146    fn default() -> Self {
147        Self {
148            node_id: None,
149            logical_rect: Rect {
150                x: 0.0,
151                y: 0.0,
152                width: 0.0,
153                height: 0.0,
154            },
155            width: 0,
156            height: 0,
157            reasons: LayerSurfaceReasons::default(),
158        }
159    }
160}
161
162#[derive(Clone, Copy, Debug, Default, PartialEq)]
163pub struct FrameStatsSnapshot {
164    pub submits: u32,
165    pub offscreen_acquires: u32,
166    pub offscreen_news: u32,
167    pub offscreen_total_bytes: u64,
168    pub upload_bytes: u64,
169    pub isolated_layer_renders: u32,
170    pub isolated_layer_pixels: u64,
171    pub layer_cache_hits: u32,
172    pub layer_cache_misses: u32,
173    pub layer_cache_evictions: u32,
174    pub layer_cache_hit_pixels: u64,
175    pub layer_cache_miss_pixels: u64,
176    pub blur_passes: u32,
177    pub composite_passes: u32,
178    pub effect_applies: u32,
179    pub shape_passes: u32,
180    pub image_passes: u32,
181    pub text_passes: u32,
182    pub offscreen_pool_size: u32,
183    pub offscreen_pool_bytes: u64,
184    pub text_pool_size: u32,
185    pub layer_cache_size: u32,
186    pub layer_cache_bytes: u64,
187    pub image_cache_size: u32,
188    pub text_cache_size: u32,
189    pub top_isolated_layers: [Option<IsolatedLayerStat>; TOP_ISOLATED_LAYER_LIMIT],
190    pub top_isolated_layer_count: usize,
191}
192
193impl FrameStatsSnapshot {
194    pub fn top_isolated_layers(self) -> impl Iterator<Item = IsolatedLayerStat> {
195        self.top_isolated_layers
196            .into_iter()
197            .flatten()
198            .take(self.top_isolated_layer_count)
199    }
200
201    fn layer_cache_hit_rate(self) -> f64 {
202        let total = self.layer_cache_hits + self.layer_cache_misses;
203        if total > 0 {
204            (self.layer_cache_hits as f64 / total as f64) * 100.0
205        } else {
206            0.0
207        }
208    }
209
210    fn print(self, frame_count: u64) {
211        let mb = self.offscreen_total_bytes as f64 / (1024.0 * 1024.0);
212        let upload_mb = self.upload_bytes as f64 / (1024.0 * 1024.0);
213        let pool_mb = self.offscreen_pool_bytes as f64 / (1024.0 * 1024.0);
214        let layer_cache_hit_mpx = self.layer_cache_hit_pixels as f64 / 1_000_000.0;
215        let layer_cache_miss_mpx = self.layer_cache_miss_pixels as f64 / 1_000_000.0;
216        let layer_cache_mb = self.layer_cache_bytes as f64 / (1024.0 * 1024.0);
217        let isolated_layer_mpx = self.isolated_layer_pixels as f64 / 1_000_000.0;
218        eprintln!(
219            "[GPU f#{}] submits={} | offscreen: acq={} new={} {:.1}MB pool={}({:.1}MB) | \
220             uploads={:.2}MB | \
221             isolated_layers={} area={:.2}MP | \
222             layer_cache: hit={} miss={} {:.1}% evict={} hit_px={:.2}MP miss_px={:.2}MP size={}({:.1}MB) | \
223             blur={} composite={} effect={} | shape={} image={} text={} | \
224             caches: text_pool={} img={} txt={}",
225            frame_count,
226            self.submits,
227            self.offscreen_acquires,
228            self.offscreen_news,
229            mb,
230            self.offscreen_pool_size,
231            pool_mb,
232            upload_mb,
233            self.isolated_layer_renders,
234            isolated_layer_mpx,
235            self.layer_cache_hits,
236            self.layer_cache_misses,
237            self.layer_cache_hit_rate(),
238            self.layer_cache_evictions,
239            layer_cache_hit_mpx,
240            layer_cache_miss_mpx,
241            self.layer_cache_size,
242            layer_cache_mb,
243            self.blur_passes,
244            self.composite_passes,
245            self.effect_applies,
246            self.shape_passes,
247            self.image_passes,
248            self.text_passes,
249            self.text_pool_size,
250            self.image_cache_size,
251            self.text_cache_size,
252        );
253        for (index, layer) in self.top_isolated_layers().enumerate() {
254            eprintln!(
255                "  [isolated #{index}] node={:?} rect=({:.1},{:.1},{:.1},{:.1}) target={}x{} reasons={}",
256                layer.node_id,
257                layer.logical_rect.x,
258                layer.logical_rect.y,
259                layer.logical_rect.width,
260                layer.logical_rect.height,
261                layer.width,
262                layer.height,
263                layer.reasons.display(),
264            );
265        }
266    }
267}
268
269/// Per-frame debug counters for GPU work instrumentation.
270/// Uses `Cell` fields so counters can be bumped through shared references.
271#[derive(Default)]
272pub(crate) struct FrameStats {
273    pub submits: Cell<u32>,
274    pub offscreen_acquires: Cell<u32>,
275    pub offscreen_news: Cell<u32>,
276    pub offscreen_total_bytes: Cell<u64>,
277    pub upload_bytes: Cell<u64>,
278    pub isolated_layer_renders: Cell<u32>,
279    pub isolated_layer_pixels: Cell<u64>,
280    pub layer_cache_hits: Cell<u32>,
281    pub layer_cache_misses: Cell<u32>,
282    pub layer_cache_evictions: Cell<u32>,
283    pub layer_cache_hit_pixels: Cell<u64>,
284    pub layer_cache_miss_pixels: Cell<u64>,
285    pub blur_passes: Cell<u32>,
286    pub composite_passes: Cell<u32>,
287    pub effect_applies: Cell<u32>,
288    pub shape_passes: Cell<u32>,
289    pub image_passes: Cell<u32>,
290    pub text_passes: Cell<u32>,
291    // Pool/cache sizes snapshotted at end of frame
292    pub offscreen_pool_size: Cell<u32>,
293    pub offscreen_pool_bytes: Cell<u64>,
294    pub text_pool_size: Cell<u32>,
295    pub layer_cache_size: Cell<u32>,
296    pub layer_cache_bytes: Cell<u64>,
297    pub image_cache_size: Cell<u32>,
298    pub text_cache_size: Cell<u32>,
299    top_isolated_layers: RefCell<[Option<IsolatedLayerStat>; TOP_ISOLATED_LAYER_LIMIT]>,
300    top_isolated_layer_count: Cell<usize>,
301}
302
303impl FrameStats {
304    pub fn bump_submits(&self) {
305        self.submits.set(self.submits.get() + 1);
306    }
307
308    pub fn record_offscreen_acquire(&self, width: u32, height: u32, is_new: bool) {
309        self.offscreen_acquires
310            .set(self.offscreen_acquires.get() + 1);
311        if is_new {
312            self.offscreen_news.set(self.offscreen_news.get() + 1);
313        }
314        self.offscreen_total_bytes
315            .set(self.offscreen_total_bytes.get() + (width as u64) * (height as u64) * 4);
316    }
317
318    pub fn record_upload_bytes(&self, bytes: u64) {
319        self.upload_bytes
320            .set(self.upload_bytes.get().saturating_add(bytes));
321    }
322
323    pub fn record_isolated_layer_render(
324        &self,
325        width: u32,
326        height: u32,
327        node_id: Option<NodeId>,
328        logical_rect: Rect,
329        reasons: LayerSurfaceReasons,
330    ) {
331        self.isolated_layer_renders
332            .set(self.isolated_layer_renders.get().saturating_add(1));
333        self.isolated_layer_pixels.set(
334            self.isolated_layer_pixels
335                .get()
336                .saturating_add((width as u64) * (height as u64)),
337        );
338        self.record_top_isolated_layer(IsolatedLayerStat {
339            node_id,
340            logical_rect,
341            width,
342            height,
343            reasons,
344        });
345    }
346
347    pub fn record_layer_cache_hit(&self, width: u32, height: u32) {
348        self.layer_cache_hits
349            .set(self.layer_cache_hits.get().saturating_add(1));
350        self.layer_cache_hit_pixels.set(
351            self.layer_cache_hit_pixels
352                .get()
353                .saturating_add((width as u64) * (height as u64)),
354        );
355    }
356
357    pub fn record_layer_cache_miss(&self, width: u32, height: u32) {
358        self.layer_cache_misses
359            .set(self.layer_cache_misses.get().saturating_add(1));
360        self.layer_cache_miss_pixels.set(
361            self.layer_cache_miss_pixels
362                .get()
363                .saturating_add((width as u64) * (height as u64)),
364        );
365    }
366
367    pub fn record_layer_cache_eviction(&self) {
368        self.layer_cache_evictions
369            .set(self.layer_cache_evictions.get().saturating_add(1));
370    }
371
372    pub fn bump_shapes(&self) {
373        self.shape_passes.set(self.shape_passes.get() + 1);
374    }
375
376    pub fn bump_images(&self) {
377        self.image_passes.set(self.image_passes.get() + 1);
378    }
379
380    pub fn bump_text(&self) {
381        self.text_passes.set(self.text_passes.get() + 1);
382    }
383
384    pub fn snapshot(&self) -> FrameStatsSnapshot {
385        FrameStatsSnapshot {
386            submits: self.submits.get(),
387            offscreen_acquires: self.offscreen_acquires.get(),
388            offscreen_news: self.offscreen_news.get(),
389            offscreen_total_bytes: self.offscreen_total_bytes.get(),
390            upload_bytes: self.upload_bytes.get(),
391            isolated_layer_renders: self.isolated_layer_renders.get(),
392            isolated_layer_pixels: self.isolated_layer_pixels.get(),
393            layer_cache_hits: self.layer_cache_hits.get(),
394            layer_cache_misses: self.layer_cache_misses.get(),
395            layer_cache_evictions: self.layer_cache_evictions.get(),
396            layer_cache_hit_pixels: self.layer_cache_hit_pixels.get(),
397            layer_cache_miss_pixels: self.layer_cache_miss_pixels.get(),
398            blur_passes: self.blur_passes.get(),
399            composite_passes: self.composite_passes.get(),
400            effect_applies: self.effect_applies.get(),
401            shape_passes: self.shape_passes.get(),
402            image_passes: self.image_passes.get(),
403            text_passes: self.text_passes.get(),
404            offscreen_pool_size: self.offscreen_pool_size.get(),
405            offscreen_pool_bytes: self.offscreen_pool_bytes.get(),
406            text_pool_size: self.text_pool_size.get(),
407            layer_cache_size: self.layer_cache_size.get(),
408            layer_cache_bytes: self.layer_cache_bytes.get(),
409            image_cache_size: self.image_cache_size.get(),
410            text_cache_size: self.text_cache_size.get(),
411            top_isolated_layers: *self.top_isolated_layers.borrow(),
412            top_isolated_layer_count: self.top_isolated_layer_count.get(),
413        }
414    }
415
416    pub fn reset(&self) {
417        self.submits.set(0);
418        self.offscreen_acquires.set(0);
419        self.offscreen_news.set(0);
420        self.offscreen_total_bytes.set(0);
421        self.upload_bytes.set(0);
422        self.isolated_layer_renders.set(0);
423        self.isolated_layer_pixels.set(0);
424        self.layer_cache_hits.set(0);
425        self.layer_cache_misses.set(0);
426        self.layer_cache_evictions.set(0);
427        self.layer_cache_hit_pixels.set(0);
428        self.layer_cache_miss_pixels.set(0);
429        self.blur_passes.set(0);
430        self.composite_passes.set(0);
431        self.effect_applies.set(0);
432        self.shape_passes.set(0);
433        self.image_passes.set(0);
434        self.text_passes.set(0);
435        *self.top_isolated_layers.borrow_mut() = [None; TOP_ISOLATED_LAYER_LIMIT];
436        self.top_isolated_layer_count.set(0);
437    }
438
439    pub fn maybe_print_snapshot(
440        &self,
441        snapshot: FrameStatsSnapshot,
442        frame_count: &mut u64,
443        enabled: bool,
444    ) {
445        if !enabled {
446            return;
447        }
448        *frame_count += 1;
449        if (*frame_count).is_multiple_of(60) {
450            snapshot.print(*frame_count);
451        }
452    }
453
454    fn record_top_isolated_layer(&self, layer: IsolatedLayerStat) {
455        if !layer.reasons.has_any() {
456            return;
457        }
458
459        let mut top_layers = self.top_isolated_layers.borrow_mut();
460        let len = self.top_isolated_layer_count.get();
461        let insert_at = top_layers[..len]
462            .iter()
463            .enumerate()
464            .find_map(|(index, existing)| {
465                existing
466                    .filter(|existing| layer.pixel_area() > existing.pixel_area())
467                    .map(|_| index)
468            })
469            .unwrap_or(len);
470
471        if insert_at >= TOP_ISOLATED_LAYER_LIMIT {
472            return;
473        }
474
475        let new_len = if len < TOP_ISOLATED_LAYER_LIMIT {
476            len + 1
477        } else {
478            TOP_ISOLATED_LAYER_LIMIT
479        };
480
481        let mut index = new_len.saturating_sub(1);
482        while index > insert_at {
483            top_layers[index] = top_layers[index - 1];
484            index -= 1;
485        }
486        top_layers[insert_at] = Some(layer);
487        self.top_isolated_layer_count.set(new_len);
488    }
489}
490
491pub(crate) fn gpu_stats_enabled() -> bool {
492    use std::sync::OnceLock;
493    static ENABLED: OnceLock<bool> = OnceLock::new();
494    *ENABLED.get_or_init(|| {
495        std::env::var("CRANPOSE_GPU_STATS")
496            .map(|v| matches!(v.as_str(), "1" | "true" | "yes"))
497            .unwrap_or(false)
498    })
499}
500
501#[cfg(test)]
502mod tests {
503    use super::*;
504
505    #[test]
506    fn layer_cache_counters_accumulate_and_reset() {
507        let stats = FrameStats::default();
508        stats.record_upload_bytes(512);
509        stats.record_layer_cache_hit(10, 20);
510        stats.record_layer_cache_hit(3, 4);
511        stats.record_layer_cache_miss(5, 6);
512        stats.record_layer_cache_eviction();
513
514        assert_eq!(stats.layer_cache_hits.get(), 2);
515        assert_eq!(stats.layer_cache_misses.get(), 1);
516        assert_eq!(stats.layer_cache_evictions.get(), 1);
517        assert_eq!(stats.layer_cache_hit_pixels.get(), 212);
518        assert_eq!(stats.layer_cache_miss_pixels.get(), 30);
519
520        stats.record_isolated_layer_render(
521            7,
522            8,
523            Some(9),
524            Rect {
525                x: 2.0,
526                y: 3.0,
527                width: 4.0,
528                height: 5.0,
529            },
530            LayerSurfaceReasons {
531                immediate_shadow: true,
532                ..LayerSurfaceReasons::default()
533            },
534        );
535        let snapshot = stats.snapshot();
536
537        assert_eq!(snapshot.isolated_layer_renders, 1);
538        assert_eq!(snapshot.isolated_layer_pixels, 56);
539        assert_eq!(snapshot.upload_bytes, 512);
540        assert_eq!(snapshot.layer_cache_hits, 2);
541        assert_eq!(snapshot.layer_cache_misses, 1);
542        let top_layers = snapshot.top_isolated_layers().collect::<Vec<_>>();
543        assert_eq!(top_layers.len(), 1);
544        assert_eq!(top_layers[0].node_id, Some(9));
545        assert_eq!(stats.layer_cache_hits.get(), 2);
546        assert_eq!(stats.layer_cache_misses.get(), 1);
547
548        stats.reset();
549
550        assert_eq!(stats.layer_cache_hits.get(), 0);
551        assert_eq!(stats.layer_cache_misses.get(), 0);
552        assert_eq!(stats.layer_cache_evictions.get(), 0);
553        assert_eq!(stats.layer_cache_hit_pixels.get(), 0);
554        assert_eq!(stats.layer_cache_miss_pixels.get(), 0);
555        assert_eq!(stats.upload_bytes.get(), 0);
556        assert_eq!(stats.isolated_layer_renders.get(), 0);
557        assert_eq!(stats.isolated_layer_pixels.get(), 0);
558        assert_eq!(stats.top_isolated_layer_count.get(), 0);
559    }
560
561    #[test]
562    fn maybe_print_snapshot_only_advances_frame_counter_when_enabled() {
563        let stats = FrameStats::default();
564        let snapshot = stats.snapshot();
565        let mut frame_count = 0;
566
567        stats.maybe_print_snapshot(snapshot, &mut frame_count, false);
568        assert_eq!(frame_count, 0);
569
570        stats.maybe_print_snapshot(snapshot, &mut frame_count, true);
571        assert_eq!(frame_count, 1);
572    }
573
574    #[test]
575    fn layer_surface_reasons_report_runtime_only_bits() {
576        let reasons = LayerSurfaceReasons {
577            immediate_shadow: true,
578            mixed_direct_content: true,
579            ..LayerSurfaceReasons::default()
580        };
581
582        assert!(reasons.has_any());
583        assert!(reasons.has_renderer_forced_surface());
584        assert_eq!(
585            reasons.labels().collect::<Vec<_>>(),
586            vec!["immediate_shadow", "mixed_direct_content"]
587        );
588        assert_eq!(reasons.display(), "immediate_shadow+mixed_direct_content");
589    }
590
591    #[test]
592    fn mixed_direct_content_only_is_diagnostic_not_isolating() {
593        let reasons = LayerSurfaceReasons {
594            mixed_direct_content: true,
595            ..LayerSurfaceReasons::default()
596        };
597
598        assert!(!reasons.has_any());
599        assert!(!reasons.has_renderer_forced_surface());
600        assert_eq!(
601            reasons.labels().collect::<Vec<_>>(),
602            vec!["mixed_direct_content"]
603        );
604        assert_eq!(reasons.display(), "mixed_direct_content");
605    }
606
607    #[test]
608    fn has_any_matches_has_isolating_requirement_for_each_requirement() {
609        let all_requirements = [
610            SurfaceRequirement::ExplicitOffscreen,
611            SurfaceRequirement::RenderEffect,
612            SurfaceRequirement::Backdrop,
613            SurfaceRequirement::GroupOpacity,
614            SurfaceRequirement::BlendMode,
615            SurfaceRequirement::ImmediateShadow,
616            SurfaceRequirement::TextMaterialMask,
617            SurfaceRequirement::MotionStableCapture,
618            SurfaceRequirement::NonTranslationTransform,
619            SurfaceRequirement::MixedDirectContent,
620        ];
621        for requirement in all_requirements {
622            let set = SurfaceRequirementSet::default().with(requirement);
623            let reasons = LayerSurfaceReasons::from(set);
624            assert_eq!(
625                reasons.has_any(),
626                set.has_isolating_requirement(),
627                "has_any vs has_isolating_requirement mismatch for {requirement:?}"
628            );
629        }
630    }
631
632    #[test]
633    fn top_isolated_layers_keep_largest_runtime_surfaces() {
634        let stats = FrameStats::default();
635        for index in 0..(TOP_ISOLATED_LAYER_LIMIT + 2) {
636            stats.record_isolated_layer_render(
637                16 + index as u32,
638                8 + index as u32,
639                Some(index),
640                Rect {
641                    x: index as f32,
642                    y: 0.0,
643                    width: 10.0,
644                    height: 10.0,
645                },
646                LayerSurfaceReasons {
647                    immediate_shadow: true,
648                    ..LayerSurfaceReasons::default()
649                },
650            );
651        }
652
653        let snapshot = stats.snapshot();
654        let top_layers = snapshot.top_isolated_layers().collect::<Vec<_>>();
655        assert_eq!(top_layers.len(), TOP_ISOLATED_LAYER_LIMIT);
656        assert_eq!(top_layers[0].node_id, Some(TOP_ISOLATED_LAYER_LIMIT + 1));
657        assert_eq!(top_layers[1].node_id, Some(TOP_ISOLATED_LAYER_LIMIT));
658    }
659}