Measuring a Rust Desktop Shell Before Optimizing It
A slow build, a flickering panel, and an unavailable service looked related until process and state measurements separated them.
A Glimpse development session had three symptoms at once: the panel flickered, one service reported unavailable, and Rust builds felt unusually slow. Treating them as one performance problem would have produced confident changes to the wrong subsystem, so I measured the runtime, service state, and build pipeline separately.
This is a field note rather than a benchmark. The session did not produce a durable comparison table, and I will not turn a handful of observations into one. It did produce a diagnostic order that made the problems distinguishable.
Start with the process that owns the visible symptom
For panel flicker, the first useful question was whether the shell process was busy, blocked, or repeatedly recreating UI. A short sample of process CPU, memory, thread state, and logs is more informative than a system-wide dashboard because it stays attached to the component that owns the pixels.
Linux process states helped separate waiting from work. A thread in interruptible sleep is usually waiting for an event. A thread stuck in uninterruptible sleep points toward kernel I/O. Sustained CPU points somewhere else again. None of those states proves a cause, but they tell you which evidence to collect next.
The unavailable label also needed its own trace. It came from a service state transition, so the right evidence was the sequence of snapshots and the conditions that caused publication. A renderer can flicker because it receives semantically unchanged updates even when the underlying service is healthy. That led to suppressing notifications when only an internal reason changed and the configured and effective theme modes did not.
Build speed is a different experiment
Compilation time belongs to the compiler process, cache, filesystem, and build graph. Sampling the running panel cannot explain it. For that path I checked compiler concurrency, target-directory behavior, incremental compilation, and whether sccache was producing hits rather than merely being installed.
The distinction between elapsed time and CPU time matters here. A build can feel slow because it is waiting on storage or contending with another build, even if no single compiler process looks expensive. Cargo's timing output and repeated warm builds can separate dependency compilation from linking and cache misses, but only if the source tree and build options stay fixed.
I kept three evidence lanes
The session became manageable once I separated the evidence:
- Visible UI behavior is traced through the process and render updates that own it.
- Service availability is traced through state transitions and their publication rules.
- Developer build latency is measured as a separate workload with controlled warm and cold runs.
Those lanes can interact. A development build may saturate the machine enough to make a UI stutter, or a noisy service may increase rendering work. The interaction should be demonstrated by aligned samples, not inferred because the symptoms happened during the same afternoon.
Performance work becomes much smaller once the nouns are precise. "Glimpse is slow" was not one bug. It was a rendering symptom, a state-notification question, and a build workflow that each needed a different instrument.