Replace 4 counter track components with a single generic TrackCounter#5944
Replace 4 counter track components with a single generic TrackCounter#5944fatadel merged 11 commits intofirefox-devtools:mainfrom
Conversation
Make counters self-describing in terms of rendering by adding `display` field of `CounterDisplayConfig` type. The value is derived from a counter's `category` and `name` fields. This data is sufficient to understand how a counter should be rendered allowing us to remove hardcoded logic for each counter. This is the first PR for issue firefox-devtools#5752.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #5944 +/- ##
==========================================
- Coverage 85.33% 85.25% -0.09%
==========================================
Files 323 317 -6
Lines 32262 31935 -327
Branches 8895 8842 -53
==========================================
- Hits 27532 27226 -306
+ Misses 4298 4277 -21
Partials 432 432 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
canova
left a comment
There was a problem hiding this comment.
I'm a bit worried about the way we decide how we figure out which tooltip to show. Currently it works, but it looks brittle considering that another counter could have similar fields. I'm still leaning towards defining tooltip fields inside the profile format. But @fqueze was saying that it was difficult to make it generic. I think I would be okay with losing the l10n part of the tooltips to make it generic, so we can define all the things like the label and what it should display. What do you think?
|
I was thinking about something like this that we can define in the counter schema: type CounterTooltipDataSource =
| 'count' // samples.count[i]
| 'rate' // count / sampleTimeDelta
| 'cpu-ratio' // rate / maxCounterSampleCountPerMs
| 'accumulated' // accumulatedCounts[i] - minCount
| 'count-range' // total range across the visible graph
| 'selection-total' // sum over the preview selection
| 'sample-number'; // samples.number[i] — row is omitted when null
type CounterTooltipFormatter = 'bytes' | 'bytes-per-second' | 'percent' | 'number';
type CounterTooltipRow =
| { type: 'value'; source: CounterTooltipDataSource; format: CounterTooltipFormatter; label: string }
| { type: 'separator' };
// In CounterDisplayConfig:
tooltipRows: CounterTooltipRow[];And then we could use that like: tooltipRows: [
{ type: 'value', source: 'accumulated', format: 'bytes', label: 'relative memory at this time' },
{ type: 'value', source: 'count-range', format: 'bytes', label: 'memory range in graph' },
{ type: 'value', source: 'sample-number', format: 'number', label: 'allocations/deallocations since previous sample' },
]But not so sure if it works with all the counters that we have. |
Ideally we would have a generic implementation defined in the counter schema inside the profile JSON, and we can have specific overrides for specific counters (eg power, to show the CO2e values), similar to how network markers are handled differently. |
|
Yeah that sounds good. Also, adding context here after talking about it in sync. It would make sense to keep a |
Collapse the separate Memory, Power, ProcessCPU, and Bandwidth track implementations into a single TrackCounter component that renders any counter type using CounterDisplayConfig from PR firefox-devtools#5912. The LocalTrack union type is simplified from 8 variants to 5 by replacing 'memory', 'power', 'process-cpu', and 'bandwidth' with a single 'counter' type. The component branches on display.graphType for canvas drawing (accumulated vs rate) and on display.unit for tooltip rendering (bytes, pWh, percent, etc.). Track index ordering (for URL backward compatibility) is handled by a category-based mapping function. Display ordering uses the new display.sortWeight field. Part of firefox-devtools#5752.
cc362ba to
925b5f7
Compare
No, this was not intentional. I have realized that I missed it because the profile I've used did not have the Compositor track. |
|
Thanks for the feedback on tooltip rendering, @fqueze and @canova ! After thinking about it and consulting with Claude, here's the approach I'd like to propose. It combines @fqueze's idea of "generic default + specific overrides" with @canova's suggestion of a tooltipType field. The concept: generic by default, with opt-in overrides for rich tooltips We add an optional tooltipType field to CounterDisplayConfig: export type CounterDisplayConfig = { }; The logic in the component is:
|
|
(apparently the comment I wrote in the morning wasn't sent, and I lost the message. rewriting) I've thought about it a bit more and discussed with @fqueze and I think it might make sense to not add In the code where we decide which tooltip to show, instead of this field, we can look at the This would prevent us from adding an intermediate display field that will be removed soon. WDYT? |
Yeah, it makes sense. Instead of adding a |
# Conflicts: # src/app-logic/constants.ts # src/components/timeline/TrackBandwidthGraph.tsx # src/components/timeline/TrackMemoryGraph.tsx # src/components/timeline/TrackPowerGraph.tsx # src/profile-logic/process-profile.ts # src/profile-logic/processed-profile-versioning.ts # src/test/components/TrackBandwidth.test.tsx # src/test/components/TrackMemory.test.tsx # src/test/fixtures/profiles/processed-profile.ts
canova
left a comment
There was a problem hiding this comment.
Nice!
I spent quite some time reviewing this but unfortunately I'm not done yet. Pushing my review comments that I added so far so you can look at them sooner.
| // Power tracks are there only if the power feature is enabled. So they should | ||
| // be visible by default whenever they're included in a profile. (fallthrough) |
There was a problem hiding this comment.
How do we decide which track to make it visible by default now?
There was a problem hiding this comment.
Could you elaborate on your question, please? Here specifically we were returning true for all the counter tracks, the same happens now.
| counters !== null && | ||
| counters[track.counterIndex].display.markerSchemaLocation === | ||
| 'timeline-memory' |
There was a problem hiding this comment.
Previously we only checked for track.type === 'memory, why extra checks now?
Also, ideally getProcessesWithMemoryTrack should go away to make this generic. Can be done in a follow-up.
There was a problem hiding this comment.
Oh I've missed this one. Yeah this can be simplified. Pushing now!
There was a problem hiding this comment.
Regarding getProcessesWithMemoryTrack selector, I've created the following issue - #5962.
|
|
||
| const { category, name } = counter; | ||
|
|
||
| // Power tooltip — delegate to the dedicated component. |
There was a problem hiding this comment.
Could you file an issue for making the tooltip generic as a follow-up so we can discuss how we can make it generic and think about the counter schema for this one?
Reshape LOCAL_TRACK_INDEX_ORDER and LOCAL_TRACK_DISPLAY_ORDER so that 'counter' occupies a real slot alongside the other local-track types. LOCAL_TRACK_DISPLAY_ORDER becomes the single source of truth for cross-type ordering; display.sortWeight is reduced to a tie-breaker between two counters. Add a v16 URL upgrader that remaps localTrackOrderByPid and hiddenLocalTracksByPid from the old per-counter-type indexes (memory=2, power=6, process-cpu=5, bandwidth=8) to the new single counter slot so existing URLs keep pointing at the same tracks.
| Array [ | ||
| "moveTo", | ||
| 0, | ||
| 24, |
There was a problem hiding this comment.
The single line-rate getY uses the zero-special-case from the old bandwidth code (if (!rawY) return deviceHeight + deviceLineHalfWidth), which pushes exact zeros just below the visible canvas so near-zero values are noticeable. Old TrackProcessCPUGraph didn't have this case - it drew zero samples on the baseline - which is why here the snapshot now shows moveTo(0, 26) instead of moveTo(0, 24). This keeps the rate path uniform across all counters. Happy to revert to per-counter behaviour if you'd rather preserve the old ProcessCPU rendering.
There was a problem hiding this comment.
I see. I think it's okay to keep this behavior. And let's see if people are going to like it or not. It might be an improvement in general.
|
Thanks for the early feedback, @canova! Please, take another look when you have time 🙏🏻 |
- Pass a boolean hasMarkers prop to TrackCounterGraph instead of the whole CounterDisplayConfig, and move the track wrapper's sizing into CSS so the TSX stays free of inline style. - Use a switch with assertExhaustiveCheck(display.graphType) in both the canvas draw and the hovered-sample dot, so adding a new graph type surfaces as a TypeScript error at those sites. - Replace the optional-chain + null-and-undefined check on counters with ensureExists, both in getTimelineHeight and in getProcessesWithMemoryTrack. - Drop a stray Math.round from the rate path's y-coordinate so rate counter snapshots match their pre-unification values.
c79f4a7 to
53d42a2
Compare
canova
left a comment
There was a problem hiding this comment.
I'm done doing a full review now! Looks pretty good to me! I added some nits, but I have one blocking comment which is about using memory specific markers when markerSchemaLocation is specified. I think this will result us seeing memory related markers for all the counters that specify a markerSchemaLocation, so it would be good to make this part generic as well. See my comment below too.
Also I found another thing. This won't work anymore after this PR:
We can add this context menu item for all the counter tracks.
Also, I noticed that we don't actually touch the profile upgraders. So I think there is nothing that prevents us from doing a deploy, right?
Oh actually you're right. I was confusing |
And even the changes in |
Oh indeed! I now remember - I was reordering that by the sortWeight 😅 Which of the following would you prefer?
I vote for 3. |
|
By the way, thanks a lot for your review @canova! I will soon address all the issues. |
|
@fatadel Yeah, I would prefer 3 too. But still there is no reason to block the deploy :) |
Rework TrackCounter so its optional marker row filters by the counter's own display.markerSchemaLocation rather than reusing the memory-specific TimelineMarkersMemory component. Introduces a per-thread selector factory getTimelineMarkerIndexesBySchemaLocation(location) with a location-keyed cache of memoized selectors, plus a new TimelineMarkersCounter component that plugs it in. TimelineMarkersMemory stays untouched for TrackThread's "show memory markers on the main thread when there's no memory track" fallback; the corresponding CSS rule keeps its height so that path is unaffected until the follow-up issue firefox-devtools#5962 retires it.
- computeLocalTracksByPid no longer drops power counters with at most two samples. - Counter-counter tie-break: keepProfileThreadOrder takes precedence over display.sortWeight in _getDefaultLocalTrackOrder. - Reorder the v62 processed-profile upgrader branches to match the sortWeight-ascending layout in _deriveCounterDisplay. - Drive --graph-height and --markers-height on the TrackCounter wrapper from the corresponding TS constants. - Accept 'counter' in TrackContextMenu's ALLOWED_TYPES. - Document why event-delay and process-cpu have no entries in the v16 upgrader's slot maps.
canova
left a comment
There was a problem hiding this comment.
Looks good! I added two more comments below but I think we can land after.
…ma-location selector Tightens the marker schema location field to MarkerDisplayLocation throughout the counter display config and consumers, removing the lingering string cast in the per-thread selector. The dedicated memory/IPC marker selectors are now built on top of getTimelineMarkerIndexesBySchemaLocation so there is a single source of truth for the schema-location filtering.
|
@canova, thank you for the thorough review! I've addressed those issues now. |


Collapse the separate Memory, Power, ProcessCPU, and Bandwidth track
implementations into a single TrackCounter component that renders any
counter type using CounterDisplayConfig from PR #5912.
The LocalTrack union is simplified from 8 variants to 5 by replacing
'memory', 'power', 'process-cpu', and 'bandwidth' with a single
'counter' type. The graph branches on display.graphType for canvas
drawing (accumulated vs rate) and on counter.category/counter.name
for tooltip rendering, while display.unit and display.label drive
value formatting and the track label.
Counter tracks get a single dedicated slot in LOCAL_TRACK_INDEX_ORDER;
existing URLs are remapped by a new v16 URL upgrader. Display ordering
between counters uses the new display.sortWeight field, falling back
to a natural sort on counter name.
Closes #5752.