Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 47 additions & 64 deletions src/components/shared/thread/ActivityGraphFills.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { bisectionRight } from 'firefox-profiler/utils/bisect';
import { ensureExists } from 'firefox-profiler/utils/types';

import './ActivityGraph.css';

Expand Down Expand Up @@ -58,8 +57,11 @@ type SampleContributionToPixel = {
type CategoryFill = {
readonly category: IndexIntoCategoryList;
readonly fillStyle: string | CanvasPattern;
// The Float32Arrays are mutated in place during the computation step.
// Mutated in place during the computation step.
// Contains values between 0 and 100.
readonly perPixelContribution: Float32Array<ArrayBuffer>;
// Mutated in place during the computation step.
// Contains values between 0 and 1.
readonly accumulatedUpperEdge: Float32Array<ArrayBuffer>;
};

Expand Down Expand Up @@ -182,7 +184,7 @@ export class ActivityGraphFillComputer {
// Only copy the first array, as there is no accumulation.
const { accumulatedUpperEdge, perPixelContribution } = mutableFills[0];
for (let i = 0; i < perPixelContribution.length; i++) {
accumulatedUpperEdge[i] = perPixelContribution[i];
accumulatedUpperEdge[i] = perPixelContribution[i] / 100;
}
}

Expand All @@ -194,7 +196,7 @@ export class ActivityGraphFillComputer {
} of mutableFills.slice(1)) {
for (let i = 0; i < perPixelContribution.length; i++) {
accumulatedUpperEdge[i] =
previousUpperEdge[i] + perPixelContribution[i];
previousUpperEdge[i] + perPixelContribution[i] / 100;
}
previousUpperEdge = accumulatedUpperEdge;
}
Expand Down Expand Up @@ -236,16 +238,16 @@ export class ActivityGraphFillComputer {
}

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

let beforeSampleCpuRatio = 1;
let afterSampleCpuRatio = 1;
if (enableCPUUsage && threadCPURatio) {
beforeSampleCpuRatio = threadCPURatio[i];
afterSampleCpuRatio = threadCPURatio[i + 1];
let beforeSampleCpuPercent = 100;
let afterSampleCpuPercent = 100;
if (enableCPUUsage) {
beforeSampleCpuPercent = threadCPUPercent[i];
afterSampleCpuPercent = threadCPUPercent[i + 1];
}

const percentageBuffers = this.mutablePercentageBuffers[category];
Expand All @@ -258,8 +260,8 @@ export class ActivityGraphFillComputer {
prevSampleTime,
sampleTime,
nextSampleTime,
beforeSampleCpuRatio,
afterSampleCpuRatio,
beforeSampleCpuPercent,
afterSampleCpuPercent,
rangeStart
);

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

let beforeSampleCpuRatio = 1;
let afterSampleCpuRatio = 1;
if (enableCPUUsage && threadCPURatio) {
beforeSampleCpuRatio = threadCPURatio[lastIdx];

const nextIdxInFullThread = sampleIndexOffset + lastIdx + 1;
if (nextIdxInFullThread < fullThread.samples.length) {
// Since we are zoomed in the timeline, rangeFilteredThread will not
// have the information of the next sample. So we need to get that
// information from the full thread.
afterSampleCpuRatio = ensureExists(fullThread.samples.threadCPURatio)[
nextIdxInFullThread
];
} else {
// If we don't have this information in the full thread, simply use the
// previous CPU ratio.
afterSampleCpuRatio = beforeSampleCpuRatio;
}
let beforeSampleCpuPercent = 100;
let afterSampleCpuPercent = 100;
if (enableCPUUsage) {
beforeSampleCpuPercent = threadCPUPercent[lastIdx];
afterSampleCpuPercent = threadCPUPercent[lastIdx + 1]; // guaranteed to exist
}

const nextSampleTime = sampleTime + interval;
Expand All @@ -303,8 +292,8 @@ export class ActivityGraphFillComputer {
prevSampleTime,
sampleTime,
nextSampleTime,
beforeSampleCpuRatio,
afterSampleCpuRatio,
beforeSampleCpuPercent,
afterSampleCpuPercent,
rangeStart
);
}
Expand Down Expand Up @@ -439,9 +428,8 @@ export class ActivityFillGraphQuerier {
return null;
}

const threadCPURatio = samples.threadCPURatio;
if (!threadCPURatio) {
// There is no threadCPURatio information in the array. Return null.
if (!samples.hasCPUDeltas) {
// There is no real CPU usage information. Return null.
return null;
}

Expand Down Expand Up @@ -599,6 +587,8 @@ export class ActivityFillGraphQuerier {
/**
* Compute how much a sample contributes to a given pixel after smoothing has
* been applied.
*
* Returns a value between 0 and 1.
*/
_getSmoothedContributionFromSampleToPixel(
xPixel: number,
Expand Down Expand Up @@ -627,20 +617,12 @@ export class ActivityFillGraphQuerier {
? fullThread.samples.time[fullThreadSample + 1]
: sampleTime + interval;

let beforeSampleCpuRatio = 1;
let afterSampleCpuRatio = 1;
const { threadCPURatio } = samples;
if (enableCPUUsage && threadCPURatio) {
beforeSampleCpuRatio = threadCPURatio[sample];
// Use the fullThread here to properly get the next in case zoomed in.
const fullThreadSamplesCPURatio = ensureExists(
fullThread.samples.threadCPURatio
);
if (fullThreadSample + 1 < fullThreadSamplesCPURatio.length) {
afterSampleCpuRatio = fullThreadSamplesCPURatio[fullThreadSample + 1];
} else {
afterSampleCpuRatio = beforeSampleCpuRatio;
}
let beforeSampleCpuPercent = 100;
let afterSampleCpuPercent = 100;
const { threadCPUPercent } = samples;
if (enableCPUUsage) {
beforeSampleCpuPercent = threadCPUPercent[sample];
afterSampleCpuPercent = threadCPUPercent[sample + 1]; // guaranteed to exist
}

const kernelRangeStartTime = rangeStart + kernelPos / xPixelsPerMs;
Expand All @@ -651,8 +633,8 @@ export class ActivityFillGraphQuerier {
prevSampleTime,
sampleTime,
nextSampleTime,
beforeSampleCpuRatio,
afterSampleCpuRatio,
beforeSampleCpuPercent,
afterSampleCpuPercent,
kernelRangeStartTime
);

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

return sum;
return sum / 100;
}
}

Expand Down Expand Up @@ -775,8 +757,8 @@ function _accumulateInBuffer(
prevSampleTime: Milliseconds,
sampleTime: Milliseconds,
nextSampleTime: Milliseconds,
beforeSampleCpuRatio: number,
afterSampleCpuRatio: number,
beforeSampleCpuPercent: number,
afterSampleCpuPercent: number,
bufferTimeRangeStart: Milliseconds
) {
const { xPixelsPerMs } = renderedComponentSettings;
Expand Down Expand Up @@ -825,36 +807,37 @@ function _accumulateInBuffer(
// This is because CPU usage number of a sample represents the CPU usage
// starting starting from the previous sample time to this sample time.
// These parts will be:
// - Between `sampleCategoryStartPixel` and `samplePixel` with beforeSampleCpuRatio.
// - Between `samplePixel` and `sampleCategoryEndPixel` with afterSampleCpuRatio.
// - Between `sampleCategoryStartPixel` and `samplePixel` with beforeSampleCpuPercent.
// - Between `samplePixel` and `sampleCategoryEndPixel` with afterSampleCpuPercent.

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

// Subtract the partial pixels from start and end of the first part.
percentageBuffer[intCategoryStartPixel] -=
beforeSampleCpuRatio * (sampleCategoryStartPixel - intCategoryStartPixel);
beforeSampleCpuPercent * (sampleCategoryStartPixel - intCategoryStartPixel);
percentageBuffer[intSamplePixel] -=
beforeSampleCpuRatio * (1 - (samplePixel - intSamplePixel));
beforeSampleCpuPercent * (1 - (samplePixel - intSamplePixel));

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

// Subtract the partial pixels from start and end of the second part.
percentageBuffer[intSamplePixel] -=
afterSampleCpuRatio * (samplePixel - intSamplePixel);
afterSampleCpuPercent * (samplePixel - intSamplePixel);
percentageBuffer[intCategoryEndPixel] -=
afterSampleCpuRatio * (1 - (sampleCategoryEndPixel - intCategoryEndPixel));
afterSampleCpuPercent *
(1 - (sampleCategoryEndPixel - intCategoryEndPixel));
}
/**
* Apply a 1d box blur to a destination array.
Expand Down
5 changes: 2 additions & 3 deletions src/components/shared/thread/CPUGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import { PureComponent } from 'react';

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

import type {
Thread,
Expand Down Expand Up @@ -43,7 +42,7 @@ export class ThreadCPUGraph extends PureComponent<Props> {
if (sampleIndex >= samples.length - 1) {
return 0;
}
return ensureExists(samples.threadCPURatio)[sampleIndex + 1] || 0;
return samples.threadCPUPercent[sampleIndex + 1] || 0;
};

override render() {
Expand All @@ -65,7 +64,7 @@ export class ThreadCPUGraph extends PureComponent<Props> {
return (
<ThreadHeightGraph
heightFunc={this._heightFunction}
maxValue={1}
maxValue={100}
className={className}
trackName={trackName}
interval={interval}
Expand Down
5 changes: 2 additions & 3 deletions src/components/timeline/TrackThread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ class TimelineTrackThreadImpl extends PureComponent<Props> {
/>
) : null}
{isExperimentalCPUGraphsEnabled &&
rangeFilteredThread.samples.threadCPURatio !== undefined ? (
rangeFilteredThread.samples.hasCPUDeltas ? (
<ThreadCPUGraph
className="threadCPUGraph"
trackName={trackName}
Expand Down Expand Up @@ -336,8 +336,7 @@ export const TimelineTrackThread = explicitConnect<
const fullThread = selectors.getThread(state);
const timelineType = getTimelineType(state);
const enableCPUUsage =
timelineType === 'cpu-category' &&
fullThread.samples.threadCPURatio !== undefined;
timelineType === 'cpu-category' && fullThread.samples.hasCPUDeltas;

return {
fullThread,
Expand Down
47 changes: 22 additions & 25 deletions src/profile-logic/cpu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ import {
} from 'firefox-profiler/utils/types';
import { numberSeriesToDeltas } from 'firefox-profiler/utils/number-series';

import type {
RawThread,
Profile,
RawSamplesTable,
} from 'firefox-profiler/types';
import type { RawThread, Profile } from 'firefox-profiler/types';

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

/**
* Computes the threadCPURatio column for the SamplesTable.
* Computes the threadCPUPercent column for the SamplesTable.
*
* The CPU ratio is a number between 0 and 1, and describes the CPU use between
* The CPU percentage is a number between 0 and 100, and describes the CPU use between
* the previous sample time and the current sample time. It is the ratio of cpu
* time to elapsed wall clock time.
* time to elapsed wall clock time, times 100.
*
* This function returns undefined if `samples` does not have a `threadCPUDelta`
* This function synthesizes 100% values if `samples` does not have a `threadCPUDelta`
* column.
*
* The returned array has length samples.length + 1, and the first and last elements are
* always zero.
*/
export function computeThreadCPURatio(
samples: RawSamplesTable,
export function computeThreadCPUPercent(
threadCPUDelta: Array<number | null>,
timeDeltas: number[],
referenceCPUDeltaPerMs: number
): Float64Array | undefined {
const { threadCPUDelta } = samples;

if (!threadCPUDelta) {
return undefined;
}

const threadCPURatio: Float64Array = new Float64Array(threadCPUDelta.length);
): Uint8Array {
const threadCPUPercent: Uint8Array = new Uint8Array(
threadCPUDelta.length + 1
);

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

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

// Limit values to 1.0.
threadCPURatio[i] =
cpuDelta <= referenceCpuDelta ? cpuDelta / referenceCpuDelta : 1;
threadCPUPercent[i] =
cpuDelta <= referenceCpuDelta
? Math.round((100 * cpuDelta) / referenceCpuDelta)
: 100;
}

return threadCPURatio;
return threadCPUPercent;
}
Loading