Skip to content

Commit f3f07c7

Browse files
committed
Rename threadCPURatio to threadCPUPercent.
This turns it into a Uint8Array of integer percentages, which requires less memory bandwidth when rendering the activity graph. We now always have a fallback threadCPUPercent array, even if the original profile doesn't contain any threadCPUDelta information. The synthesized values are set to 100%. We also have a trailing extra value in order to simplify the rendering code for the last sample.
1 parent c62a2eb commit f3f07c7

File tree

9 files changed

+394
-806
lines changed

9 files changed

+394
-806
lines changed

src/components/shared/thread/ActivityGraphFills.tsx

Lines changed: 47 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44
import { bisectionRight } from 'firefox-profiler/utils/bisect';
5-
import { ensureExists } from 'firefox-profiler/utils/types';
65

76
import './ActivityGraph.css';
87

@@ -58,8 +57,11 @@ type SampleContributionToPixel = {
5857
type CategoryFill = {
5958
readonly category: IndexIntoCategoryList;
6059
readonly fillStyle: string | CanvasPattern;
61-
// The Float32Arrays are mutated in place during the computation step.
60+
// Mutated in place during the computation step.
61+
// Contains values between 0 and 100.
6262
readonly perPixelContribution: Float32Array<ArrayBuffer>;
63+
// Mutated in place during the computation step.
64+
// Contains values between 0 and 1.
6365
readonly accumulatedUpperEdge: Float32Array<ArrayBuffer>;
6466
};
6567

@@ -182,7 +184,7 @@ export class ActivityGraphFillComputer {
182184
// Only copy the first array, as there is no accumulation.
183185
const { accumulatedUpperEdge, perPixelContribution } = mutableFills[0];
184186
for (let i = 0; i < perPixelContribution.length; i++) {
185-
accumulatedUpperEdge[i] = perPixelContribution[i];
187+
accumulatedUpperEdge[i] = perPixelContribution[i] / 100;
186188
}
187189
}
188190

@@ -194,7 +196,7 @@ export class ActivityGraphFillComputer {
194196
} of mutableFills.slice(1)) {
195197
for (let i = 0; i < perPixelContribution.length; i++) {
196198
accumulatedUpperEdge[i] =
197-
previousUpperEdge[i] + perPixelContribution[i];
199+
previousUpperEdge[i] + perPixelContribution[i] / 100;
198200
}
199201
previousUpperEdge = accumulatedUpperEdge;
200202
}
@@ -236,16 +238,16 @@ export class ActivityGraphFillComputer {
236238
}
237239

238240
// Go through the samples and accumulate the category into the percentageBuffers.
239-
const { threadCPURatio } = samples;
241+
const { threadCPUPercent } = samples;
240242
for (let i = 0; i < samples.length - 1; i++) {
241243
const nextSampleTime = samples.time[i + 1];
242244
const category = samples.category[i];
243245

244-
let beforeSampleCpuRatio = 1;
245-
let afterSampleCpuRatio = 1;
246-
if (enableCPUUsage && threadCPURatio) {
247-
beforeSampleCpuRatio = threadCPURatio[i];
248-
afterSampleCpuRatio = threadCPURatio[i + 1];
246+
let beforeSampleCpuPercent = 100;
247+
let afterSampleCpuPercent = 100;
248+
if (enableCPUUsage) {
249+
beforeSampleCpuPercent = threadCPUPercent[i];
250+
afterSampleCpuPercent = threadCPUPercent[i + 1];
249251
}
250252

251253
const percentageBuffers = this.mutablePercentageBuffers[category];
@@ -258,8 +260,8 @@ export class ActivityGraphFillComputer {
258260
prevSampleTime,
259261
sampleTime,
260262
nextSampleTime,
261-
beforeSampleCpuRatio,
262-
afterSampleCpuRatio,
263+
beforeSampleCpuPercent,
264+
afterSampleCpuPercent,
263265
rangeStart
264266
);
265267

@@ -271,24 +273,11 @@ export class ActivityGraphFillComputer {
271273
const lastIdx = samples.length - 1;
272274
const lastSampleCategory = samples.category[lastIdx];
273275

274-
let beforeSampleCpuRatio = 1;
275-
let afterSampleCpuRatio = 1;
276-
if (enableCPUUsage && threadCPURatio) {
277-
beforeSampleCpuRatio = threadCPURatio[lastIdx];
278-
279-
const nextIdxInFullThread = sampleIndexOffset + lastIdx + 1;
280-
if (nextIdxInFullThread < fullThread.samples.length) {
281-
// Since we are zoomed in the timeline, rangeFilteredThread will not
282-
// have the information of the next sample. So we need to get that
283-
// information from the full thread.
284-
afterSampleCpuRatio = ensureExists(fullThread.samples.threadCPURatio)[
285-
nextIdxInFullThread
286-
];
287-
} else {
288-
// If we don't have this information in the full thread, simply use the
289-
// previous CPU ratio.
290-
afterSampleCpuRatio = beforeSampleCpuRatio;
291-
}
276+
let beforeSampleCpuPercent = 100;
277+
let afterSampleCpuPercent = 100;
278+
if (enableCPUUsage) {
279+
beforeSampleCpuPercent = threadCPUPercent[lastIdx];
280+
afterSampleCpuPercent = threadCPUPercent[lastIdx + 1]; // guaranteed to exist
292281
}
293282

294283
const nextSampleTime = sampleTime + interval;
@@ -303,8 +292,8 @@ export class ActivityGraphFillComputer {
303292
prevSampleTime,
304293
sampleTime,
305294
nextSampleTime,
306-
beforeSampleCpuRatio,
307-
afterSampleCpuRatio,
295+
beforeSampleCpuPercent,
296+
afterSampleCpuPercent,
308297
rangeStart
309298
);
310299
}
@@ -439,9 +428,8 @@ export class ActivityFillGraphQuerier {
439428
return null;
440429
}
441430

442-
const threadCPURatio = samples.threadCPURatio;
443-
if (!threadCPURatio) {
444-
// There is no threadCPURatio information in the array. Return null.
431+
if (!samples.hasCPUDeltas) {
432+
// There is no real CPU usage information. Return null.
445433
return null;
446434
}
447435

@@ -599,6 +587,8 @@ export class ActivityFillGraphQuerier {
599587
/**
600588
* Compute how much a sample contributes to a given pixel after smoothing has
601589
* been applied.
590+
*
591+
* Returns a value between 0 and 1.
602592
*/
603593
_getSmoothedContributionFromSampleToPixel(
604594
xPixel: number,
@@ -627,20 +617,12 @@ export class ActivityFillGraphQuerier {
627617
? fullThread.samples.time[fullThreadSample + 1]
628618
: sampleTime + interval;
629619

630-
let beforeSampleCpuRatio = 1;
631-
let afterSampleCpuRatio = 1;
632-
const { threadCPURatio } = samples;
633-
if (enableCPUUsage && threadCPURatio) {
634-
beforeSampleCpuRatio = threadCPURatio[sample];
635-
// Use the fullThread here to properly get the next in case zoomed in.
636-
const fullThreadSamplesCPURatio = ensureExists(
637-
fullThread.samples.threadCPURatio
638-
);
639-
if (fullThreadSample + 1 < fullThreadSamplesCPURatio.length) {
640-
afterSampleCpuRatio = fullThreadSamplesCPURatio[fullThreadSample + 1];
641-
} else {
642-
afterSampleCpuRatio = beforeSampleCpuRatio;
643-
}
620+
let beforeSampleCpuPercent = 100;
621+
let afterSampleCpuPercent = 100;
622+
const { threadCPUPercent } = samples;
623+
if (enableCPUUsage) {
624+
beforeSampleCpuPercent = threadCPUPercent[sample];
625+
afterSampleCpuPercent = threadCPUPercent[sample + 1]; // guaranteed to exist
644626
}
645627

646628
const kernelRangeStartTime = rangeStart + kernelPos / xPixelsPerMs;
@@ -651,8 +633,8 @@ export class ActivityFillGraphQuerier {
651633
prevSampleTime,
652634
sampleTime,
653635
nextSampleTime,
654-
beforeSampleCpuRatio,
655-
afterSampleCpuRatio,
636+
beforeSampleCpuPercent,
637+
afterSampleCpuPercent,
656638
kernelRangeStartTime
657639
);
658640

@@ -661,7 +643,7 @@ export class ActivityFillGraphQuerier {
661643
sum += SMOOTHING_KERNEL[i] * pixelsAroundX[i];
662644
}
663645

664-
return sum;
646+
return sum / 100;
665647
}
666648
}
667649

@@ -775,8 +757,8 @@ function _accumulateInBuffer(
775757
prevSampleTime: Milliseconds,
776758
sampleTime: Milliseconds,
777759
nextSampleTime: Milliseconds,
778-
beforeSampleCpuRatio: number,
779-
afterSampleCpuRatio: number,
760+
beforeSampleCpuPercent: number,
761+
afterSampleCpuPercent: number,
780762
bufferTimeRangeStart: Milliseconds
781763
) {
782764
const { xPixelsPerMs } = renderedComponentSettings;
@@ -825,36 +807,37 @@ function _accumulateInBuffer(
825807
// This is because CPU usage number of a sample represents the CPU usage
826808
// starting starting from the previous sample time to this sample time.
827809
// These parts will be:
828-
// - Between `sampleCategoryStartPixel` and `samplePixel` with beforeSampleCpuRatio.
829-
// - Between `samplePixel` and `sampleCategoryEndPixel` with afterSampleCpuRatio.
810+
// - Between `sampleCategoryStartPixel` and `samplePixel` with beforeSampleCpuPercent.
811+
// - Between `samplePixel` and `sampleCategoryEndPixel` with afterSampleCpuPercent.
830812

831813
// Here we are accumulating the first part of the sample. It will use the
832814
// CPU delta number that belongs to this sample.
833815
// This part starts from the "sample start time" to "sample time" and uses
834-
// beforeSampleCpuRatio.
816+
// beforeSampleCpuPercent.
835817
for (let i = intCategoryStartPixel; i <= intSamplePixel; i++) {
836-
percentageBuffer[i] += beforeSampleCpuRatio;
818+
percentageBuffer[i] += beforeSampleCpuPercent;
837819
}
838820

839821
// Subtract the partial pixels from start and end of the first part.
840822
percentageBuffer[intCategoryStartPixel] -=
841-
beforeSampleCpuRatio * (sampleCategoryStartPixel - intCategoryStartPixel);
823+
beforeSampleCpuPercent * (sampleCategoryStartPixel - intCategoryStartPixel);
842824
percentageBuffer[intSamplePixel] -=
843-
beforeSampleCpuRatio * (1 - (samplePixel - intSamplePixel));
825+
beforeSampleCpuPercent * (1 - (samplePixel - intSamplePixel));
844826

845827
// Here we are accumulating the second part of the sample. It will use the
846828
// CPU delta number that belongs to the next sample.
847829
// This part starts from "sample time" to "sample end time" and uses
848-
// afterSampleCpuRatio.
830+
// afterSampleCpuPercent.
849831
for (let i = intSamplePixel; i <= intCategoryEndPixel; i++) {
850-
percentageBuffer[i] += afterSampleCpuRatio;
832+
percentageBuffer[i] += afterSampleCpuPercent;
851833
}
852834

853835
// Subtract the partial pixels from start and end of the second part.
854836
percentageBuffer[intSamplePixel] -=
855-
afterSampleCpuRatio * (samplePixel - intSamplePixel);
837+
afterSampleCpuPercent * (samplePixel - intSamplePixel);
856838
percentageBuffer[intCategoryEndPixel] -=
857-
afterSampleCpuRatio * (1 - (sampleCategoryEndPixel - intCategoryEndPixel));
839+
afterSampleCpuPercent *
840+
(1 - (sampleCategoryEndPixel - intCategoryEndPixel));
858841
}
859842
/**
860843
* Apply a 1d box blur to a destination array.

src/components/shared/thread/CPUGraph.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import { PureComponent } from 'react';
55

66
import { ThreadHeightGraph } from './HeightGraph';
7-
import { ensureExists } from 'firefox-profiler/utils/types';
87

98
import type {
109
Thread,
@@ -43,7 +42,7 @@ export class ThreadCPUGraph extends PureComponent<Props> {
4342
if (sampleIndex >= samples.length - 1) {
4443
return 0;
4544
}
46-
return ensureExists(samples.threadCPURatio)[sampleIndex + 1] || 0;
45+
return samples.threadCPUPercent[sampleIndex + 1] || 0;
4746
};
4847

4948
override render() {
@@ -65,7 +64,7 @@ export class ThreadCPUGraph extends PureComponent<Props> {
6564
return (
6665
<ThreadHeightGraph
6766
heightFunc={this._heightFunction}
68-
maxValue={1}
67+
maxValue={100}
6968
className={className}
7069
trackName={trackName}
7170
interval={interval}

src/components/timeline/TrackThread.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ class TimelineTrackThreadImpl extends PureComponent<Props> {
281281
/>
282282
) : null}
283283
{isExperimentalCPUGraphsEnabled &&
284-
rangeFilteredThread.samples.threadCPURatio !== undefined ? (
284+
rangeFilteredThread.samples.hasCPUDeltas ? (
285285
<ThreadCPUGraph
286286
className="threadCPUGraph"
287287
trackName={trackName}
@@ -336,8 +336,7 @@ export const TimelineTrackThread = explicitConnect<
336336
const fullThread = selectors.getThread(state);
337337
const timelineType = getTimelineType(state);
338338
const enableCPUUsage =
339-
timelineType === 'cpu-category' &&
340-
fullThread.samples.threadCPURatio !== undefined;
339+
timelineType === 'cpu-category' && fullThread.samples.hasCPUDeltas;
341340

342341
return {
343342
fullThread,

src/profile-logic/cpu.ts

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@ import {
88
} from 'firefox-profiler/utils/types';
99
import { numberSeriesToDeltas } from 'firefox-profiler/utils/number-series';
1010

11-
import type {
12-
RawThread,
13-
Profile,
14-
RawSamplesTable,
15-
} from 'firefox-profiler/types';
11+
import type { RawThread, Profile } from 'firefox-profiler/types';
1612

1713
/**
1814
* Compute the max CPU cycles per ms for the thread. Should only be called when
@@ -93,31 +89,30 @@ export function computeReferenceCPUDeltaPerMs(profile: Profile): number {
9389
}
9490

9591
/**
96-
* Computes the threadCPURatio column for the SamplesTable.
92+
* Computes the threadCPUPercent column for the SamplesTable.
9793
*
98-
* The CPU ratio is a number between 0 and 1, and describes the CPU use between
94+
* The CPU percentage is a number between 0 and 100, and describes the CPU use between
9995
* the previous sample time and the current sample time. It is the ratio of cpu
100-
* time to elapsed wall clock time.
96+
* time to elapsed wall clock time, times 100.
10197
*
102-
* This function returns undefined if `samples` does not have a `threadCPUDelta`
98+
* This function synthesizes 100% values if `samples` does not have a `threadCPUDelta`
10399
* column.
100+
*
101+
* The returned array has length samples.length + 1, and the first and last elements are
102+
* always zero.
104103
*/
105-
export function computeThreadCPURatio(
106-
samples: RawSamplesTable,
104+
export function computeThreadCPUPercent(
105+
threadCPUDelta: Array<number | null>,
107106
timeDeltas: number[],
108107
referenceCPUDeltaPerMs: number
109-
): Float64Array | undefined {
110-
const { threadCPUDelta } = samples;
111-
112-
if (!threadCPUDelta) {
113-
return undefined;
114-
}
115-
116-
const threadCPURatio: Float64Array = new Float64Array(threadCPUDelta.length);
108+
): Uint8Array {
109+
const threadCPUPercent: Uint8Array = new Uint8Array(
110+
threadCPUDelta.length + 1
111+
);
117112

118-
// Ignore threadCPUDelta[0] and set threadCPURatio[0] to zero - there is no
113+
// Ignore threadCPUDelta[0] and set threadCPUPercent[0] to zero - there is no
119114
// previous sample so there is no meaningful value we could compute here.
120-
threadCPURatio[0] = 0;
115+
threadCPUPercent[0] = 0;
121116

122117
// For the rest of the samples, compute the ratio based on the CPU delta and
123118
// on the elapsed time between samples (timeDeltas[i]).
@@ -131,14 +126,16 @@ export function computeThreadCPURatio(
131126
// be null if the samples at the beginning were collected by the base
132127
// profiler, which doesn't support collecting CPU delta information yet,
133128
// see bug 1756519.
134-
threadCPURatio[i] = 1;
129+
threadCPUPercent[i] = 100;
135130
continue;
136131
}
137132

138133
// Limit values to 1.0.
139-
threadCPURatio[i] =
140-
cpuDelta <= referenceCpuDelta ? cpuDelta / referenceCpuDelta : 1;
134+
threadCPUPercent[i] =
135+
cpuDelta <= referenceCpuDelta
136+
? Math.round((100 * cpuDelta) / referenceCpuDelta)
137+
: 100;
141138
}
142139

143-
return threadCPURatio;
140+
return threadCPUPercent;
144141
}

0 commit comments

Comments
 (0)