Merge "tp: migrate chrome metrics descriptor generation to build time"
diff --git a/CHANGELOG b/CHANGELOG
index ee47b46..a80bf0d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,7 @@
Trace Processor:
*
UI:
+ * Flow events are now drawn as arrows for the currently selected slice.
*
diff --git a/src/trace_processor/metrics/android/java_heap_histogram.sql b/src/trace_processor/metrics/android/java_heap_histogram.sql
index f7cddf2..bc81600 100644
--- a/src/trace_processor/metrics/android/java_heap_histogram.sql
+++ b/src/trace_processor/metrics/android/java_heap_histogram.sql
@@ -26,8 +26,13 @@
'android.content.ContentProvider',
'android.content.BroadcastReceiver',
'android.content.Context',
+ 'android.content.Intent',
+ 'android.content.res.ApkAssets',
'android.os.Handler',
- 'android.graphics.Bitmap')
+ 'android.os.Parcel',
+ 'android.graphics.Bitmap',
+ 'android.graphics.BaseCanvas',
+ 'com.android.server.am.PendingIntentRecord')
UNION ALL
SELECT child.id, parent.category
FROM heap_graph_class child JOIN cls_visitor parent ON parent.cls_id = child.superclass_id
diff --git a/tools/gen_cc_proto_descriptor.py b/tools/gen_cc_proto_descriptor.py
index 458fc5f..0c2dc66 100755
--- a/tools/gen_cc_proto_descriptor.py
+++ b/tools/gen_cc_proto_descriptor.py
@@ -39,8 +39,8 @@
binary, width=80, initial_indent=' ', subsequent_indent=' ')
relative_target = os.path.relpath(target, gendir)
- include_guard = relative_target.replace('/', '_').replace('.',
- '_').upper() + '_'
+ include_guard = relative_target.replace('\\', '_').replace('/', '_').replace(
+ '.', '_').upper() + '_'
with open(target, 'wb') as f:
f.write("""/*
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index 3cd754d..b56cfcb 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -534,6 +534,7 @@
--collapsed-background: hsla(190, 49%, 97%, 1);
--collapsed-transparent: hsla(190, 49%, 97%, 0);
--expanded-background: hsl(215, 22%, 19%);
+ --expanded-transparent: hsl(215, 22%, 19%, 0);
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: 1fr;
@@ -547,7 +548,7 @@
margin-top: -1px;
}
&[collapsed=true] {
- background-color: var(--collapsed-background-transparent);
+ background-color: var(--collapsed-transparent);
.shell {
border-right: 1px solid #c7d0db;
background-color: var(--collapsed-background);
@@ -557,7 +558,7 @@
};
}
&[collapsed=false] {
- background-color: var(--expanded-background);
+ background-color: var(--expanded-transparent);
color: white;
font-weight: bold;
.shell.flash {
diff --git a/ui/src/common/colorizer.ts b/ui/src/common/colorizer.ts
index 601f9b0..79105cf 100644
--- a/ui/src/common/colorizer.ts
+++ b/ui/src/common/colorizer.ts
@@ -127,6 +127,10 @@
return Object.assign({}, MD_PALETTE[colorIdx]);
}
+export function hueForSlice(sliceName: string): number {
+ return hash(sliceName, 360);
+}
+
export function colorForThread(thread?: {pid?: number, tid: number}): Color {
if (thread === undefined) {
return Object.assign({}, GREY_COLOR);
diff --git a/ui/src/controller/flow_events_controller.ts b/ui/src/controller/flow_events_controller.ts
index f6f3dce..277af85 100644
--- a/ui/src/controller/flow_events_controller.ts
+++ b/ui/src/controller/flow_events_controller.ts
@@ -52,8 +52,8 @@
const query = `
select
- f.slice_out, t1.track_id, t1.name, (t1.ts+t1.dur),
- f.slice_in, t2.track_id, t2.name, t2.ts
+ f.slice_out, t1.track_id, t1.name, t1.ts, (t1.ts+t1.dur), t1.depth,
+ f.slice_in, t2.track_id, t2.name, t2.ts, (t2.ts+t2.dur), t2.depth
from flow f
join slice t1 on f.slice_out = t1.slice_id
join slice t2 on f.slice_in = t2.slice_id
@@ -66,25 +66,33 @@
const beginSliceId = res.columns[0].longValues![i];
const beginTrackId = res.columns[1].longValues![i];
const beginSliceName = res.columns[2].stringValues![i];
- const beginTs = fromNs(res.columns[3].longValues![i]);
+ const beginSliceStartTs = fromNs(res.columns[3].longValues![i]);
+ const beginSliceEndTs = fromNs(res.columns[4].longValues![i]);
+ const beginDepth = res.columns[5].longValues![i];
- const endSliceId = res.columns[4].longValues![i];
- const endTrackId = res.columns[5].longValues![i];
- const endSliceName = res.columns[6].stringValues![i];
- const endTs = fromNs(res.columns[7].longValues![i]);
+ const endSliceId = res.columns[6].longValues![i];
+ const endTrackId = res.columns[7].longValues![i];
+ const endSliceName = res.columns[8].stringValues![i];
+ const endSliceStartTs = fromNs(res.columns[9].longValues![i]);
+ const endSliceEndTs = fromNs(res.columns[10].longValues![i]);
+ const endDepth = res.columns[11].longValues![i];
flows.push({
begin: {
trackId: beginTrackId,
sliceId: beginSliceId,
sliceName: beginSliceName,
- ts: beginTs
+ sliceStartTs: beginSliceStartTs,
+ sliceEndTs: beginSliceEndTs,
+ depth: beginDepth
},
end: {
trackId: endTrackId,
sliceId: endSliceId,
sliceName: endSliceName,
- ts: endTs
+ sliceStartTs: endSliceStartTs,
+ sliceEndTs: endSliceEndTs,
+ depth: endDepth
}
});
}
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
new file mode 100644
index 0000000..51d12f7
--- /dev/null
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -0,0 +1,272 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use size 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.
+
+import {hueForSlice} from '../common/colorizer';
+import {TRACK_SHELL_WIDTH} from './css_constants';
+import {FlowPoint, globals} from './globals';
+import {PanelVNode} from './panel';
+import {findUiTrackId} from './scroll_helper';
+import {SliceRect} from './track';
+import {TrackGroupPanel} from './track_group_panel';
+import {TrackPanel} from './track_panel';
+
+const TRACK_GROUP_CONNECTION_OFFSET = 5;
+const TRIANGLE_SIZE = 5;
+const CIRCLE_RADIUS = 3;
+const BEZIER_OFFSET = 30;
+
+type LineDirection = 'LEFT'|'RIGHT'|'UP'|'DOWN';
+type ConnectionType = 'TRACK'|'TRACK_GROUP';
+
+interface TrackPanelInfo {
+ panel: TrackPanel;
+ yStart: number;
+}
+
+interface TrackGroupPanelInfo {
+ panel: TrackGroupPanel;
+ yStart: number;
+ height: number;
+}
+
+function HasTrackId(obj: {}): obj is {trackId: number} {
+ return (obj as {trackId?: number}).trackId !== undefined;
+}
+
+function HasId(obj: {}): obj is {id: number} {
+ return (obj as {id?: number}).id !== undefined;
+}
+
+function HasTrackGroupId(obj: {}): obj is {trackGroupId: string} {
+ return (obj as {trackGroupId?: string}).trackGroupId !== undefined;
+}
+
+export class FlowEventsRendererArgs {
+ trackIdToTrackPanel: Map<number, TrackPanelInfo>;
+ groupIdToTrackGroupPanel: Map<string, TrackGroupPanelInfo>;
+
+ constructor(public canvasWidth: number, public canvasHeight: number) {
+ this.trackIdToTrackPanel = new Map<number, TrackPanelInfo>();
+ this.groupIdToTrackGroupPanel = new Map<string, TrackGroupPanelInfo>();
+ }
+
+ registerPanel(panel: PanelVNode, yStart: number, height: number) {
+ if (panel.state instanceof TrackPanel && HasId(panel.attrs)) {
+ const config = globals.state.tracks[panel.attrs.id].config;
+ if (HasTrackId(config)) {
+ this.trackIdToTrackPanel.set(
+ config.trackId, {panel: panel.state, yStart});
+ }
+ } else if (
+ panel.state instanceof TrackGroupPanel &&
+ HasTrackGroupId(panel.attrs)) {
+ this.groupIdToTrackGroupPanel.set(
+ panel.attrs.trackGroupId, {panel: panel.state, yStart, height});
+ }
+ }
+}
+
+export class FlowEventsRenderer {
+ private getTrackGroupIdByTrackId(trackId: number): string|undefined {
+ const uiTrackId = findUiTrackId(trackId);
+ return uiTrackId ? globals.state.tracks[uiTrackId].trackGroup : undefined;
+ }
+
+ private getTrackGroupYCoordinate(
+ args: FlowEventsRendererArgs, trackId: number): number|undefined {
+ const trackGroupId = this.getTrackGroupIdByTrackId(trackId);
+ if (!trackGroupId) {
+ return undefined;
+ }
+ const trackGroupInfo = args.groupIdToTrackGroupPanel.get(trackGroupId);
+ if (!trackGroupInfo) {
+ return undefined;
+ }
+ return trackGroupInfo.yStart + trackGroupInfo.height -
+ TRACK_GROUP_CONNECTION_OFFSET;
+ }
+
+ private getTrackYCoordinate(args: FlowEventsRendererArgs, trackId: number):
+ number|undefined {
+ return args.trackIdToTrackPanel.get(trackId) ?.yStart;
+ }
+
+ private getYConnection(
+ args: FlowEventsRendererArgs, trackId: number,
+ rect?: SliceRect): {y: number, connection: ConnectionType}|undefined {
+ if (!rect) {
+ const y = this.getTrackGroupYCoordinate(args, trackId);
+ if (y === undefined) {
+ return undefined;
+ }
+ return {y, connection: 'TRACK_GROUP'};
+ }
+ const y = (this.getTrackYCoordinate(args, trackId) || 0) + rect.top +
+ rect.height * 0.5;
+
+ return {
+ y: Math.min(Math.max(0, y), args.canvasHeight),
+ connection: 'TRACK'
+ };
+ }
+
+ private getXCoordinate(ts: number): number {
+ return globals.frontendLocalState.timeScale.timeToPx(ts);
+ }
+
+ private getSliceRect(args: FlowEventsRendererArgs, point: FlowPoint):
+ SliceRect|undefined {
+ const trackPanel = args.trackIdToTrackPanel.get(point.trackId) ?.panel;
+ if (!trackPanel) {
+ return undefined;
+ }
+ return trackPanel.getSliceRect(
+ point.sliceStartTs, point.sliceEndTs, point.depth);
+ }
+
+ render(ctx: CanvasRenderingContext2D, args: FlowEventsRendererArgs) {
+ ctx.save();
+ ctx.translate(TRACK_SHELL_WIDTH, 0);
+ ctx.rect(0, 0, args.canvasWidth - TRACK_SHELL_WIDTH, args.canvasHeight);
+ ctx.clip();
+
+ globals.boundFlows.forEach(flow => {
+ const beginSliceRect = this.getSliceRect(args, flow.begin);
+ const endSliceRect = this.getSliceRect(args, flow.end);
+
+ const beginYConnection =
+ this.getYConnection(args, flow.begin.trackId, beginSliceRect);
+ const endYConnection =
+ this.getYConnection(args, flow.end.trackId, endSliceRect);
+
+ if (!beginYConnection || !endYConnection) {
+ return;
+ }
+
+ let beginDir: LineDirection = 'LEFT';
+ let endDir: LineDirection = 'RIGHT';
+ if (beginYConnection.connection === 'TRACK_GROUP') {
+ beginDir = beginYConnection.y > endYConnection.y ? 'DOWN' : 'UP';
+ }
+ if (endYConnection.connection === 'TRACK_GROUP') {
+ endDir = endYConnection.y > beginYConnection.y ? 'DOWN' : 'UP';
+ }
+
+ const begin = {
+ x: this.getXCoordinate(flow.begin.sliceEndTs),
+ y: beginYConnection.y,
+ hue: hueForSlice(flow.begin.sliceName),
+ dir: beginDir
+ };
+ const end = {
+ x: this.getXCoordinate(flow.end.sliceStartTs),
+ y: endYConnection.y,
+ hue: hueForSlice(flow.end.sliceName),
+ dir: endDir
+ };
+ this.drawFlowArrow(ctx, begin, end);
+ });
+
+ ctx.restore();
+ }
+
+ private getDeltaX(dir: LineDirection, offset: number): number {
+ switch (dir) {
+ case 'LEFT':
+ return -offset;
+ case 'RIGHT':
+ return offset;
+ case 'UP':
+ return 0;
+ case 'DOWN':
+ return 0;
+ default:
+ return 0;
+ }
+ }
+
+ private getDeltaY(dir: LineDirection, offset: number): number {
+ switch (dir) {
+ case 'LEFT':
+ return 0;
+ case 'RIGHT':
+ return 0;
+ case 'UP':
+ return -offset;
+ case 'DOWN':
+ return offset;
+ default:
+ return 0;
+ }
+ }
+
+ private drawFlowArrow(
+ ctx: CanvasRenderingContext2D,
+ begin: {x: number, y: number, hue: number, dir: LineDirection},
+ end: {x: number, y: number, hue: number, dir: LineDirection}) {
+ const grad = ctx.createLinearGradient(begin.x, begin.y, end.x, end.y);
+ grad.addColorStop(0, `hsl(${begin.hue}, 50%, 65%)`);
+ grad.addColorStop(1, `hsl(${end.hue}, 50%, 65%)`);
+
+ const END_OFFSET =
+ (end.dir === 'RIGHT' || end.dir === 'LEFT' ? TRIANGLE_SIZE : 0);
+
+ // draw curved line from begin to end (bezier curve)
+ ctx.strokeStyle = grad;
+ ctx.lineWidth = 2;
+ ctx.beginPath();
+ ctx.moveTo(begin.x, begin.y);
+ ctx.bezierCurveTo(
+ begin.x - this.getDeltaX(begin.dir, BEZIER_OFFSET),
+ begin.y - this.getDeltaY(begin.dir, BEZIER_OFFSET),
+ end.x - this.getDeltaX(end.dir, BEZIER_OFFSET + END_OFFSET),
+ end.y - this.getDeltaY(end.dir, BEZIER_OFFSET + END_OFFSET),
+ end.x - this.getDeltaX(end.dir, END_OFFSET),
+ end.y - this.getDeltaY(end.dir, END_OFFSET));
+ ctx.stroke();
+
+ // TODO (andrewbb): probably we should add a parameter 'MarkerType' to be
+ // able to choose what marker we want to draw _before_ the function call.
+ // e.g. triangle, circle, square?
+ if (begin.dir !== 'RIGHT' && begin.dir !== 'LEFT') {
+ // draw a circle if we the line has a vertical connection
+ ctx.fillStyle = `hsl(${begin.hue}, 50%, 65%)`;
+ ctx.beginPath();
+ ctx.arc(begin.x, begin.y, 3, 0, 2 * Math.PI);
+ ctx.closePath();
+ ctx.fill();
+ }
+
+
+ if (end.dir !== 'RIGHT' && end.dir !== 'LEFT') {
+ // draw a circle if we the line has a vertical connection
+ ctx.fillStyle = `hsl(${begin.hue}, 50%, 65%)`;
+ ctx.beginPath();
+ ctx.arc(end.x, end.y, CIRCLE_RADIUS, 0, 2 * Math.PI);
+ ctx.closePath();
+ ctx.fill();
+ } else {
+ const dx = this.getDeltaX(end.dir, TRIANGLE_SIZE);
+ const dy = this.getDeltaY(end.dir, TRIANGLE_SIZE);
+ // draw small triangle
+ ctx.fillStyle = `hsl(${end.hue}, 50%, 65%)`;
+ ctx.beginPath();
+ ctx.moveTo(end.x, end.y);
+ ctx.lineTo(end.x - dx - dy, end.y + dx - dy);
+ ctx.lineTo(end.x - dx + dy, end.y - dx - dy);
+ ctx.closePath();
+ ctx.fill();
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 97a4727..83e9096 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -50,10 +50,14 @@
}
export interface FlowPoint {
+ trackId: number;
+
sliceName: string;
sliceId: number;
- trackId: number;
- ts: number;
+ sliceStartTs: number;
+ sliceEndTs: number;
+
+ depth: number;
}
export interface Flow {
@@ -250,8 +254,8 @@
return assertExists(this._boundFlows);
}
- set boundFlows(click: Flow[]) {
- this._boundFlows = assertExists(click);
+ set boundFlows(boundFlows: Flow[]) {
+ this._boundFlows = assertExists(boundFlows);
}
get counterDetails() {
diff --git a/ui/src/frontend/panel_container.ts b/ui/src/frontend/panel_container.ts
index d7199a6..4de44aa 100644
--- a/ui/src/frontend/panel_container.ts
+++ b/ui/src/frontend/panel_container.ts
@@ -17,6 +17,10 @@
import {assertExists, assertTrue} from '../base/logging';
import {TOPBAR_HEIGHT, TRACK_SHELL_WIDTH} from './css_constants';
+import {
+ FlowEventsRenderer,
+ FlowEventsRendererArgs
+} from './flow_events_renderer';
import {globals} from './globals';
import {isPanelVNode, Panel, PanelSize, PanelVNode} from './panel';
import {
@@ -60,6 +64,8 @@
private totalPanelHeight = 0;
private canvasHeight = 0;
+ private flowEventsRenderer: FlowEventsRenderer;
+
private panelPerfStats = new WeakMap<Panel, RunningStatistics>();
private perfStats = {
totalPanels: 0,
@@ -157,6 +163,7 @@
this.canvasRedrawer = () => this.redrawCanvas();
globals.rafScheduler.addRedrawCallback(this.canvasRedrawer);
perfDisplay.addContainer(this);
+ this.flowEventsRenderer = new FlowEventsRenderer();
}
oncreate(vnodeDom: m.CVnodeDOM<Attrs>) {
@@ -326,24 +333,27 @@
const panels = assertExists(this.attrs).panels;
assertTrue(panels.length === this.panelPositions.length);
let totalOnCanvas = 0;
+ const flowEventsRendererArgs =
+ new FlowEventsRendererArgs(this.parentWidth, this.canvasHeight);
for (let i = 0; i < panels.length; i++) {
const panel = panels[i];
const panelHeight = this.panelPositions[i].height;
const yStartOnCanvas = panelYStart - canvasYStart;
- if (!this.overlapsCanvas(yStartOnCanvas, yStartOnCanvas + panelHeight)) {
- panelYStart += panelHeight;
- continue;
- }
-
- totalOnCanvas++;
-
if (!isPanelVNode(panel)) {
throw new Error('Vnode passed to panel container is not a panel');
}
// TODO(hjd): This cast should be unnecessary given the type guard above.
const p = panel as PanelVNode<{}>;
+ flowEventsRendererArgs.registerPanel(p, yStartOnCanvas, panelHeight);
+
+ if (!this.overlapsCanvas(yStartOnCanvas, yStartOnCanvas + panelHeight)) {
+ panelYStart += panelHeight;
+ continue;
+ }
+
+ totalOnCanvas++;
this.ctx.save();
this.ctx.translate(0, yStartOnCanvas);
@@ -362,6 +372,7 @@
this.drawTopLayerOnCanvas();
const redrawDur = debugNow() - redrawStart;
this.updatePerfStats(redrawDur, panels.length, totalOnCanvas);
+ this.flowEventsRenderer.render(this.ctx, flowEventsRendererArgs);
}
// The panels each draw on the canvas but some details need to be drawn across
diff --git a/ui/src/frontend/track.ts b/ui/src/frontend/track.ts
index 4df2730..c5af2f2 100644
--- a/ui/src/frontend/track.ts
+++ b/ui/src/frontend/track.ts
@@ -35,6 +35,14 @@
create(TrackState: TrackState): Track;
}
+export interface SliceRect {
+ left: number;
+ width: number;
+ top: number;
+ height: number;
+ visible: boolean;
+}
+
/**
* The abstract class that needs to be implemented by all tracks.
*/
@@ -116,4 +124,15 @@
ctx.fillText(text2, xPos + 8, this.getHeight() / 2 + 6);
}
}
+
+ /**
+ * Returns a place where a given slice should be drawn. Should be implemented
+ * only for track types that support slices e.g. chrome_slice, async_slices
+ * tStart - slice start time in seconds, tEnd - slice end time in seconds,
+ * depth - slice depth
+ */
+ getSliceRect(_tStart: number, _tEnd: number, _depth: number): SliceRect
+ |undefined {
+ return undefined;
+ }
}
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index 91c0933..c65af87 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -146,8 +146,14 @@
onupdate({dom}: m.CVnodeDOM<Attrs>) {
const shell = assertExists(dom.querySelector('.shell'));
this.shellWidth = shell.getBoundingClientRect().width;
- this.backgroundColor =
- getComputedStyle(dom).getPropertyValue('--collapsed-background');
+ // TODO(andrewbb): move this to css_constants
+ if (this.trackGroupState.collapsed) {
+ this.backgroundColor =
+ getComputedStyle(dom).getPropertyValue('--collapsed-background');
+ } else {
+ this.backgroundColor =
+ getComputedStyle(dom).getPropertyValue('--expanded-background');
+ }
}
highlightIfTrackSelected(ctx: CanvasRenderingContext2D, size: PanelSize) {
@@ -169,13 +175,14 @@
renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
const collapsed = this.trackGroupState.collapsed;
- if (!collapsed) return;
-
- ctx.save();
ctx.fillStyle = this.backgroundColor;
ctx.fillRect(0, 0, size.width, size.height);
+ if (!collapsed) return;
+
+ this.highlightIfTrackSelected(ctx, size);
+
drawGridLines(
ctx,
globals.frontendLocalState.timeScale,
@@ -183,6 +190,7 @@
size.width,
size.height);
+ ctx.save();
ctx.translate(this.shellWidth, 0);
if (this.summaryTrack) {
this.summaryTrack.render(ctx);
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 59c7fe7..e982f99 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -25,7 +25,7 @@
import {BLANK_CHECKBOX, CHECKBOX, STAR, STAR_BORDER} from './icons';
import {Panel, PanelSize} from './panel';
import {verticalScrollToTrack} from './scroll_helper';
-import {Track} from './track';
+import {SliceRect, Track} from './track';
import {trackRegistry} from './track_registry';
import {
drawVerticalLineAtTime,
@@ -385,4 +385,9 @@
}
}
}
+
+ getSliceRect(tStart: number, tDur: number, depth: number): SliceRect
+ |undefined {
+ return this.track.getSliceRect(tStart, tDur, depth);
+ }
}
diff --git a/ui/src/tracks/chrome_slices/frontend.ts b/ui/src/tracks/chrome_slices/frontend.ts
index d956acf..ad7b0b7 100644
--- a/ui/src/tracks/chrome_slices/frontend.ts
+++ b/ui/src/tracks/chrome_slices/frontend.ts
@@ -14,11 +14,12 @@
import {Actions} from '../../common/actions';
import {cropText, drawIncompleteSlice} from '../../common/canvas_utils';
+import {hueForSlice} from '../../common/colorizer';
import {TrackState} from '../../common/state';
import {toNs} from '../../common/time';
import {checkerboardExcept} from '../../frontend/checkerboard';
import {globals} from '../../frontend/globals';
-import {Track} from '../../frontend/track';
+import {SliceRect, Track} from '../../frontend/track';
import {trackRegistry} from '../../frontend/track_registry';
import {Config, Data, SLICE_TRACK_KIND} from './common';
@@ -29,15 +30,6 @@
const CHEVRON_WIDTH_PX = 10;
const HALF_CHEVRON_WIDTH_PX = CHEVRON_WIDTH_PX / 2;
-function hash(s: string): number {
- let hash = 0x811c9dc5 & 0xfffffff;
- for (let i = 0; i < s.length; i++) {
- hash ^= s.charCodeAt(i);
- hash = (hash * 16777619) & 0xffffffff;
- }
- return hash & 0xff;
-}
-
export class ChromeSliceTrack extends Track<Config, Data> {
static readonly kind: string = SLICE_TRACK_KIND;
static create(trackState: TrackState): Track {
@@ -74,7 +66,6 @@
// measuretext is expensive so we only use it once.
const charWidth = ctx.measureText('ACBDLqsdfg').width / 10;
- const pxEnd = timeScale.timeToPx(visibleWindowTime.end);
// The draw of the rect on the selected slice must happen after the other
// drawings, otherwise it would result under another rect.
@@ -89,13 +80,13 @@
const isInstant = data.isInstant[i];
const title = data.strings[titleId];
let incompleteSlice = false;
-
if (toNs(tEnd) - toNs(tStart) === -1) { // incomplete slice
incompleteSlice = true;
tEnd = tStart + INCOMPLETE_SLICE_TIME_S;
}
- if (tEnd <= visibleWindowTime.start || tStart >= visibleWindowTime.end) {
+ const rect = this.getSliceRect(tStart, tEnd, depth);
+ if (!rect || !rect.visible) {
continue;
}
@@ -104,18 +95,8 @@
currentSelection.kind === 'CHROME_SLICE' &&
currentSelection.id !== undefined && currentSelection.id === sliceId;
- const rectXStart = Math.max(timeScale.timeToPx(tStart), 0);
- let rectXEnd = Math.min(timeScale.timeToPx(tEnd), pxEnd);
- let rectWidth = rectXEnd - rectXStart;
-
- // All slices should be at least 1px.
- if (rectWidth < 1) {
- rectWidth = 1;
- rectXEnd = rectXStart + 1;
- }
- const rectYStart = TRACK_PADDING + depth * SLICE_HEIGHT;
const name = title.replace(/( )?\d+/g, '');
- const hue = hash(name);
+ const hue = hueForSlice(name);
const saturation = isSelected ? 80 : 50;
const hovered = titleId === this.hoveredTitleId;
const color = `hsl(${hue}, ${saturation}%, ${hovered ? 30 : 65}%)`;
@@ -130,25 +111,20 @@
// D B
// Then B, C, D and back to A:
if (isInstant) {
- ctx.moveTo(rectXStart, rectYStart);
- ctx.lineTo(
- rectXStart + HALF_CHEVRON_WIDTH_PX, rectYStart + SLICE_HEIGHT);
- ctx.lineTo(
- rectXStart, rectYStart + SLICE_HEIGHT - HALF_CHEVRON_WIDTH_PX);
- ctx.lineTo(
- rectXStart - HALF_CHEVRON_WIDTH_PX, rectYStart + SLICE_HEIGHT);
- ctx.lineTo(rectXStart, rectYStart);
+ ctx.moveTo(rect.left, rect.top);
+ ctx.lineTo(rect.left + HALF_CHEVRON_WIDTH_PX, rect.top + SLICE_HEIGHT);
+ ctx.lineTo(rect.left, rect.top + SLICE_HEIGHT - HALF_CHEVRON_WIDTH_PX);
+ ctx.lineTo(rect.left - HALF_CHEVRON_WIDTH_PX, rect.top + SLICE_HEIGHT);
+ ctx.lineTo(rect.left, rect.top);
ctx.fill();
continue;
}
-
- if (incompleteSlice && rectWidth > SLICE_HEIGHT / 4) {
+ if (incompleteSlice && rect.width > SLICE_HEIGHT / 4) {
drawIncompleteSlice(
- ctx, rectXStart, rectYStart, rectWidth, SLICE_HEIGHT, color);
+ ctx, rect.left, rect.top, rect.width, SLICE_HEIGHT, color);
} else {
- ctx.fillRect(rectXStart, rectYStart, rectWidth, SLICE_HEIGHT);
+ ctx.fillRect(rect.left, rect.top, rect.width, SLICE_HEIGHT);
}
-
// Selected case
if (isSelected) {
drawRectOnSelected = () => {
@@ -156,16 +132,16 @@
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeRect(
- rectXStart, rectYStart - 1.5, rectWidth, SLICE_HEIGHT + 3);
+ rect.left, rect.top - 1.5, rect.width, SLICE_HEIGHT + 3);
ctx.closePath();
};
}
ctx.fillStyle = 'white';
- const displayText = cropText(title, charWidth, rectWidth);
- const rectXCenter = rectXStart + rectWidth / 2;
+ const displayText = cropText(title, charWidth, rect.width);
+ const rectXCenter = rect.left + rect.width / 2;
ctx.textBaseline = "middle";
- ctx.fillText(displayText, rectXCenter, rectYStart + SLICE_HEIGHT / 2);
+ ctx.fillText(displayText, rectXCenter, rect.top + SLICE_HEIGHT / 2);
}
drawRectOnSelected();
}
@@ -233,6 +209,22 @@
getHeight() {
return SLICE_HEIGHT * (this.config.maxDepth + 1) + 2 * TRACK_PADDING;
}
+
+ getSliceRect(tStart: number, tEnd: number, depth: number): SliceRect
+ |undefined {
+ const {timeScale, visibleWindowTime} = globals.frontendLocalState;
+ const pxEnd = timeScale.timeToPx(visibleWindowTime.end);
+ const left = Math.max(timeScale.timeToPx(tStart), 0);
+ const right = Math.min(timeScale.timeToPx(tEnd), pxEnd);
+ return {
+ left,
+ width: Math.max(right - left, 1),
+ top: TRACK_PADDING + depth * SLICE_HEIGHT,
+ height: SLICE_HEIGHT,
+ visible:
+ !(tEnd <= visibleWindowTime.start || tStart >= visibleWindowTime.end)
+ };
+ }
}
trackRegistry.register(ChromeSliceTrack);