Skip to content

Commit c231ed3

Browse files
authored
Drive counter tooltips from a tooltipRows schema (#6023)
Replace the four hardcoded category branches in TrackCounterGraph's tooltip rendering (Memory / Power / Bandwidth / processCPU) with a declarative tooltipRows array on CounterDisplayConfig. Each row picks a data source (count, rate, accumulated, selection-total, …), a value format (unit, optional CO₂e, optional auto-scale ladder), and a label. The new TrackCounterTooltip component iterates the rows, resolves each source, formats the value, and renders through TooltipDetails. Memory and CPU tooltips now use the same TooltipDetails layout as Power and Bandwidth. Profile labels are raw English so the format stays self-describing. The frontend translates labels it recognizes from a private allow-list and renders unknown ones verbatim. The dedicated TooltipTrackPower component is removed; the power/energy unit ladders move into the formatter. Bumps the processed profile format to v63. The v63 upgrader derives tooltipRows from each counter's category and name. Closes #5961
1 parent a2f2f13 commit c231ed3

22 files changed

Lines changed: 1005 additions & 573 deletions

docs-developer/CHANGELOG-formats.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ Note that this is not an exhaustive list. Processed profile format upgraders can
66

77
## Processed profile format
88

9+
### Version 63
10+
11+
A new `tooltipRows` field was added to `CounterDisplayConfig`.
12+
This metadata describes the rows of the counter's hover tooltip (data source, value format, label).
13+
For existing profiles, the rows are derived from the counter's `category` and `name`.
14+
915
### Version 62
1016

1117
A new `display` field of type `CounterDisplayConfig` was added to `RawCounter`.

locales/en-US/app.ftl

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -964,9 +964,28 @@ TrackNameButton--hide-process =
964964
## the UI. To learn more about it, visit:
965965
## https://profiler.firefox.com/docs/#/./memory-allocations?id=memory-track
966966

967-
TrackMemoryGraph--relative-memory-at-this-time = relative memory at this time
968-
TrackMemoryGraph--memory-range-in-graph = memory range in graph
969-
TrackMemoryGraph--allocations-and-deallocations-since-the-previous-sample = allocations and deallocations since the previous sample
967+
# Variables:
968+
# $value (String) - the relative memory at this time (e.g. "5MB")
969+
TrackMemoryGraph--relative-memory-at-this-time2 = { $value }
970+
.label = relative memory at this time
971+
972+
# Variables:
973+
# $value (String) - the memory range across the graph (e.g. "5MB")
974+
TrackMemoryGraph--memory-range-in-graph2 = { $value }
975+
.label = memory range in graph
976+
977+
# Variables:
978+
# $value (String) - count of allocations and deallocations since the previous sample
979+
TrackMemoryGraph--allocations-and-deallocations-since-the-previous-sample2 = { $value }
980+
.label = allocations and deallocations since the previous sample
981+
982+
## TrackProcessCPUGraph
983+
## This is used to show the CPU usage of a process over time in the timeline.
984+
985+
# Variables:
986+
# $value (String) - the CPU usage at this sample (e.g. "50%")
987+
TrackProcessCPUGraph--cpu = { $value }
988+
.label = CPU
970989
971990
## TrackPower
972991
## This is used to show the power used by the CPU and other chips in a computer,

src/app-logic/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const GECKO_PROFILE_VERSION = 34;
1212
// The current version of the "processed" profile format.
1313
// Please don't forget to update the processed profile format changelog in
1414
// `docs-developer/CHANGELOG-formats.md`.
15-
export const PROCESSED_PROFILE_VERSION = 62;
15+
export const PROCESSED_PROFILE_VERSION = 63;
1616

1717
// The following are the margin sizes for the left and right of the timeline. Independent
1818
// components need to share these values.

src/components/timeline/TrackCounterGraph.tsx

Lines changed: 12 additions & 231 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,23 @@
44

55
import * as React from 'react';
66
import { InView } from 'react-intersection-observer';
7-
import { Localized } from '@fluent/react';
87
import { withSize } from 'firefox-profiler/components/shared/WithSize';
98
import {
109
getStrokeColor,
1110
getFillColor,
1211
getDotColor,
1312
} from 'firefox-profiler/profile-logic/graph-color';
1413
import explicitConnect from 'firefox-profiler/utils/connect';
15-
import {
16-
formatBytes,
17-
formatNumber,
18-
formatPercent,
19-
} from 'firefox-profiler/utils/format-numbers';
2014
import { bisectionRight } from 'firefox-profiler/utils/bisect';
2115
import {
2216
getCommittedRange,
2317
getCounterSelectors,
24-
getPreviewSelection,
2518
getProfileInterval,
2619
} from 'firefox-profiler/selectors/profile';
2720
import { getThreadSelectors } from 'firefox-profiler/selectors/per-thread';
28-
import { Tooltip } from 'firefox-profiler/components/tooltip/Tooltip';
29-
import { TooltipTrackPower } from 'firefox-profiler/components/tooltip/TrackPower';
30-
import {
31-
TooltipDetails,
32-
TooltipDetail,
33-
TooltipDetailSeparator,
34-
} from 'firefox-profiler/components/tooltip/TooltipDetails';
21+
import { TrackCounterTooltip } from './TrackCounterTooltip';
3522
import { EmptyThreadIndicator } from './EmptyThreadIndicator';
36-
import { getSampleIndexRangeForSelection } from 'firefox-profiler/profile-logic/profile-data';
3723
import { assertExhaustiveCheck } from 'firefox-profiler/utils/types';
38-
import { co2 } from '@tgwf/co2';
3924

4025
import type {
4126
CounterIndex,
@@ -44,7 +29,6 @@ import type {
4429
ThreadIndex,
4530
AccumulatedCounterSamples,
4631
Milliseconds,
47-
PreviewSelection,
4832
CssPixels,
4933
StartEndRange,
5034
IndexIntoSamplesTable,
@@ -379,7 +363,6 @@ type StateProps = {
379363
readonly interval: Milliseconds;
380364
readonly filteredThread: Thread;
381365
readonly unfilteredSamplesRange: StartEndRange | null;
382-
readonly previewSelection: PreviewSelection | null;
383366
};
384367

385368
type DispatchProps = {};
@@ -393,10 +376,8 @@ type State = {
393376
};
394377

395378
/**
396-
* The generic counter track graph component. It renders information from any counters
397-
* (eg, Memory, Power, etc.) as a graph in the timeline. It branches on
398-
* `display.graphType` for drawing, and on `counter.category`/`counter.name`
399-
* for tooltip rendering of known counter types.
379+
* The generic counter track graph component. It renders any counter
380+
* (Memory, Power, etc.) as a graph in the timeline.
400381
*/
401382
class TrackCounterGraphImpl extends React.PureComponent<Props, State> {
402383
override state = {
@@ -405,8 +386,6 @@ class TrackCounterGraphImpl extends React.PureComponent<Props, State> {
405386
mouseY: 0,
406387
};
407388

408-
_co2: InstanceType<typeof co2> | null = null;
409-
410389
_onMouseLeave = () => {
411390
// This persistTooltips property is part of the web console API. It helps
412391
// in being able to inspect and debug tooltips.
@@ -509,44 +488,14 @@ class TrackCounterGraphImpl extends React.PureComponent<Props, State> {
509488
}
510489
};
511490

512-
_formatDataTransferValue(bytes: number, l10nId: string) {
513-
if (!this._co2) {
514-
this._co2 = new co2({ model: 'swd' });
515-
}
516-
// By default, when estimating emissions per byte, co2.js takes into account
517-
// emissions for the user device, the data center and the network.
518-
// Because we already have power tracks showing the power use and estimated
519-
// emissions of the device, set the 'device' grid intensity to 0 to avoid
520-
// double counting.
521-
const co2eq = this._co2.perByteTrace(bytes, false, {
522-
gridIntensity: { device: 0 },
523-
});
524-
const carbonValue = formatNumber(
525-
typeof co2eq.co2 === 'number' ? co2eq.co2 : co2eq.co2.total
526-
);
527-
const value = formatBytes(bytes);
528-
return (
529-
<Localized
530-
id={l10nId}
531-
vars={{ value, carbonValue }}
532-
attrs={{ label: true }}
533-
>
534-
<TooltipDetail label="">{value}</TooltipDetail>
535-
</Localized>
536-
);
537-
}
538-
539491
_renderTooltip(counterIndex: number): React.ReactNode {
540492
const {
541-
accumulatedSamples,
542493
counter,
543494
rangeStart,
544495
rangeEnd,
545-
interval,
496+
accumulatedSamples,
546497
maxCounterSampleCountPerMs,
547-
previewSelection,
548498
} = this.props;
549-
const { display } = counter;
550499
const { mouseX, mouseY } = this.state;
551500
const { samples } = counter;
552501

@@ -563,182 +512,15 @@ class TrackCounterGraphImpl extends React.PureComponent<Props, State> {
563512
return null;
564513
}
565514

566-
const { category, name } = counter;
567-
568-
// Power tooltip — delegate to the dedicated component.
569-
if (category === 'power') {
570-
return (
571-
<Tooltip mouseX={mouseX} mouseY={mouseY}>
572-
<TooltipTrackPower
573-
counter={counter}
574-
counterSampleIndex={counterIndex}
575-
/>
576-
</Tooltip>
577-
);
578-
}
579-
580-
// Process CPU tooltip.
581-
if (category === 'CPU' && name === 'processCPU') {
582-
const cpuUsage = samples.count[counterIndex];
583-
const sampleTimeDeltaInMs =
584-
counterIndex === 0
585-
? interval
586-
: samples.time[counterIndex] - samples.time[counterIndex - 1];
587-
const cpuRatio =
588-
cpuUsage / sampleTimeDeltaInMs / maxCounterSampleCountPerMs;
589-
return (
590-
<Tooltip mouseX={mouseX} mouseY={mouseY}>
591-
<div className="timelineTrackCounterTooltip">
592-
<div className="timelineTrackCounterTooltipLine">
593-
CPU:{' '}
594-
<span className="timelineTrackCounterTooltipNumber">
595-
{formatPercent(cpuRatio)}
596-
</span>
597-
</div>
598-
</div>
599-
</Tooltip>
600-
);
601-
}
602-
603-
// Bandwidth tooltip — bytes with rate, CO2, and accumulated total.
604-
if (category === 'Bandwidth') {
605-
const { minCount, countRange, accumulatedCounts } = accumulatedSamples;
606-
const bytes = accumulatedCounts[counterIndex] - minCount;
607-
const operations =
608-
samples.number !== undefined ? samples.number[counterIndex] : null;
609-
610-
const sampleTimeDeltaInMs =
611-
counterIndex === 0
612-
? interval
613-
: samples.time[counterIndex] - samples.time[counterIndex - 1];
614-
const unitGraphCount = samples.count[counterIndex] / sampleTimeDeltaInMs;
615-
616-
let rangeTotal = 0;
617-
if (previewSelection) {
618-
const [beginIndex, endIndex] = getSampleIndexRangeForSelection(
619-
samples,
620-
previewSelection.selectionStart,
621-
previewSelection.selectionEnd
622-
);
623-
624-
for (
625-
let counterSampleIndex = beginIndex;
626-
counterSampleIndex < endIndex;
627-
counterSampleIndex++
628-
) {
629-
rangeTotal += samples.count[counterSampleIndex];
630-
}
631-
}
632-
633-
let ops;
634-
if (operations !== null) {
635-
ops = formatNumber(operations, 2, 0);
636-
}
637-
638-
return (
639-
<Tooltip mouseX={mouseX} mouseY={mouseY}>
640-
<div className="timelineTrackCounterTooltip">
641-
<TooltipDetails>
642-
{this._formatDataTransferValue(
643-
unitGraphCount * 1000 /* ms -> s */,
644-
'TrackBandwidthGraph--speed'
645-
)}
646-
{operations !== null ? (
647-
<Localized
648-
id="TrackBandwidthGraph--read-write-operations-since-the-previous-sample"
649-
vars={{ value: ops || '' }}
650-
attrs={{ label: true }}
651-
>
652-
<TooltipDetail label="">{ops}</TooltipDetail>
653-
</Localized>
654-
) : null}
655-
<TooltipDetailSeparator />
656-
{this._formatDataTransferValue(
657-
bytes,
658-
'TrackBandwidthGraph--cumulative-bandwidth-at-this-time'
659-
)}
660-
{this._formatDataTransferValue(
661-
countRange,
662-
'TrackBandwidthGraph--total-bandwidth-in-graph'
663-
)}
664-
{previewSelection
665-
? this._formatDataTransferValue(
666-
rangeTotal,
667-
'TrackBandwidthGraph--total-bandwidth-in-range'
668-
)
669-
: null}
670-
</TooltipDetails>
671-
</div>
672-
</Tooltip>
673-
);
674-
}
675-
676-
// Memory tooltip — accumulated bytes with operations count.
677-
if (category === 'Memory') {
678-
const { minCount, countRange, accumulatedCounts } = accumulatedSamples;
679-
const bytes = accumulatedCounts[counterIndex] - minCount;
680-
const operations =
681-
samples.number !== undefined ? samples.number[counterIndex] : null;
682-
return (
683-
<Tooltip mouseX={mouseX} mouseY={mouseY}>
684-
<div className="timelineTrackCounterTooltip">
685-
<div className="timelineTrackCounterTooltipLine">
686-
<span className="timelineTrackCounterTooltipNumber">
687-
{formatBytes(bytes)}
688-
</span>
689-
<Localized id="TrackMemoryGraph--relative-memory-at-this-time">
690-
relative memory at this time
691-
</Localized>
692-
</div>
693-
694-
<div className="timelineTrackCounterTooltipLine">
695-
<span className="timelineTrackCounterTooltipNumber">
696-
{formatBytes(countRange)}
697-
</span>
698-
<Localized id="TrackMemoryGraph--memory-range-in-graph">
699-
memory range in graph
700-
</Localized>
701-
</div>
702-
{operations !== null ? (
703-
<div className="timelineTrackCounterTooltipLine">
704-
<span className="timelineTrackCounterTooltipNumber">
705-
{formatNumber(operations, 2, 0)}
706-
</span>
707-
<Localized id="TrackMemoryGraph--allocations-and-deallocations-since-the-previous-sample">
708-
allocations and deallocations since the previous sample
709-
</Localized>
710-
</div>
711-
) : null}
712-
</div>
713-
</Tooltip>
714-
);
715-
}
716-
717-
// Generic tooltip for unknown counter types - format the value based on
718-
// the counter's unit.
719-
const value = samples.count[counterIndex];
720-
let formattedValue;
721-
if (display.unit === 'bytes') {
722-
formattedValue = formatBytes(value);
723-
} else if (display.unit === 'percent') {
724-
formattedValue = formatPercent(value);
725-
} else if (display.unit) {
726-
// Bypasses i18n but this is hit only for unknown counters.
727-
formattedValue = `${formatNumber(value)} ${display.unit}`;
728-
} else {
729-
formattedValue = formatNumber(value);
730-
}
731515
return (
732-
<Tooltip mouseX={mouseX} mouseY={mouseY}>
733-
<div className="timelineTrackCounterTooltip">
734-
<div className="timelineTrackCounterTooltipLine">
735-
<span className="timelineTrackCounterTooltipNumber">
736-
{formattedValue}
737-
</span>
738-
{display.label || name}
739-
</div>
740-
</div>
741-
</Tooltip>
516+
<TrackCounterTooltip
517+
counter={counter}
518+
counterIndex={counterIndex}
519+
accumulatedSamples={accumulatedSamples}
520+
maxCounterSampleCountPerMs={maxCounterSampleCountPerMs}
521+
mouseX={mouseX}
522+
mouseY={mouseY}
523+
/>
742524
);
743525
}
744526

@@ -894,7 +676,6 @@ export const TrackCounterGraph = explicitConnect<
894676
interval: getProfileInterval(state),
895677
filteredThread: selectors.getFilteredThread(state),
896678
unfilteredSamplesRange: selectors.unfilteredSamplesRange(state),
897-
previewSelection: getPreviewSelection(state),
898679
};
899680
},
900681
component: withSize(TrackCounterGraphImpl),

0 commit comments

Comments
 (0)