Visualize CPU time versus wall time for thread slices.
Thread slices now have a highlighted section to show how much of the
duration represents CPU time (darker) and how much represents wall
time (dark + lighter).
Bug: 141530588
Change-Id: I1c61cdb62e599b5782c2a91ca2da869f787e7a72
diff --git a/CHANGELOG b/CHANGELOG
index ff0a59d..a44b463 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,7 +4,8 @@
Trace Processor:
*
UI:
- *
+ * Added a highlighted section to thread slices to visualize CPU time
+ (darker) verses wall time (lighter).
SDK:
*
diff --git a/docs/contributing/testing.md b/docs/contributing/testing.md
index 98f0812..6eabc54 100644
--- a/docs/contributing/testing.md
+++ b/docs/contributing/testing.md
@@ -124,6 +124,35 @@
be ignored (for example,
`SELECT RUN_METRIC('metric file') as surpress_query_output`)
+UI pixel diff tests
+-----------------
+The pixel tests are used to ensure core user journeys work by verifying they
+are the same pixel to pixel against a golden screenshot. They use a headless
+chrome to load the webpage and take a screenshot and compare pixel by pixel a
+golden screenshot. You can run these tests by using `ui/run-integrationtests`.
+
+
+These test fail when a certain number of pixels are different. If these tests
+fail, you'll need to investigate the diff and determine if its intentional. If
+its a desired change you will need to update the screenshots on a linux machine
+to get the CI to pass. You can update them by generating and uploading a new
+baseline (this requires access to a google bucket through gcloud which only
+googlers have access to, googlers can install gcloud
+[here](https://g3doc.corp.google.com/cloud/sdk/g3doc/index.md#installing-and-using-the-cloud-sdk)).
+
+```
+ui/run-integrationtests --rebaseline
+tools/add_test_data test/data/ui-screenshots
+```
+
+Once finished you can commit and upload as part of your CL to cause the CI to
+use your new screenshots.
+
+NOTE: If you see a failing diff test you can see the pixel differences on the CI
+by using the link to the UI and replace `/ui/index.html` with
+`/ui-test-artifacts/<name_of_failing_png_test_from_logs>.png`. This allows you
+to tell where in the picture the change was introduced.
+
Android CTS tests
-----------------
CTS tests ensure that any vendors who modify Android remain compliant with the
diff --git a/tools/install-build-deps b/tools/install-build-deps
index 8399fce..d7c0797 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -234,8 +234,8 @@
# Example traces for regression tests.
Dependency(
'test/data.zip',
- 'https://storage.googleapis.com/perfetto/test-data-20210715-134242.zip',
- '4d79b592f66ee7c57811407a0e91f34491121aaab989804e137859d03b54e934',
+ 'https://storage.googleapis.com/perfetto/test-data-20210715-171257.zip',
+ '7389d64a15b2ec2598340beec4db87f96da6457d39651f70eb9a759c5537b594',
'all', 'all',
),
diff --git a/ui/src/common/colorizer.ts b/ui/src/common/colorizer.ts
index 9adde38..5ff5e78 100644
--- a/ui/src/common/colorizer.ts
+++ b/ui/src/common/colorizer.ts
@@ -159,3 +159,16 @@
const lightness = isSelected ? 30 : hash(sliceName + 'x', 40) + 40;
return [hue, saturation, lightness];
}
+
+// Lightens the color for thread slices to represent wall time.
+export function hslForThreadIdleSlice(
+ hue: number,
+ saturation: number,
+ lightness: number,
+ isSelected: boolean|null): [number, number, number] {
+ // Increase lightness by 80% when selected and 40% otherwise,
+ // without exceeding 88.
+ let newLightness = isSelected ? lightness * 1.8 : lightness * 1.4;
+ newLightness = Math.min(newLightness, 88);
+ return [hue, saturation, newLightness];
+}
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index dd69835..f62b45f 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -816,13 +816,15 @@
thread_track.name as trackName,
tid,
thread.name as threadName,
- max(depth) as maxDepth,
+ max(slice.depth) as maxDepth,
+ count(thread_slice.id) > 0 as hasThreadSlice,
process.upid as upid,
process.pid as pid
from slice
join thread_track on slice.track_id = thread_track.id
join thread using(utid)
left join process using(upid)
+ left join thread_slice on slice.id = thread_slice.id
group by thread_track.id
`);
@@ -835,6 +837,7 @@
maxDepth: NUM,
upid: NUM_NULL,
pid: NUM_NULL,
+ hasThreadSlice: NUM,
});
for (; it.valid(); it.next()) {
const utid = it.utid;
@@ -845,6 +848,7 @@
const upid = it.upid;
const pid = it.pid;
const maxDepth = it.maxDepth;
+ const hasThreadSlice = it.hasThreadSlice;
const trackKindPriority =
TrackDecider.inferTrackKindPriority(threadName, tid, pid);
@@ -859,7 +863,7 @@
name,
trackGroup: uuid,
trackKindPriority,
- config: {trackId, maxDepth, tid}
+ config: {trackId, maxDepth, tid, isThreadSlice: hasThreadSlice === 1}
});
}
}
diff --git a/ui/src/tracks/chrome_slices/common.ts b/ui/src/tracks/chrome_slices/common.ts
index 41fc2f8..7b80743 100644
--- a/ui/src/tracks/chrome_slices/common.ts
+++ b/ui/src/tracks/chrome_slices/common.ts
@@ -20,6 +20,7 @@
maxDepth: number;
namespace: string;
trackId: number;
+ isThreadSlice?: boolean;
}
export interface Data extends TrackData {
@@ -33,4 +34,5 @@
colors?: Uint16Array; // Index into strings.
isInstant: Uint16Array;
isIncomplete: Uint16Array;
+ cpuTimeRatio?: Float64Array;
}
diff --git a/ui/src/tracks/chrome_slices/controller.ts b/ui/src/tracks/chrome_slices/controller.ts
index 3c6822a..1a41def 100644
--- a/ui/src/tracks/chrome_slices/controller.ts
+++ b/ui/src/tracks/chrome_slices/controller.ts
@@ -36,7 +36,14 @@
// ns per quantization bucket (i.e. ns per pixel). /2 * 2 is to force it to
// be an even number, so we can snap in the middle.
const bucketNs = Math.max(Math.round(resolution * 1e9 * pxSize / 2) * 2, 1);
- const tableName = this.namespaceTable('slice');
+
+ const isThreadSlice = this.config.isThreadSlice;
+ let tableName = this.namespaceTable('slice');
+ let threadDurQuery = ', dur';
+ if (isThreadSlice) {
+ tableName = this.namespaceTable('thread_slice');
+ threadDurQuery = ', iif(thread_dur IS NULL, dur, thread_dur)';
+ }
if (this.maxDurNs === 0) {
const query = `
@@ -56,6 +63,7 @@
name,
dur = 0 as isInstant,
dur = -1 as isIncomplete
+ ${threadDurQuery} as threadDur
FROM ${tableName}
WHERE track_id = ${this.config.trackId} AND
ts >= (${startNs - this.maxDurNs}) AND
@@ -77,6 +85,7 @@
titles: new Uint16Array(numRows),
isInstant: new Uint16Array(numRows),
isIncomplete: new Uint16Array(numRows),
+ cpuTimeRatio: new Float64Array(numRows)
};
const stringIndexes = new Map<string, number>();
@@ -97,7 +106,8 @@
sliceId: NUM,
name: STR,
isInstant: NUM,
- isIncomplete: NUM
+ isIncomplete: NUM,
+ threadDur: NUM
});
for (let row = 0; it.valid(); it.next(), row++) {
const startNsQ = it.tsq;
@@ -123,6 +133,16 @@
slices.titles[row] = internString(it.name);
slices.isInstant[row] = it.isInstant;
slices.isIncomplete[row] = it.isIncomplete;
+
+ let cpuTimeRatio = 1;
+ if (!it.isInstant && !it.isIncomplete) {
+ // Rounding the CPU time ratio to two decimal places and ensuring
+ // it is less than or equal to one, incase the thread duration exceeds
+ // the total duration.
+ cpuTimeRatio =
+ Math.min(Math.round((it.threadDur / it.dur) * 100) / 100, 1);
+ }
+ slices.cpuTimeRatio![row] = cpuTimeRatio;
}
return slices;
}
diff --git a/ui/src/tracks/chrome_slices/frontend.ts b/ui/src/tracks/chrome_slices/frontend.ts
index 403f01b..0bc9ab4 100644
--- a/ui/src/tracks/chrome_slices/frontend.ts
+++ b/ui/src/tracks/chrome_slices/frontend.ts
@@ -16,7 +16,7 @@
import {Actions} from '../../common/actions';
import {cropText, drawIncompleteSlice} from '../../common/canvas_utils';
-import {hslForSlice} from '../../common/colorizer';
+import {hslForSlice, hslForThreadIdleSlice} from '../../common/colorizer';
import {TRACE_MARGIN_TIME_S} from '../../common/constants';
import {TrackState} from '../../common/state';
import {checkerboardExcept} from '../../frontend/checkerboard';
@@ -85,6 +85,7 @@
const isIncomplete = data.isIncomplete[i];
const title = data.strings[titleId];
const colorOverride = data.colors && data.strings[data.colors[i]];
+ const isThreadSlice = this.config.isThreadSlice;
if (isIncomplete) { // incomplete slice
tEnd = visibleWindowTime.end;
}
@@ -103,8 +104,9 @@
const highlighted = titleId === this.hoveredTitleId ||
globals.frontendLocalState.highlightedSliceId === sliceId;
- const [hue, saturation, lightness] =
- hslForSlice(name, highlighted || isSelected);
+ const hasFocus = highlighted || isSelected;
+
+ const [hue, saturation, lightness] = hslForSlice(name, hasFocus);
let color: string;
if (colorOverride === undefined) {
@@ -114,6 +116,16 @@
}
ctx.fillStyle = color;
+ if (isThreadSlice) {
+ const cpuTimeRatio = data.cpuTimeRatio![i];
+ const [GradientHue, GradientSaturation, GradientLightness] =
+ hslForThreadIdleSlice(hue, saturation, lightness, hasFocus);
+ const gradientColor =
+ hsluvToHex([GradientHue, GradientSaturation, GradientLightness]);
+ ctx.fillStyle = this.createGradientForThreadSlice(
+ tStart, tEnd, cpuTimeRatio, color, gradientColor, ctx);
+ }
+
// We draw instant events as upward facing chevrons starting at A:
// A
// ###
@@ -270,6 +282,23 @@
!(tEnd <= visibleWindowTime.start || tStart >= visibleWindowTime.end)
};
}
+
+ createGradientForThreadSlice(
+ tStart: number, tEnd: number, cpuTimeRatio: number, color: string,
+ gradientColor: string, ctx: CanvasRenderingContext2D): CanvasGradient {
+ const timeScale = globals.frontendLocalState.timeScale;
+ const {start: windowStart, end: windowEnd} =
+ globals.frontendLocalState.visibleWindowTime;
+ const start = isFinite(timeScale.timeToPx(tStart)) ?
+ timeScale.timeToPx(tStart) :
+ windowStart;
+ const end = isFinite(timeScale.timeToPx(tEnd)) ? timeScale.timeToPx(tEnd) :
+ windowEnd;
+ const gradient = ctx.createLinearGradient(start, 0, end, 0);
+ gradient.addColorStop(cpuTimeRatio, color);
+ gradient.addColorStop(cpuTimeRatio, gradientColor);
+ return gradient;
+ }
}
trackRegistry.register(ChromeSliceTrack);