perfetto-ui: Show slice wakeup info
Click on a slice and see the wakeup information, on the main UI
and in the details panel.
See here: https://taylori-dot-perfetto-ui.appspot.com/#!/?s=b1830d45dc57166937cb2f12d6cde9114265363ef7312eb50c6616f77bd39
Also fixed a small bug in the drawing of vertical lines.
Bug:118895197
Change-Id: Ic7d6df601281102402a2cf6e48d8628bd9ab4679
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index 3c2abfd..a1abd66 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -399,13 +399,19 @@
table {
font-size: 14px;
- width: 100%;
- td:first-child {
- width: 20%;
- }
+ width: 50%;
+ min-width: 200px;
+ max-width: 50%;
+ table-layout: fixed;
+ word-wrap: break-word;
tr:hover {
background-color: hsl(214, 22%, 90%);
}
+ th {
+ text-align: left;
+ width: 30%;
+ font-weight: normal;
+ }
}
}
diff --git a/ui/src/common/canvas_utils.ts b/ui/src/common/canvas_utils.ts
new file mode 100644
index 0000000..198375f
--- /dev/null
+++ b/ui/src/common/canvas_utils.ts
@@ -0,0 +1,62 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+export function cropText(str: string, charWidth: number, rectWidth: number) {
+ const maxTextWidth = rectWidth - 4;
+ let displayText = '';
+ const nameLength = str.length * charWidth;
+ if (nameLength < maxTextWidth) {
+ displayText = str;
+ } else {
+ // -3 for the 3 ellipsis.
+ const displayedChars = Math.floor(maxTextWidth / charWidth) - 3;
+ if (displayedChars > 3) {
+ displayText = str.substring(0, displayedChars) + '...';
+ }
+ }
+ return displayText;
+}
+
+export function drawDoubleHeadedArrow(
+ ctx: CanvasRenderingContext2D,
+ x: number,
+ y: number,
+ length: number,
+ showArrowHeads: boolean,
+ width = 2,
+ color = 'black') {
+ ctx.beginPath();
+ ctx.lineWidth = width;
+ ctx.lineCap = 'round';
+ ctx.strokeStyle = color;
+ ctx.moveTo(x, y);
+ ctx.lineTo(x + length, y);
+ ctx.stroke();
+ ctx.closePath();
+ // Arrowheads on the each end of the line.
+ if (showArrowHeads) {
+ ctx.beginPath();
+ ctx.moveTo(x + length - 8, y - 4);
+ ctx.lineTo(x + length, y);
+ ctx.lineTo(x + length - 8, y + 4);
+ ctx.stroke();
+ ctx.closePath();
+ ctx.beginPath();
+ ctx.moveTo(x + 8, y - 4);
+ ctx.lineTo(x, y);
+ ctx.lineTo(x + 8, y + 4);
+ ctx.stroke();
+ ctx.closePath();
+ }
+}
\ No newline at end of file
diff --git a/ui/src/common/time.ts b/ui/src/common/time.ts
index 4b2b32f..33e13b8 100644
--- a/ui/src/common/time.ts
+++ b/ui/src/common/time.ts
@@ -44,7 +44,7 @@
export function timeToCode(sec: number) {
let result = '';
let ns = Math.round(sec * 1e9);
- if (ns < 1) return '0s ';
+ if (ns < 1) return '0s';
const unitAndValue = [
['m', 60000000000],
['s', 1000000000],
@@ -61,7 +61,7 @@
result += i.toLocaleString() + unit + ' ';
}
});
- return result;
+ return result.slice(0, -1);
}
export class TimeSpan {
diff --git a/ui/src/common/time_unittest.ts b/ui/src/common/time_unittest.ts
index 479e77c..bbaacd9 100644
--- a/ui/src/common/time_unittest.ts
+++ b/ui/src/common/time_unittest.ts
@@ -15,16 +15,16 @@
import {timeToCode} from './time';
test('seconds to code', () => {
- expect(timeToCode(3)).toEqual('3s ');
- expect(timeToCode(60)).toEqual('1m ');
- expect(timeToCode(63)).toEqual('1m 3s ');
- expect(timeToCode(63.2)).toEqual('1m 3s 200ms ');
- expect(timeToCode(63.2221)).toEqual('1m 3s 222ms 100us ');
- expect(timeToCode(63.2221111)).toEqual('1m 3s 222ms 111us 100ns ');
- expect(timeToCode(0.2221111)).toEqual('222ms 111us 100ns ');
- expect(timeToCode(0.000001)).toEqual('1us ');
- expect(timeToCode(0.000003)).toEqual('3us ');
- expect(timeToCode(1.000001)).toEqual('1s 1us ');
- expect(timeToCode(200.00000003)).toEqual('3m 20s 30ns ');
- expect(timeToCode(0)).toEqual('0s ');
+ expect(timeToCode(3)).toEqual('3s');
+ expect(timeToCode(60)).toEqual('1m');
+ expect(timeToCode(63)).toEqual('1m 3s');
+ expect(timeToCode(63.2)).toEqual('1m 3s 200ms');
+ expect(timeToCode(63.2221)).toEqual('1m 3s 222ms 100us');
+ expect(timeToCode(63.2221111)).toEqual('1m 3s 222ms 111us 100ns');
+ expect(timeToCode(0.2221111)).toEqual('222ms 111us 100ns');
+ expect(timeToCode(0.000001)).toEqual('1us');
+ expect(timeToCode(0.000003)).toEqual('3us');
+ expect(timeToCode(1.000001)).toEqual('1s 1us');
+ expect(timeToCode(200.00000003)).toEqual('3m 20s 30ns');
+ expect(timeToCode(0)).toEqual('0s');
});
diff --git a/ui/src/common/track_utils.ts b/ui/src/common/track_utils.ts
deleted file mode 100644
index 0d406d5..0000000
--- a/ui/src/common/track_utils.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-export function cropText(str: string, charWidth: number, rectWidth: number) {
- const maxTextWidth = rectWidth - 4;
- let displayText = '';
- const nameLength = str.length * charWidth;
- if (nameLength < maxTextWidth) {
- displayText = str;
- } else {
- // -3 for the 3 ellipsis.
- const displayedChars = Math.floor(maxTextWidth / charWidth) - 3;
- if (displayedChars > 3) {
- displayText = str.substring(0, displayedChars) + '...';
- }
- }
- return displayText;
-}
\ No newline at end of file
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index acdea6c..308b09e 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -12,10 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Controller} from './controller';
-import {globals} from './globals';
import {Engine} from '../common/engine';
import {fromNs} from '../common/time';
+import {SliceDetails} from '../frontend/globals';
+
+import {Controller} from './controller';
+import {globals} from './globals';
export interface SelectionControllerArgs {
engine: Engine;
@@ -40,7 +42,7 @@
this.lastSelectedSlice = selectedSlice;
if (selectedSlice !== undefined) {
- const sqlQuery = `SELECT ts, dur, priority, end_state FROM sched
+ const sqlQuery = `SELECT ts, dur, priority, end_state, utid FROM sched
WHERE row_id = ${selectedSlice}`;
this.args.engine.query(sqlQuery).then(result => {
// Check selection is still the same on completion of query.
@@ -49,15 +51,52 @@
selection &&
selection.kind === 'SLICE' &&
selection.id === selectedSlice) {
- const ts = fromNs(result.columns[0].longValues![0] as number);
- const timeFromStart = ts - globals.state.traceTime.startSec;
+ const ts = result.columns[0].longValues![0] as number;
+ const timeFromStart = fromNs(ts) - globals.state.traceTime.startSec;
const dur = fromNs(result.columns[1].longValues![0] as number);
const priority = result.columns[2].longValues![0] as number;
const endState = result.columns[3].stringValues![0];
- const selected = {ts: timeFromStart, dur, priority, endState};
- globals.publish('SliceDetails', selected);
+ const selected:
+ SliceDetails = {ts: timeFromStart, dur, priority, endState};
+ const utid = result.columns[4].longValues![0];
+ this.schedulingDetails(ts, utid).then(wakeResult => {
+ Object.assign(selected, wakeResult);
+ globals.publish('SliceDetails', selected);
+ });
}
});
}
}
+
+ async schedulingDetails(ts: number, utid: number|Long) {
+ // Find the ts of the first sched_wakeup before the current slice.
+ const queryWakeupTs = `select ts from instants where name = 'sched_wakeup'
+ and ref = ${utid} and ts < ${ts} order by ts desc limit 1`;
+ const wakeupRow = await this.args.engine.queryOneRow(queryWakeupTs);
+ // Find the previous sched slice for the current utid.
+ const queryPrevSched = `select ts from sched where utid = ${utid}
+ and ts < ${ts} order by ts desc limit 1`;
+ const prevSchedRow = await this.args.engine.queryOneRow(queryPrevSched);
+ // If this is the first sched slice for this utid or if the wakeup found
+ // was after the previous slice then we know the wakeup was for this slice.
+ if (prevSchedRow[0] && wakeupRow[0] < prevSchedRow[0]) {
+ return undefined;
+ }
+ const wakeupTs = wakeupRow[0];
+ // Find the sched slice with the utid of the waker running when the
+ // sched wakeup occurred. This is the waker.
+ const queryWaker = `select utid, cpu from sched where utid =
+ (select utid from raw where name = 'sched_wakeup' and ts = ${wakeupTs})
+ and ts < ${wakeupTs} and ts + dur >= ${wakeupTs};`;
+ const wakerRow = await this.args.engine.queryOneRow(queryWaker);
+ if (wakerRow) {
+ return {
+ wakeupTs: fromNs(wakeupTs),
+ wakerUtid: wakerRow[0],
+ wakerCpu: wakerRow[1]
+ };
+ } else {
+ return undefined;
+ }
+ }
}
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 905110d..747c8db 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -27,6 +27,9 @@
dur?: number;
priority?: number;
endState?: string;
+ wakeupTs?: number;
+ wakerUtid?: number;
+ wakerCpu?: number;
}
export interface QuantizedLoad {
diff --git a/ui/src/frontend/notes_panel.ts b/ui/src/frontend/notes_panel.ts
index e765c66..5a9605c 100644
--- a/ui/src/frontend/notes_panel.ts
+++ b/ui/src/frontend/notes_panel.ts
@@ -24,7 +24,7 @@
import {hsl} from 'color-convert';
const FLAG_WIDTH = 16;
-const MOUSE_OFFSET = 4;
+const MOUSE_OFFSET = 6;
const FLAG = `\uE153`;
function toSummary(s: string) {
diff --git a/ui/src/frontend/slice_panel.ts b/ui/src/frontend/slice_panel.ts
index 73b24cd..ca0cc19 100644
--- a/ui/src/frontend/slice_panel.ts
+++ b/ui/src/frontend/slice_panel.ts
@@ -14,8 +14,10 @@
import * as m from 'mithril';
+import {drawDoubleHeadedArrow} from '../common/canvas_utils';
import {translateState} from '../common/thread_state';
import {timeToCode} from '../common/time';
+
import {globals} from './globals';
import {Panel, PanelSize} from './panel';
@@ -32,23 +34,23 @@
'.details-panel',
m('.details-panel-heading', `Slice Details:`),
m('.details-table', [m('table', [
- m('tr', m('td', `PID`), m('td', `${threadInfo.pid}`)),
+ m('tr', m('th', `PID`), m('td', `${threadInfo.pid}`)),
m('tr',
- m('td', `Process name`),
+ m('th', `Process name`),
m('td', `${threadInfo.procName}`)),
- m('tr', m('td', `TID`), m('td', `${threadInfo.tid}`)),
+ m('tr', m('th', `TID`), m('td', `${threadInfo.tid}`)),
m('tr',
- m('td', `Thread name`),
+ m('th', `Thread name`),
m('td', `${threadInfo.threadName}`)),
m('tr',
- m('td', `Start time`),
+ m('th', `Start time`),
m('td', `${timeToCode(sliceInfo.ts)}`)),
m('tr',
- m('td', `Duration`),
+ m('th', `Duration`),
m('td', `${timeToCode(sliceInfo.dur)}`)),
- m('tr', m('td', `Prio`), m('td', `${sliceInfo.priority}`)),
+ m('tr', m('th', `Prio`), m('td', `${sliceInfo.priority}`)),
m('tr',
- m('td', `End State`),
+ m('th', `End State`),
m('td', `${translateState(sliceInfo.endState)}`))
])], ));
}
@@ -57,5 +59,65 @@
'.details-panel', m('.details-panel-heading', `Slice Details:`, ));
}
}
- renderCanvas(_ctx: CanvasRenderingContext2D, _size: PanelSize) {}
+renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
+ const details = globals.sliceDetails;
+ // Show expanded details on the scheduling of the currently selected slice.
+ if (details.wakeupTs && details.wakerUtid !== undefined) {
+ const threadInfo = globals.threads.get(details.wakerUtid);
+ // Draw separation line.
+ ctx.fillStyle = '#3c4b5d';
+ ctx.fillRect(size.width / 2, 10, 1, size.height - 10);
+ ctx.font = '16px Google Sans';
+ ctx.fillText('Scheduling Latency:', size.width / 2 + 30, 30);
+ // Draw diamond and vertical line.
+ const startDraw = {x: size.width / 2 + 30, y: 52};
+ ctx.beginPath();
+ ctx.moveTo(startDraw.x, startDraw.y + 28);
+ ctx.fillStyle = 'black';
+ ctx.lineTo(startDraw.x + 6, startDraw.y + 20);
+ ctx.lineTo(startDraw.x, startDraw.y + 12);
+ ctx.lineTo(startDraw.x - 6, startDraw.y + 20);
+ ctx.fill();
+ ctx.closePath();
+ ctx.fillRect(startDraw.x - 1, startDraw.y, 2, 100);
+
+ // Wakeup explanation text.
+ ctx.font = '13px Google Sans';
+ ctx.fillStyle = '#3c4b5d';
+ if (threadInfo) {
+ const displayText =
+ `Wakeup @ ${
+ timeToCode(
+ details.wakeupTs - globals.state.traceTime.startSec)
+ } on CPU ${details.wakerCpu} by`;
+ const processText = `P: ${threadInfo.procName} [${threadInfo.pid}]`;
+ const threadText = `T: ${threadInfo.threadName} [${threadInfo.tid}]`;
+ ctx.fillText(displayText, startDraw.x + 20, startDraw.y + 20);
+ ctx.fillText(processText, startDraw.x + 20, startDraw.y + 37);
+ ctx.fillText(threadText, startDraw.x + 20, startDraw.y + 55);
+ }
+
+ // Draw latency arrow and explanation text.
+ drawDoubleHeadedArrow(ctx, startDraw.x, startDraw.y + 80, 60, true);
+ if (details.ts) {
+ const displayLatency =
+ `Scheduling latency: ${
+ timeToCode(
+ details.ts -
+ (details.wakeupTs -
+ globals.state.traceTime.startSec))
+ }`;
+ ctx.fillText(displayLatency, startDraw.x + 70, startDraw.y + 86);
+ const explain1 =
+ 'This is the interval from when the task became eligible to run';
+ const explain2 =
+ '(e.g. because of notifying a wait queue it was suspended on) to';
+ const explain3 = 'when it started running.';
+ ctx.font = '10px Google Sans';
+ ctx.fillText(explain1, startDraw.x + 70, startDraw.y + 86 + 16);
+ ctx.fillText(explain2, startDraw.x + 70, startDraw.y + 86 + 16 + 12);
+ ctx.fillText(explain3, startDraw.x + 70, startDraw.y + 86 + 16 + 24);
+ }
+ }
+}
}
diff --git a/ui/src/frontend/thread_state_panel.ts b/ui/src/frontend/thread_state_panel.ts
index 12c0055..a65cea9 100644
--- a/ui/src/frontend/thread_state_panel.ts
+++ b/ui/src/frontend/thread_state_panel.ts
@@ -36,17 +36,17 @@
m('.details-panel-heading', 'Thread State'),
m('.details-table', [m('table', [
m('tr',
- m('td', `Start time`),
+ m('th', `Start time`),
m('td',
`${
timeToCode(attrs.ts - globals.state.traceTime.startSec)
}`)),
- m('tr', m('td', `Duration`), m('td', `${timeToCode(attrs.dur)}`)),
+ m('tr', m('th', `Duration`), m('td', `${timeToCode(attrs.dur)}`)),
m('tr',
- m('td', `State`),
+ m('th', `State`),
m('td', `${translateState(attrs.state)}`)),
m('tr',
- m('td', `Process`),
+ m('th', `Process`),
m('td', `${threadInfo.procName} [${threadInfo.pid}]`)),
])]));
} else {
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index 4893f94..6dc195c 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -147,6 +147,15 @@
size.height,
`rgba(52,69,150,0.3)`);
}
+ if (globals.state.currentSelection.kind === 'SLICE' &&
+ globals.sliceDetails.wakeupTs !== undefined) {
+ drawVerticalLineAtTime(
+ ctx,
+ localState.timeScale,
+ globals.sliceDetails.wakeupTs,
+ size.height,
+ `black`);
+ }
}
}
}
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index ca75f50..950fed9 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -259,6 +259,15 @@
size.height,
`rgba(52,69,150,0.3)`);
}
+ if (globals.state.currentSelection.kind === 'SLICE' &&
+ globals.sliceDetails.wakeupTs !== undefined) {
+ drawVerticalLineAtTime(
+ ctx,
+ localState.timeScale,
+ globals.sliceDetails.wakeupTs,
+ size.height,
+ `black`);
+ }
}
}
}
diff --git a/ui/src/frontend/vertical_line_helper.ts b/ui/src/frontend/vertical_line_helper.ts
index 9f91650..59828b6 100644
--- a/ui/src/frontend/vertical_line_helper.ts
+++ b/ui/src/frontend/vertical_line_helper.ts
@@ -34,8 +34,8 @@
ctx.strokeStyle = color;
const prevLineWidth = ctx.lineWidth;
ctx.lineWidth = lineWidth;
- ctx.moveTo(xPos + (lineWidth / 2), 0);
- ctx.lineTo(xPos + (lineWidth / 2), height);
+ ctx.moveTo(xPos, 0);
+ ctx.lineTo(xPos, height);
ctx.stroke();
ctx.closePath();
ctx.lineWidth = prevLineWidth;
diff --git a/ui/src/tracks/chrome_slices/frontend.ts b/ui/src/tracks/chrome_slices/frontend.ts
index 38f937f..d83da53 100644
--- a/ui/src/tracks/chrome_slices/frontend.ts
+++ b/ui/src/tracks/chrome_slices/frontend.ts
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import {cropText} from '../../common/canvas_utils';
import {TrackState} from '../../common/state';
-import {cropText} from '../../common/track_utils';
import {checkerboardExcept} from '../../frontend/checkerboard';
import {globals} from '../../frontend/globals';
import {Track} from '../../frontend/track';
diff --git a/ui/src/tracks/cpu_slices/frontend.ts b/ui/src/tracks/cpu_slices/frontend.ts
index f24f3b8..aceba82 100644
--- a/ui/src/tracks/cpu_slices/frontend.ts
+++ b/ui/src/tracks/cpu_slices/frontend.ts
@@ -15,8 +15,9 @@
import {search, searchEq} from '../../base/binary_search';
import {assertTrue} from '../../base/logging';
import {Actions} from '../../common/actions';
+import {cropText, drawDoubleHeadedArrow} from '../../common/canvas_utils';
import {TrackState} from '../../common/state';
-import {cropText} from '../../common/track_utils';
+import {timeToString} from '../../common/time';
import {checkerboardExcept} from '../../frontend/checkerboard';
import {colorForThread, hueForCpu} from '../../frontend/colorizer';
import {globals} from '../../frontend/globals';
@@ -180,26 +181,64 @@
ctx.fillText(subTitle, rectXCenter, MARGIN_TOP + RECT_HEIGHT / 2 + 11);
}
- // Draw a rectangle around the slice that is currently selected.
const selection = globals.state.currentSelection;
+ const details = globals.sliceDetails;
if (selection !== null && selection.kind === 'SLICE') {
- const sliceIndex = searchEq(data.ids, selection.id);
- if (sliceIndex[0] !== sliceIndex[1]) {
- const tStart = data.starts[sliceIndex[0]];
- const tEnd = data.ends[sliceIndex[0]];
- const utid = data.utids[sliceIndex[0]];
+ const [startIndex, endIndex] = searchEq(data.ids, selection.id);
+ if (startIndex !== endIndex) {
+ const tStart = data.starts[startIndex];
+ const tEnd = data.ends[startIndex];
+ const utid = data.utids[startIndex];
const color = colorForThread(globals.threads.get(utid));
const rectStart = timeScale.timeToPx(tStart);
const rectEnd = timeScale.timeToPx(tEnd);
+ // Draw a rectangle around the slice that is currently selected.
ctx.strokeStyle = `hsl(${color.h}, ${color.s}%, 30%)`;
ctx.beginPath();
ctx.lineWidth = 3;
- ctx.moveTo(rectStart, MARGIN_TOP - 1.5);
- ctx.lineTo(rectEnd, MARGIN_TOP - 1.5);
- ctx.lineTo(rectEnd, MARGIN_TOP + RECT_HEIGHT + 1.5);
- ctx.lineTo(rectStart, MARGIN_TOP + RECT_HEIGHT + 1.5);
- ctx.lineTo(rectStart, MARGIN_TOP - 1.5);
- ctx.stroke();
+ ctx.strokeRect(
+ rectStart, MARGIN_TOP - 1.5, rectEnd - rectStart, RECT_HEIGHT + 3);
+ ctx.closePath();
+ // Draw arrow from wakeup time of current slice.
+ if (details.wakeupTs) {
+ const wakeupPos = timeScale.timeToPx(details.wakeupTs);
+ const latencyWidth = rectStart - wakeupPos;
+ drawDoubleHeadedArrow(
+ ctx,
+ wakeupPos,
+ MARGIN_TOP + RECT_HEIGHT,
+ latencyWidth,
+ latencyWidth >= 20);
+ // Latency time with a white semi-transparent background.
+ const displayText = timeToString(tStart - details.wakeupTs);
+ const measured = ctx.measureText(displayText);
+ if (latencyWidth >= measured.width + 2) {
+ ctx.fillStyle = 'rgba(255,255,255,0.7)';
+ ctx.fillRect(
+ wakeupPos + latencyWidth / 2 - measured.width / 2 - 1,
+ MARGIN_TOP + RECT_HEIGHT - 12,
+ measured.width + 2,
+ 11);
+ ctx.textBaseline = 'bottom';
+ ctx.fillStyle = 'black';
+ ctx.fillText(
+ displayText,
+ wakeupPos + (latencyWidth) / 2,
+ MARGIN_TOP + RECT_HEIGHT - 1);
+ }
+ }
+ }
+
+ // Draw diamond if the track being drawn is the cpu of the waker.
+ if (this.config.cpu === details.wakerCpu && details.wakeupTs) {
+ const wakeupPos = timeScale.timeToPx(details.wakeupTs);
+ ctx.beginPath();
+ ctx.moveTo(wakeupPos, MARGIN_TOP + RECT_HEIGHT / 2 + 8);
+ ctx.fillStyle = 'black';
+ ctx.lineTo(wakeupPos + 6, MARGIN_TOP + RECT_HEIGHT / 2);
+ ctx.lineTo(wakeupPos, MARGIN_TOP + RECT_HEIGHT / 2 - 8);
+ ctx.lineTo(wakeupPos - 6, MARGIN_TOP + RECT_HEIGHT / 2);
+ ctx.fill();
ctx.closePath();
}
}
diff --git a/ui/src/tracks/thread_state/frontend.ts b/ui/src/tracks/thread_state/frontend.ts
index 03ccbbf..ebae655 100644
--- a/ui/src/tracks/thread_state/frontend.ts
+++ b/ui/src/tracks/thread_state/frontend.ts
@@ -15,9 +15,9 @@
import {search, searchEq} from '../../base/binary_search';
import {Actions} from '../../common/actions';
+import {cropText} from '../../common/canvas_utils';
import {TrackState} from '../../common/state';
import {translateState} from '../../common/thread_state';
-import {cropText} from '../../common/track_utils';
import {colorForState} from '../../frontend/colorizer';
import {globals} from '../../frontend/globals';
import {Track} from '../../frontend/track';