Skip to main content

cranpose_core/
diagnostics.rs

1//! The framework's diagnostic and ablation switches, each named by an
2//! environment variable, in one struct: [`DIAGNOSTICS`].
3//!
4//! A switch is read once and cached until a [`crate::debug_toggles`]
5//! override changes, because reading the environment walks the whole
6//! variable array under a lock, and several switches sit on paths that run
7//! once a frame or once a node. Android seeds the variables from
8//! `debug.cranpose.*` system properties before the first read.
9//!
10//! Without the `diagnostics` feature every switch reads as unset, whatever
11//! the environment holds, and the code behind it compiles out of the build.
12
13use std::str::FromStr;
14
15#[cfg(feature = "diagnostics")]
16use crate::debug_toggles::DebugToggle;
17
18/// One diagnostic or ablation switch, named by an environment variable.
19#[cfg(feature = "diagnostics")]
20pub struct Diagnostic(DebugToggle);
21
22/// One diagnostic or ablation switch; this build leaves them out, so it
23/// always reads as unset.
24#[cfg(not(feature = "diagnostics"))]
25pub struct Diagnostic(());
26
27impl Diagnostic {
28    #[cfg(feature = "diagnostics")]
29    const fn new(name: &'static str) -> Self {
30        Self(DebugToggle::new(name))
31    }
32
33    #[cfg(not(feature = "diagnostics"))]
34    const fn new(_name: &'static str) -> Self {
35        Self(())
36    }
37
38    /// Reads the switch's value through `read`; `None` while it is unset.
39    pub fn with<R>(&self, read: impl FnOnce(Option<&str>) -> R) -> R {
40        #[cfg(feature = "diagnostics")]
41        {
42            self.0.with(read)
43        }
44        #[cfg(not(feature = "diagnostics"))]
45        {
46            read(None)
47        }
48    }
49
50    /// Whether the switch holds any value.
51    pub fn is_set(&self) -> bool {
52        self.with(|value| value.is_some())
53    }
54
55    /// Whether the switch is on: `1`, `true` or `yes`.
56    pub fn flag(&self) -> bool {
57        self.with(|value| matches!(value, Some("1" | "true" | "yes")))
58    }
59
60    /// Whether the switch holds exactly `expected`.
61    pub fn equals(&self, expected: &str) -> bool {
62        self.with(|value| value == Some(expected))
63    }
64
65    /// The switch parsed as `T`, when set and well formed.
66    pub fn parse<T: FromStr>(&self) -> Option<T> {
67        self.with(|value| value.and_then(|value| value.trim().parse().ok()))
68    }
69
70    /// The switch as a threshold in milliseconds: a finite number from zero
71    /// up. Zero reports every frame.
72    pub fn threshold_ms(&self) -> Option<f64> {
73        self.parse::<f64>()
74            .filter(|threshold| threshold.is_finite() && *threshold >= 0.0)
75    }
76}
77
78/// Every diagnostic and ablation switch, by the environment variable that
79/// names it. An unset switch leaves the framework as it ships.
80pub struct Diagnostics {
81    /// `CRANPOSE_A11Y_SYNC`: Android publishes the accessibility tree on
82    /// the UI thread (`1`) or off it (`0`).
83    pub a11y_sync: Diagnostic,
84    /// `CRANPOSE_ABLATE`: the comma-separated renderer stages to leave out.
85    pub ablate: Diagnostic,
86    /// `CRANPOSE_ADPF`: `0` turns off Android's performance hint session.
87    pub adpf: Diagnostic,
88    /// `CRANPOSE_ASSERT_STATE_CHAIN`: checks every state record chain.
89    pub assert_state_chain: Diagnostic,
90    /// `CRANPOSE_ASYNC_HAPTICS`: `0`, `false`, `off` or `no` plays Android
91    /// haptics on the calling thread.
92    pub async_haptics: Diagnostic,
93    /// `CRANPOSE_BACKGROUND_PIPELINES`: `0` or `1` turns compiling
94    /// pipelines on background threads off or on.
95    pub background_pipelines: Diagnostic,
96    /// `COMPOSE_DEBUG_MODIFIERS`: logs modifier chains.
97    pub compose_debug_modifiers: Diagnostic,
98    /// `COMPOSE_DEBUG_SLOT_TABLE`: logs the slot table.
99    pub compose_debug_slot_table: Diagnostic,
100    /// `CRANPOSE_CORE_PIN`: `0` keeps the render threads off the fast cores.
101    pub core_pin: Diagnostic,
102    /// `CRANPOSE_DEBUG_SCOPE_LABELS`: labels recompose scopes in traces.
103    pub debug_scope_labels: Diagnostic,
104    /// `CRANPOSE_DESKTOP_FRAME_TELEMETRY`: logs desktop frames over the
105    /// default threshold.
106    pub desktop_frame_telemetry: Diagnostic,
107    /// `CRANPOSE_DESKTOP_FRAME_TELEMETRY_MS`: logs desktop frames over this
108    /// many milliseconds.
109    pub desktop_frame_telemetry_ms: Diagnostic,
110    /// `CRANPOSE_DISABLE_NAV_SNAP`: the navigation bar does not snap.
111    pub disable_nav_snap: Diagnostic,
112    /// `CRANPOSE_DISABLE_SCROLL_TRANSLATE`: a scroll rebuilds its layers in
113    /// place of moving them.
114    pub disable_scroll_translate: Diagnostic,
115    /// `CRANPOSE_DISABLE_STRUCTURAL_DIRT`: the shell does not mark the
116    /// parents of structural changes.
117    pub disable_structural_dirt: Diagnostic,
118    /// `CRANPOSE_ENCODE_DELAY_MS`: waits this many milliseconds before each
119    /// frame's encode on the present thread.
120    pub encode_delay_ms: Diagnostic,
121    /// `CRANPOSE_FRAME_LEAD_TENTHS`: pins the frame lead, in tenths of a
122    /// frame period.
123    pub frame_lead_tenths: Diagnostic,
124    /// `CRANPOSE_FRAME_STAGE_TELEMETRY`: logs the shell's frame stages over
125    /// the default threshold.
126    pub frame_stage_telemetry: Diagnostic,
127    /// `CRANPOSE_FRAME_STAGE_TELEMETRY_MS`: logs the shell's frame stages
128    /// over this many milliseconds.
129    pub frame_stage_telemetry_ms: Diagnostic,
130    /// `CRANPOSE_GLASS_MATERIAL_FOLDS`: `1` or `0` forces the glass
131    /// material folds on or off.
132    pub glass_material_folds: Diagnostic,
133    /// `CRANPOSE_GPU_FENCE_PROFILE`: profiles GPU fences.
134    pub gpu_fence_profile: Diagnostic,
135    /// `CRANPOSE_GPU_PASS_TIMING`: times GPU passes with timestamp queries.
136    pub gpu_pass_timing: Diagnostic,
137    /// `CRANPOSE_GPU_SHADOW_CACHE_DIAG`: logs the shadow cache.
138    pub gpu_shadow_cache_diag: Diagnostic,
139    /// `CRANPOSE_GPU_STAGE_DIAG`: logs the renderer's frame stages.
140    pub gpu_stage_diag: Diagnostic,
141    /// `CRANPOSE_GPU_STATS`: collects and prints the renderer's frame stats.
142    pub gpu_stats: Diagnostic,
143    /// `CRANPOSE_LAYOUT_INVALIDATION_DISPATCH_DIAG`: logs layout
144    /// invalidations (`all` for every one).
145    pub layout_invalidation_dispatch_diag: Diagnostic,
146    /// `CRANPOSE_LAYOUT_MEASURE_TELEMETRY`: logs measure passes over the
147    /// default threshold.
148    pub layout_measure_telemetry: Diagnostic,
149    /// `CRANPOSE_LAYOUT_MEASURE_TELEMETRY_MS`: logs measure passes over this
150    /// many milliseconds.
151    pub layout_measure_telemetry_ms: Diagnostic,
152    /// `CRANPOSE_LAYOUT_REPASS_SCHEDULE_DIAG`: logs layout repass
153    /// scheduling (`all` for every one).
154    pub layout_repass_schedule_diag: Diagnostic,
155    /// `CRANPOSE_LAZY_CACHE_TELEMETRY`: logs the lazy list item cache.
156    pub lazy_cache_telemetry: Diagnostic,
157    /// `CRANPOSE_LAZY_ITEM_TELEMETRY`: logs lazy list item work.
158    pub lazy_item_telemetry: Diagnostic,
159    /// `CRANPOSE_LAZY_MEASURE_TELEMETRY`: logs lazy list measures.
160    pub lazy_measure_telemetry: Diagnostic,
161    /// `CRANPOSE_LOCATION_KEY_DIAGNOSTICS`: logs location key collisions.
162    pub location_key_diagnostics: Diagnostic,
163    /// `CRANPOSE_NATIVE_TRACE`: prints native window events.
164    pub native_trace: Diagnostic,
165    /// `CRANPOSE_NATIVE_WINDOW_TIMING`: prints native window timings.
166    pub native_window_timing: Diagnostic,
167    /// `CRANPOSE_NO_BACKDROP_CACHE`: draws backdrops without their cache.
168    pub no_backdrop_cache: Diagnostic,
169    /// `CRANPOSE_NO_EFFECT_DOMAINS`: effects sample their whole sources.
170    pub no_effect_domains: Diagnostic,
171    /// `CRANPOSE_NO_FILL_CACHE`: draws fills without the opaque prefix
172    /// cache.
173    pub no_fill_cache: Diagnostic,
174    /// `CRANPOSE_NO_INTERIORS_FIRST`: draws shape interiors in paint order.
175    pub no_interiors_first: Diagnostic,
176    /// `CRANPOSE_NO_SHADER_FLATTEN`: compiles shaders without flattening.
177    pub no_shader_flatten: Diagnostic,
178    /// `CRANPOSE_NO_SHADER_FOLD`: compiles shaders without folding
179    /// constants.
180    pub no_shader_fold: Diagnostic,
181    /// `CRANPOSE_NO_SHADER_SPECIALIZATION`: draws with the general shaders.
182    pub no_shader_specialization: Diagnostic,
183    /// `CRANPOSE_PACING_DIAG`: logs the desktop frame pacing.
184    pub pacing_diag: Diagnostic,
185    /// `CRANPOSE_PIPELINE_DISK_CACHE`: `0` or `1` turns the pipeline disk
186    /// cache off or on.
187    pub pipeline_disk_cache: Diagnostic,
188    /// `CRANPOSE_PRESENT_THREAD`: chooses whether Android presents on a
189    /// thread of its own.
190    pub present_thread: Diagnostic,
191    /// `CRANPOSE_PROBE_DRAW_PASSES`: draws the first this many draw passes.
192    pub probe_draw_passes: Diagnostic,
193    /// `CRANPOSE_PROBE_PASSES`: encodes the first this many passes.
194    pub probe_passes: Diagnostic,
195    /// `CRANPOSE_RECOMPOSE_SCOPE_TELEMETRY_MS`: logs recompose scopes over
196    /// this many milliseconds.
197    pub recompose_scope_telemetry_ms: Diagnostic,
198    /// `CRANPOSE_RECOMP_DIAG`: prints the recomposition rate.
199    pub recomp_diag: Diagnostic,
200    /// `CRANPOSE_RENDER_PHASE_DIRTY_DIAG`: logs what the render phase finds
201    /// dirty.
202    pub render_phase_dirty_diag: Diagnostic,
203    /// `CRANPOSE_ROBOT_BUTTON_HELPER_DIAG`: prints the robot's button
204    /// lookups.
205    pub robot_button_helper_diag: Diagnostic,
206    /// `CRANPOSE_SCENE_UPDATE_DIAG`: logs scene updates.
207    pub scene_update_diag: Diagnostic,
208    /// `CRANPOSE_SHAPE_VARIANTS`: `0` draws shapes with the general
209    /// pipeline.
210    pub shape_variants: Diagnostic,
211    /// `CRANPOSE_SKIP_SHADOWS`: leaves shadows out.
212    pub skip_shadows: Diagnostic,
213    /// `CRANPOSE_STRUCTURAL_DIAG`: logs structural child syncs.
214    pub structural_diag: Diagnostic,
215    /// `CRANPOSE_SUBCOMPOSE_TELEMETRY`: logs subcompositions.
216    pub subcompose_telemetry: Diagnostic,
217    /// `CRANPOSE_SURVIVE_GPU_ERRORS`: `0` stops on a GPU validation error.
218    pub survive_gpu_errors: Diagnostic,
219    /// `CRANPOSE_TEXT_ATLAS_FALLBACK_DIAG`: logs text that misses the glyph
220    /// atlas.
221    pub text_atlas_fallback_diag: Diagnostic,
222    /// `CRANPOSE_TEXT_LAYOUT_TELEMETRY`: logs text layouts.
223    pub text_layout_telemetry: Diagnostic,
224    /// `CRANPOSE_UPDATE_STAGE_TELEMETRY_MS`: logs the shell's update stages
225    /// over this many milliseconds.
226    pub update_stage_telemetry_ms: Diagnostic,
227    /// `CRANPOSE_VALIDATE_SLOTS`: validates the slot table after each
228    /// composition.
229    pub validate_slots: Diagnostic,
230    /// `CRANPOSE_WGPU_RENDER_STAGE_TELEMETRY`: logs the renderer's stages
231    /// over the default threshold.
232    pub wgpu_render_stage_telemetry: Diagnostic,
233    /// `CRANPOSE_WGPU_RENDER_STAGE_TELEMETRY_MS`: logs the renderer's stages
234    /// over this many milliseconds.
235    pub wgpu_render_stage_telemetry_ms: Diagnostic,
236}
237
238/// The framework's diagnostic and ablation switches.
239pub static DIAGNOSTICS: Diagnostics = Diagnostics {
240    a11y_sync: Diagnostic::new("CRANPOSE_A11Y_SYNC"),
241    ablate: Diagnostic::new("CRANPOSE_ABLATE"),
242    adpf: Diagnostic::new("CRANPOSE_ADPF"),
243    assert_state_chain: Diagnostic::new("CRANPOSE_ASSERT_STATE_CHAIN"),
244    async_haptics: Diagnostic::new("CRANPOSE_ASYNC_HAPTICS"),
245    background_pipelines: Diagnostic::new("CRANPOSE_BACKGROUND_PIPELINES"),
246    compose_debug_modifiers: Diagnostic::new("COMPOSE_DEBUG_MODIFIERS"),
247    compose_debug_slot_table: Diagnostic::new("COMPOSE_DEBUG_SLOT_TABLE"),
248    core_pin: Diagnostic::new("CRANPOSE_CORE_PIN"),
249    debug_scope_labels: Diagnostic::new("CRANPOSE_DEBUG_SCOPE_LABELS"),
250    desktop_frame_telemetry: Diagnostic::new("CRANPOSE_DESKTOP_FRAME_TELEMETRY"),
251    desktop_frame_telemetry_ms: Diagnostic::new("CRANPOSE_DESKTOP_FRAME_TELEMETRY_MS"),
252    disable_nav_snap: Diagnostic::new("CRANPOSE_DISABLE_NAV_SNAP"),
253    disable_scroll_translate: Diagnostic::new("CRANPOSE_DISABLE_SCROLL_TRANSLATE"),
254    disable_structural_dirt: Diagnostic::new("CRANPOSE_DISABLE_STRUCTURAL_DIRT"),
255    encode_delay_ms: Diagnostic::new("CRANPOSE_ENCODE_DELAY_MS"),
256    frame_lead_tenths: Diagnostic::new("CRANPOSE_FRAME_LEAD_TENTHS"),
257    frame_stage_telemetry: Diagnostic::new("CRANPOSE_FRAME_STAGE_TELEMETRY"),
258    frame_stage_telemetry_ms: Diagnostic::new("CRANPOSE_FRAME_STAGE_TELEMETRY_MS"),
259    glass_material_folds: Diagnostic::new("CRANPOSE_GLASS_MATERIAL_FOLDS"),
260    gpu_fence_profile: Diagnostic::new("CRANPOSE_GPU_FENCE_PROFILE"),
261    gpu_pass_timing: Diagnostic::new("CRANPOSE_GPU_PASS_TIMING"),
262    gpu_shadow_cache_diag: Diagnostic::new("CRANPOSE_GPU_SHADOW_CACHE_DIAG"),
263    gpu_stage_diag: Diagnostic::new("CRANPOSE_GPU_STAGE_DIAG"),
264    gpu_stats: Diagnostic::new("CRANPOSE_GPU_STATS"),
265    layout_invalidation_dispatch_diag: Diagnostic::new(
266        "CRANPOSE_LAYOUT_INVALIDATION_DISPATCH_DIAG",
267    ),
268    layout_measure_telemetry: Diagnostic::new("CRANPOSE_LAYOUT_MEASURE_TELEMETRY"),
269    layout_measure_telemetry_ms: Diagnostic::new("CRANPOSE_LAYOUT_MEASURE_TELEMETRY_MS"),
270    layout_repass_schedule_diag: Diagnostic::new("CRANPOSE_LAYOUT_REPASS_SCHEDULE_DIAG"),
271    lazy_cache_telemetry: Diagnostic::new("CRANPOSE_LAZY_CACHE_TELEMETRY"),
272    lazy_item_telemetry: Diagnostic::new("CRANPOSE_LAZY_ITEM_TELEMETRY"),
273    lazy_measure_telemetry: Diagnostic::new("CRANPOSE_LAZY_MEASURE_TELEMETRY"),
274    location_key_diagnostics: Diagnostic::new("CRANPOSE_LOCATION_KEY_DIAGNOSTICS"),
275    native_trace: Diagnostic::new("CRANPOSE_NATIVE_TRACE"),
276    native_window_timing: Diagnostic::new("CRANPOSE_NATIVE_WINDOW_TIMING"),
277    no_backdrop_cache: Diagnostic::new("CRANPOSE_NO_BACKDROP_CACHE"),
278    no_effect_domains: Diagnostic::new("CRANPOSE_NO_EFFECT_DOMAINS"),
279    no_fill_cache: Diagnostic::new("CRANPOSE_NO_FILL_CACHE"),
280    no_interiors_first: Diagnostic::new("CRANPOSE_NO_INTERIORS_FIRST"),
281    no_shader_flatten: Diagnostic::new("CRANPOSE_NO_SHADER_FLATTEN"),
282    no_shader_fold: Diagnostic::new("CRANPOSE_NO_SHADER_FOLD"),
283    no_shader_specialization: Diagnostic::new("CRANPOSE_NO_SHADER_SPECIALIZATION"),
284    pacing_diag: Diagnostic::new("CRANPOSE_PACING_DIAG"),
285    pipeline_disk_cache: Diagnostic::new("CRANPOSE_PIPELINE_DISK_CACHE"),
286    present_thread: Diagnostic::new("CRANPOSE_PRESENT_THREAD"),
287    probe_draw_passes: Diagnostic::new("CRANPOSE_PROBE_DRAW_PASSES"),
288    probe_passes: Diagnostic::new("CRANPOSE_PROBE_PASSES"),
289    recompose_scope_telemetry_ms: Diagnostic::new("CRANPOSE_RECOMPOSE_SCOPE_TELEMETRY_MS"),
290    recomp_diag: Diagnostic::new("CRANPOSE_RECOMP_DIAG"),
291    render_phase_dirty_diag: Diagnostic::new("CRANPOSE_RENDER_PHASE_DIRTY_DIAG"),
292    robot_button_helper_diag: Diagnostic::new("CRANPOSE_ROBOT_BUTTON_HELPER_DIAG"),
293    scene_update_diag: Diagnostic::new("CRANPOSE_SCENE_UPDATE_DIAG"),
294    shape_variants: Diagnostic::new("CRANPOSE_SHAPE_VARIANTS"),
295    skip_shadows: Diagnostic::new("CRANPOSE_SKIP_SHADOWS"),
296    structural_diag: Diagnostic::new("CRANPOSE_STRUCTURAL_DIAG"),
297    subcompose_telemetry: Diagnostic::new("CRANPOSE_SUBCOMPOSE_TELEMETRY"),
298    survive_gpu_errors: Diagnostic::new("CRANPOSE_SURVIVE_GPU_ERRORS"),
299    text_atlas_fallback_diag: Diagnostic::new("CRANPOSE_TEXT_ATLAS_FALLBACK_DIAG"),
300    text_layout_telemetry: Diagnostic::new("CRANPOSE_TEXT_LAYOUT_TELEMETRY"),
301    update_stage_telemetry_ms: Diagnostic::new("CRANPOSE_UPDATE_STAGE_TELEMETRY_MS"),
302    validate_slots: Diagnostic::new("CRANPOSE_VALIDATE_SLOTS"),
303    wgpu_render_stage_telemetry: Diagnostic::new("CRANPOSE_WGPU_RENDER_STAGE_TELEMETRY"),
304    wgpu_render_stage_telemetry_ms: Diagnostic::new("CRANPOSE_WGPU_RENDER_STAGE_TELEMETRY_MS"),
305};