Skip to content

Commit d3dfa05

Browse files
authored
Scale custom marker graphs on values within the committed range. (#5587)
1 parent 2166b4a commit d3dfa05

File tree

4 files changed

+159
-20
lines changed

4 files changed

+159
-20
lines changed

src/components/timeline/TrackCustomMarkerGraph.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type {
2929
IndexIntoStringTable,
3030
MarkerSchema,
3131
CollectedCustomMarkerSamples,
32+
ValueBounds,
3233
MarkerGraphType,
3334
MarkerIndex,
3435
Marker,
@@ -51,6 +52,7 @@ type CanvasProps = {
5152
readonly markerSchema: MarkerSchema;
5253
readonly markerSampleRanges: [IndexIntoSamplesTable, IndexIntoSamplesTable];
5354
readonly collectedSamples: CollectedCustomMarkerSamples;
55+
readonly valueBounds: ValueBounds;
5456
readonly width: CssPixels;
5557
readonly height: CssPixels;
5658
readonly getMarker: (param: MarkerIndex) => Marker;
@@ -101,6 +103,7 @@ class TrackCustomMarkerCanvas extends React.PureComponent<CanvasProps> {
101103
markerSchema,
102104
markerSampleRanges,
103105
collectedSamples,
106+
valueBounds,
104107
height,
105108
width,
106109
getMarker,
@@ -136,7 +139,7 @@ class TrackCustomMarkerCanvas extends React.PureComponent<CanvasProps> {
136139
throw new Error('No lines for marker ' + name);
137140
}
138141

139-
const { minNumber, maxNumber } = collectedSamples;
142+
const { minNumber, maxNumber } = valueBounds;
140143
const [sampleStart, sampleEnd] = markerSampleRanges;
141144

142145
{
@@ -336,6 +339,7 @@ type StateProps = {
336339
readonly rangeEnd: Milliseconds;
337340
readonly markerSampleRanges: [IndexIntoSamplesTable, IndexIntoSamplesTable];
338341
readonly collectedSamples: CollectedCustomMarkerSamples;
342+
readonly valueBounds: ValueBounds;
339343
readonly getMarker: (param: MarkerIndex) => Marker;
340344
};
341345

@@ -484,6 +488,7 @@ class TrackCustomMarkerGraphImpl extends React.PureComponent<Props, State> {
484488
graphHeight,
485489
width,
486490
collectedSamples,
491+
valueBounds,
487492
getMarker,
488493
} = this.props;
489494

@@ -506,7 +511,8 @@ class TrackCustomMarkerGraphImpl extends React.PureComponent<Props, State> {
506511

507512
const left = (width * (sampleTime - rangeStart)) / rangeLength;
508513

509-
const { minNumber, maxNumber, numbersPerLine } = collectedSamples;
514+
const { minNumber, maxNumber } = valueBounds;
515+
const { numbersPerLine } = collectedSamples;
510516

511517
const dots = [];
512518

@@ -572,6 +578,7 @@ class TrackCustomMarkerGraphImpl extends React.PureComponent<Props, State> {
572578
graphHeight,
573579
width,
574580
collectedSamples,
581+
valueBounds,
575582
getMarker,
576583
} = this.props;
577584

@@ -589,6 +596,7 @@ class TrackCustomMarkerGraphImpl extends React.PureComponent<Props, State> {
589596
height={graphHeight}
590597
width={width}
591598
collectedSamples={collectedSamples}
599+
valueBounds={valueBounds}
592600
getMarker={getMarker}
593601
/>
594602
{hoveredCounter === null ? null : (
@@ -620,6 +628,8 @@ export const TrackCustomMarkerGraph = explicitConnect<
620628
markerTrackSelectors.getCommittedRangeMarkerSampleRange(state),
621629
collectedSamples:
622630
markerTrackSelectors.getCollectedCustomMarkerSamples(state),
631+
valueBounds:
632+
markerTrackSelectors.getCommittedRangeMarkerSampleValueBounds(state),
623633
rangeStart: start,
624634
rangeEnd: end,
625635
getMarker: selectors.getMarkerGetter(state),

src/selectors/per-thread/markers.ts

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import type {
2727
ThreadsKey,
2828
Tid,
2929
CollectedCustomMarkerSamples,
30+
ValueBounds,
3031
IndexIntoSamplesTable,
3132
IndexIntoStringTable,
3233
State,
@@ -638,8 +639,6 @@ export function getMarkerSelectorsPerThread(
638639
);
639640
}
640641
const markerIndexes: MarkerIndex[] = [];
641-
let minNumber = Infinity;
642-
let maxNumber = -Infinity;
643642
const numbersPerLine: number[][] = [];
644643
const { graphs, name: schemaName } = markerSchema;
645644
const keys = graphs.map((graph) => {
@@ -659,19 +658,11 @@ export function getMarkerSelectorsPerThread(
659658
for (let i = 0; i < keys.length; ++i) {
660659
const val = (data as any)[keys[i]];
661660
numbersPerLine[i].push(val);
662-
if (val < minNumber) {
663-
minNumber = val;
664-
}
665-
if (val > maxNumber) {
666-
maxNumber = val;
667-
}
668661
}
669662
}
670663
});
671664

672665
return {
673-
minNumber,
674-
maxNumber,
675666
numbersPerLine,
676667
markerIndexes,
677668
};
@@ -695,9 +686,50 @@ export function getMarkerSelectorsPerThread(
695686
)
696687
);
697688

689+
const getCommittedRangeMarkerSampleValueBounds: Selector<ValueBounds> =
690+
createSelector(
691+
getCollectedCustomMarkerSamples,
692+
getCommittedRangeMarkerSampleRange,
693+
(collectedSamples, sampleRange) => {
694+
const [sampleStart, sampleEnd] = sampleRange;
695+
const { numbersPerLine } = collectedSamples;
696+
697+
// Handle edge case where there are no samples in range
698+
if (sampleStart >= sampleEnd) {
699+
return { minNumber: 0, maxNumber: 0 };
700+
}
701+
702+
let minNumber = Infinity;
703+
let maxNumber = -Infinity;
704+
705+
for (
706+
let sampleIndex = sampleStart;
707+
sampleIndex < sampleEnd;
708+
sampleIndex++
709+
) {
710+
for (
711+
let graphIndex = 0;
712+
graphIndex < numbersPerLine.length;
713+
graphIndex++
714+
) {
715+
const val = numbersPerLine[graphIndex][sampleIndex];
716+
if (val < minNumber) {
717+
minNumber = val;
718+
}
719+
if (val > maxNumber) {
720+
maxNumber = val;
721+
}
722+
}
723+
}
724+
725+
return { minNumber, maxNumber };
726+
}
727+
);
728+
698729
return {
699730
getCollectedCustomMarkerSamples,
700731
getCommittedRangeMarkerSampleRange,
732+
getCommittedRangeMarkerSampleValueBounds,
701733
};
702734
}
703735

src/test/components/TrackCustomMarker.test.tsx

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66

77
import type { CssPixels } from 'firefox-profiler/types';
88
import { Provider } from 'react-redux';
9-
import { fireEvent } from '@testing-library/react';
9+
import { fireEvent, act } from '@testing-library/react';
1010

1111
import { render } from 'firefox-profiler/test/fixtures/testing-library';
12+
import { commitRange } from 'firefox-profiler/actions/profile-view';
13+
import { getThreadSelectors } from 'firefox-profiler/selectors/per-thread';
1214
import { TrackCustomMarker } from '../../components/timeline/TrackCustomMarker';
1315
import { ensureExists } from '../../utils/types';
1416

@@ -46,9 +48,13 @@ function getMarkerPixelPosition(time: number): CssPixels {
4648
return (time * GRAPH_WIDTH) / SAMPLE_COUNT;
4749
}
4850

49-
function setup() {
51+
function setup(
52+
values: number[] = Array(SAMPLE_COUNT)
53+
.fill(0)
54+
.map((_, i) => i)
55+
) {
5056
const { profile, stringTable } = getProfileFromTextSamples(
51-
Array(SAMPLE_COUNT).fill('A').join(' ')
57+
Array(values.length).fill('A').join(' ')
5258
);
5359
const markerStringIndex = stringTable.indexForString('Marker');
5460
const threadIndex = 0;
@@ -83,9 +89,10 @@ function setup() {
8389
thread.markers.category.push(4);
8490
thread.markers.length++;
8591
};
86-
for (let i = 0; i < SAMPLE_COUNT; i++) {
87-
addMarker(i, i, i * 2);
88-
}
92+
93+
values.forEach((value, index) => {
94+
addMarker(index, value, value * 2);
95+
});
8996
const store = storeWithProfile(profile);
9097
const { getState, dispatch } = store;
9198
const flushRafCalls = mockRaf();
@@ -142,6 +149,7 @@ function setup() {
142149
flushRafCalls,
143150
getMarkerDot,
144151
getContextDrawCalls,
152+
markerStringIndex,
145153
};
146154
}
147155

@@ -300,3 +308,89 @@ describe('TrackCustomMarker with intersection observer', function () {
300308
);
301309
});
302310
});
311+
312+
describe('TrackCustomMarker with committed range scaling', function () {
313+
autoMockCanvasContext();
314+
autoMockElementSize({ width: GRAPH_WIDTH, height: GRAPH_HEIGHT });
315+
autoMockIntersectionObserver();
316+
beforeEach(addRootOverlayElement);
317+
afterEach(removeRootOverlayElement);
318+
319+
it('uses the committed range scaling selector correctly', function () {
320+
// Create markers with values: [1, 100, 2, 3] at times 0, 1, 2, 3
321+
// Full range min/max would be 1-100, but committed range 2-3 should be 2-3
322+
const { profile, dispatch, getState, markerStringIndex } = setup([
323+
1, 100, 2, 3,
324+
]);
325+
326+
// Get the selectors to test them directly
327+
const state = getState();
328+
const threadSelectors = getThreadSelectors(0);
329+
const markerSchema = ensureExists(
330+
profile.meta.markerSchema.find((schema) => schema.name === 'Marker')
331+
);
332+
const markerTrackSelectors = threadSelectors.getMarkerTrackSelectors(
333+
markerSchema,
334+
markerStringIndex
335+
);
336+
337+
// Get initial samples (full range) - should have min=1, max=200 (because second line = first * 2)
338+
const fullRangeSamples =
339+
markerTrackSelectors.getCollectedCustomMarkerSamples(state);
340+
const fullRangeValueBounds =
341+
markerTrackSelectors.getCommittedRangeMarkerSampleValueBounds(state);
342+
expect(fullRangeValueBounds.minNumber).toBe(1);
343+
expect(fullRangeValueBounds.maxNumber).toBe(200); // 100 * 2 from second line
344+
345+
// Commit range from 2ms to 3.5ms to include only the markers at times 2ms and 3ms.
346+
// The 2.1 value is to exclude from the range the end of the marker at 1ms.
347+
act(() => {
348+
dispatch(commitRange(2.1, 3.5));
349+
});
350+
351+
// Get committed range samples - should include markers at 2ms[2,4] and 3ms[3,6]
352+
const newState = getState();
353+
const committedRangeSamples =
354+
markerTrackSelectors.getCollectedCustomMarkerSamples(newState);
355+
const committedRangeValueBounds =
356+
markerTrackSelectors.getCommittedRangeMarkerSampleValueBounds(newState);
357+
expect(committedRangeValueBounds.minNumber).toBe(2);
358+
expect(committedRangeValueBounds.maxNumber).toBe(6);
359+
360+
// Verify the arrays are the same length (same structure)
361+
expect(committedRangeSamples.numbersPerLine).toHaveLength(
362+
fullRangeSamples.numbersPerLine.length
363+
);
364+
expect(committedRangeSamples.markerIndexes).toHaveLength(
365+
fullRangeSamples.markerIndexes.length
366+
);
367+
});
368+
369+
it('handles edge case where committed range has identical values', function () {
370+
// Create markers with identical values in the committed range
371+
const { profile, dispatch, getState, markerStringIndex } = setup([
372+
1, 100, 5, 5,
373+
]);
374+
375+
const threadSelectors = getThreadSelectors(0);
376+
const markerSchema = ensureExists(
377+
profile.meta.markerSchema.find((schema) => schema.name === 'Marker')
378+
);
379+
const markerTrackSelectors = threadSelectors.getMarkerTrackSelectors(
380+
markerSchema,
381+
markerStringIndex
382+
);
383+
384+
// Focus on the range with identical values at times 2ms and 3ms (values [5,10] and [5,10])
385+
act(() => {
386+
dispatch(commitRange(2.1, 3.5));
387+
});
388+
389+
// Should not crash when min === max - both markers have first=5, second=10
390+
const state = getState();
391+
const committedRangeValueBounds =
392+
markerTrackSelectors.getCommittedRangeMarkerSampleValueBounds(state);
393+
expect(committedRangeValueBounds.minNumber).toBe(5);
394+
expect(committedRangeValueBounds.maxNumber).toBe(10); // 5 * 2 from second line
395+
});
396+
});

src/types/profile-derived.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,14 +584,17 @@ export type AccumulatedCounterSamples = {
584584
* A collection of the data for all configured lines for a given marker
585585
*/
586586
export type CollectedCustomMarkerSamples = {
587-
readonly minNumber: number;
588-
readonly maxNumber: number;
589587
// This value holds the number per configured line
590588
// selection. The array will share the indexes of the range filtered marker samples.
591589
readonly numbersPerLine: number[][];
592590
readonly markerIndexes: MarkerIndex[];
593591
};
594592

593+
export type ValueBounds = {
594+
readonly minNumber: number;
595+
readonly maxNumber: number;
596+
};
597+
595598
export type StackType = 'js' | 'native' | 'unsymbolicated';
596599

597600
export type GlobalTrack =

0 commit comments

Comments
 (0)