Merge "trace_processor: Stop confusing upid with utid in systrace parsing"
diff --git a/buildtools/.gitignore b/buildtools/.gitignore
index 56cc991..1dd47b9 100644
--- a/buildtools/.gitignore
+++ b/buildtools/.gitignore
@@ -11,6 +11,7 @@
libcxx/
libcxxabi/
libunwind/
+linenoise/
linux/
linux64/
mac/
diff --git a/buildtools/BUILD.gn b/buildtools/BUILD.gn
index 33203c9..0415b3e 100644
--- a/buildtools/BUILD.gn
+++ b/buildtools/BUILD.gn
@@ -751,3 +751,22 @@
configs -= [ "//gn/standalone:extra_warnings" ]
public_configs = [ ":jsoncpp_config" ]
}
+
+config("linenoise_config") {
+ cflags = [
+ # Using -isystem instead of include_dirs (-I), so we don't need to suppress
+ # warnings coming from third-party headers. Doing so would mask warnings in
+ # our own code.
+ "-isystem",
+ rebase_path("linenoise", root_build_dir),
+ ]
+}
+
+source_set("linenoise") {
+ sources = [
+ "linenoise/linenoise.c",
+ "linenoise/linenoise.h",
+ ]
+ configs -= [ "//gn/standalone:extra_warnings" ]
+ public_configs = [ ":linenoise_config" ]
+}
diff --git a/include/perfetto/base/build_config.h b/include/perfetto/base/build_config.h
index e083415..ef00b16 100644
--- a/include/perfetto/base/build_config.h
+++ b/include/perfetto/base/build_config.h
@@ -70,6 +70,13 @@
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_CHROMIUM_BUILD() 0
#endif
+#if !defined(PERFETTO_BUILD_WITH_CHROMIUM) && \
+ !defined(PERFETTO_BUILD_WITH_ANDROID)
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_STANDALONE_BUILD() 1
+#else
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_STANDALONE_BUILD() 0
+#endif
+
#if defined(PERFETTO_START_DAEMONS_FOR_TESTING)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_START_DAEMONS() 1
#else
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 811443f..092c6bf 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -108,6 +108,9 @@
"../../protos/perfetto/trace_processor:lite",
"../base",
]
+ if (build_standalone) {
+ deps += [ "../../buildtools:linenoise" ]
+ }
sources = [
"trace_processor_shell.cc",
]
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index c485934..fd8af99 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -36,6 +36,10 @@
#define PERFETTO_HAS_SIGNAL_H() 0
#endif
+#if PERFETTO_BUILDFLAG(PERFETTO_STANDALONE_BUILD)
+#include <linenoise.h>
+#endif
+
#if PERFETTO_HAS_SIGNAL_H()
#include <signal.h>
#endif
@@ -46,11 +50,45 @@
namespace {
TraceProcessor* g_tp;
-void PrintPrompt() {
- printf("\r%80s\r> ", "");
- fflush(stdout);
+#if PERFETTO_BUILDFLAG(PERFETTO_STANDALONE_BUILD)
+
+void SetupLineEditor() {
+ linenoiseSetMultiLine(true);
+ linenoiseHistorySetMaxLen(1000);
}
+void FreeLine(char* line) {
+ linenoiseHistoryAdd(line);
+ linenoiseFree(line);
+}
+
+char* GetLine(const char* prompt) {
+ return linenoise(prompt);
+}
+
+#else
+
+void SetupLineEditor() {}
+
+void FreeLine(char* line) {
+ free(line);
+}
+
+char* GetLine(const char* prompt) {
+ printf("\r%80s\r%s", "", prompt);
+ fflush(stdout);
+ char* line = new char[1024];
+ if (!fgets(line, 1024 - 1, stdin)) {
+ FreeLine(line);
+ return nullptr;
+ }
+ if (strlen(line) > 0)
+ line[strlen(line) - 1] = 0;
+ return line;
+}
+
+#endif
+
void OnQueryResult(base::TimeNanos t_start, const protos::RawQueryResult& res) {
if (res.has_error()) {
PERFETTO_ELOG("SQLite error: %s", res.error().c_str());
@@ -175,12 +213,13 @@
signal(SIGINT, [](int) { g_tp->InterruptQuery(); });
#endif
+ SetupLineEditor();
+
for (;;) {
- PrintPrompt();
- char line[1024];
- if (!fgets(line, sizeof(line) - 1, stdin) || strcmp(line, "q\n") == 0)
- return 0;
- if (strcmp(line, "\n") == 0)
+ char* line = GetLine("> ");
+ if (!line || strcmp(line, "q\n") == 0)
+ break;
+ if (strcmp(line, "") == 0)
continue;
protos::RawQueryArgs query;
query.set_sql_query(line);
@@ -188,5 +227,9 @@
g_tp->ExecuteQuery(query, [t_start](const protos::RawQueryResult& res) {
OnQueryResult(t_start, res);
});
+
+ FreeLine(line);
}
+
+ return 0;
}
diff --git a/tools/install-build-deps b/tools/install-build-deps
index 5368905..d9512b5 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -178,6 +178,13 @@
'4adec454b60a943dd58603d4be80d42b2db62cbd',
'all',
),
+
+ # Linenoise
+ ('buildtools/linenoise',
+ 'https://github.com/antirez/linenoise.git',
+ '4a961c0108720741e2683868eb10495f015ee422',
+ 'all'
+ ),
]
# Dependencies required to build Android code.
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss
index 1b5c774..cac3d75 100644
--- a/ui/src/assets/perfetto.scss
+++ b/ui/src/assets/perfetto.scss
@@ -321,3 +321,67 @@
user-select: text;
word-break: break-word;
}
+
+.debug-panel-border {
+ position: absolute;
+ top: 0px;
+ height: 100%;
+ width: 100%;
+ border: 1px solid rgba(69, 187, 73, 0.5);
+ pointer-events: none;
+}
+
+.perf-stats {
+ --perfetto-orange: hsl(45, 100%, 48%);
+ --perfetto-red: hsl(6, 70%, 53%);
+ --stroke-color: hsl(217, 39%, 94%);
+ position: fixed;
+ bottom: 0;
+ color: var(--stroke-color);
+ font-family: monospace;
+ padding: 2px 0px;
+ z-index: 100;
+ button:hover {
+ color: var(--perfetto-red);
+ }
+ &[expanded=true] {
+ width: 600px;
+ background-color: rgba(27, 28, 29, 0.95);
+ button {
+ color: var(--perfetto-orange);
+ &:hover {
+ color: var(--perfetto-red);
+ }
+ }
+ }
+ &[expanded=false] {
+ width: var(--sidebar-width);
+ background-color: transparent;
+ }
+ i {
+ margin: 0px 24px;
+ font-size: 30px;
+ }
+ .perf-stats-content {
+ margin: 10px 24px;
+ & > section {
+ padding: 5px;
+ border-bottom: 1px solid var(--stroke-color);
+ }
+ button {
+ text-decoration: underline;
+ }
+ div {
+ margin: 2px 0px;
+ }
+ table, td, th {
+ border: 1px solid var(--stroke-color);
+ text-align: center;
+ padding: 4px;
+ margin: 4px 0px;
+ }
+ table {
+ border-collapse: collapse;
+ }
+ }
+}
diff --git a/ui/src/assets/topbar.scss b/ui/src/assets/topbar.scss
index 2ecd4ae..81a4a1b 100644
--- a/ui/src/assets/topbar.scss
+++ b/ui/src/assets/topbar.scss
@@ -180,4 +180,4 @@
right: -90%;
}
}
-}
\ No newline at end of file
+}
diff --git a/ui/src/controller/track_controller.ts b/ui/src/controller/track_controller.ts
index f38ee41..cbf444c 100644
--- a/ui/src/controller/track_controller.ts
+++ b/ui/src/controller/track_controller.ts
@@ -24,7 +24,8 @@
// TrackController is a base class overridden by track implementations (e.g.,
// sched slices, nestable slices, counters).
-export abstract class TrackController<Config = {}> extends Controller<'main'> {
+export abstract class TrackController<Config = {}, Data = {}> extends
+ Controller<'main'> {
readonly trackId: string;
readonly engine: Engine;
@@ -47,6 +48,10 @@
return this.trackState.config as Config;
}
+ publish(data: Data): void {
+ globals.publish('TrackData', {id: this.trackId, data});
+ }
+
run() {
const dataReq = this.trackState.dataReq;
if (dataReq === undefined) return;
diff --git a/ui/src/frontend/frontend_local_state.ts b/ui/src/frontend/frontend_local_state.ts
index c295c43..b9aa740 100644
--- a/ui/src/frontend/frontend_local_state.ts
+++ b/ui/src/frontend/frontend_local_state.ts
@@ -27,6 +27,7 @@
timeScale = new TimeScale(this.visibleWindowTime, [0, 0]);
private _visibleTimeLastUpdate = 0;
private pendingGlobalTimeUpdate?: TimeSpan;
+ perfDebug = false;
// TODO: there is some redundancy in the fact that both |visibleWindowTime|
// and a |timeScale| have a notion of time range. That should live in one
@@ -52,4 +53,9 @@
get visibleTimeLastUpdate() {
return this._visibleTimeLastUpdate;
}
+
+ togglePerfDebug() {
+ this.perfDebug = !this.perfDebug;
+ globals.rafScheduler.scheduleFullRedraw();
+ }
}
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 054df61..e370127 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -45,20 +45,24 @@
class Globals {
private _dispatch?: Dispatch = undefined;
private _state?: State = undefined;
- private _trackDataStore?: TrackDataStore = undefined;
- private _queryResults?: QueryResultsStore = undefined;
private _frontendLocalState?: FrontendLocalState = undefined;
private _rafScheduler?: RafScheduler = undefined;
+
+ // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
+ private _trackDataStore?: TrackDataStore = undefined;
+ private _queryResults?: QueryResultsStore = undefined;
private _overviewStore?: OverviewStore = undefined;
private _threadMap?: ThreadMap = undefined;
initialize(dispatch?: Dispatch) {
this._dispatch = dispatch;
this._state = createEmptyState();
- this._trackDataStore = new Map<string, {}>();
- this._queryResults = new Map<string, {}>();
this._frontendLocalState = new FrontendLocalState();
this._rafScheduler = new RafScheduler();
+
+ // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
+ this._trackDataStore = new Map<string, {}>();
+ this._queryResults = new Map<string, {}>();
this._overviewStore = new Map<string, QuantizedLoad[]>();
this._threadMap = new Map<number, ThreadDesc>();
}
@@ -75,6 +79,15 @@
return assertExists(this._dispatch);
}
+ get frontendLocalState() {
+ return assertExists(this._frontendLocalState);
+ }
+
+ get rafScheduler() {
+ return assertExists(this._rafScheduler);
+ }
+
+ // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
get overviewStore(): OverviewStore {
return assertExists(this._overviewStore);
}
@@ -87,14 +100,6 @@
return assertExists(this._queryResults);
}
- get frontendLocalState() {
- return assertExists(this._frontendLocalState);
- }
-
- get rafScheduler() {
- return assertExists(this._rafScheduler);
- }
-
get threads() {
return assertExists(this._threadMap);
}
@@ -102,11 +107,14 @@
resetForTesting() {
this._dispatch = undefined;
this._state = undefined;
- this._trackDataStore = undefined;
- this._queryResults = undefined;
this._frontendLocalState = undefined;
this._rafScheduler = undefined;
+
+ // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
+ this._trackDataStore = undefined;
+ this._queryResults = undefined;
this._overviewStore = undefined;
+ this._threadMap = undefined;
}
}
diff --git a/ui/src/frontend/pages.ts b/ui/src/frontend/pages.ts
index d258656..a964952 100644
--- a/ui/src/frontend/pages.ts
+++ b/ui/src/frontend/pages.ts
@@ -27,10 +27,37 @@
hash ? ['Permalink: ', m(`a[href=${url}]`, url)] : 'Uploading...');
}
-const Alerts: m.Component = {
+class Alerts implements m.ClassComponent {
view() {
return m('.alerts', renderPermalink());
- },
+ }
+}
+
+const TogglePerfDebugButton = {
+ view() {
+ return m(
+ '.perf-monitor-button',
+ m('button',
+ {
+ onclick: () => globals.frontendLocalState.togglePerfDebug(),
+ },
+ m('i.material-icons',
+ {
+ title: 'Toggle Perf Debug Mode',
+ },
+ 'assessment')));
+ }
+};
+
+const PerfStats: m.Component = {
+ view() {
+ const perfDebug = globals.frontendLocalState.perfDebug;
+ const children = [m(TogglePerfDebugButton)];
+ if (perfDebug) {
+ children.unshift(m('.perf-stats-content'));
+ }
+ return m(`.perf-stats[expanded=${perfDebug}]`, children);
+ }
};
/**
@@ -44,6 +71,7 @@
m(Topbar),
m(component),
m(Alerts),
+ m(PerfStats),
];
},
};
diff --git a/ui/src/frontend/panel_container.ts b/ui/src/frontend/panel_container.ts
index a3866bb..4fb18f5 100644
--- a/ui/src/frontend/panel_container.ts
+++ b/ui/src/frontend/panel_container.ts
@@ -17,7 +17,14 @@
import {assertExists, assertTrue} from '../base/logging';
import {globals} from './globals';
-import {isPanelVNode} from './panel';
+import {isPanelVNode, Panel, PanelSize} from './panel';
+import {
+ debugNow,
+ perfDebug,
+ perfDisplay,
+ RunningStatistics,
+ runningStatStr
+} from './perf';
/**
* If the panel container scrolls, the backing canvas height is
@@ -41,6 +48,13 @@
private totalPanelHeight = 0;
private canvasHeight = 0;
+ private panelPerfStats = new WeakMap<Panel, RunningStatistics>();
+ private perfStats = {
+ totalPanels: 0,
+ panelsOnCanvas: 0,
+ renderStats: new RunningStatistics(10),
+ };
+
// attrs received in the most recent mithril redraw.
private attrs?: Attrs;
@@ -56,6 +70,7 @@
vnode.attrs.doesScroll ? SCROLLING_CANVAS_OVERDRAW_FACTOR : 1;
this.canvasRedrawer = () => this.redrawCanvas();
globals.rafScheduler.addRedrawCallback(this.canvasRedrawer);
+ perfDisplay.addContainer(this);
}
oncreate(vnodeDom: m.CVnodeDOM<Attrs>) {
@@ -113,16 +128,21 @@
if (attrs.doesScroll) {
dom.parentElement!.removeEventListener('scroll', this.parentOnScroll);
}
+ perfDisplay.removeContainer(this);
}
view({attrs}: m.CVnode<Attrs>) {
// We receive a new vnode object with new attrs on every mithril redraw. We
// store the latest attrs so redrawCanvas can use it.
this.attrs = attrs;
+ const renderPanel = (panel: m.Vnode) => perfDebug() ?
+ m('.panel', panel, m('.debug-panel-border')) :
+ m('.panel', panel);
+
return m(
'.scroll-limiter',
m('canvas.main-canvas'),
- attrs.panels.map(panel => m('.panel', panel)));
+ attrs.panels.map(renderPanel));
}
onupdate(vnodeDom: m.CVnodeDOM<Attrs>) {
@@ -186,6 +206,7 @@
}
private redrawCanvas() {
+ const redrawStart = debugNow();
if (!this.ctx) return;
this.ctx.clearRect(0, 0, this.parentWidth, this.canvasHeight);
const canvasYStart = this.scrollTop - this.getCanvasOverdrawHeightPerSide();
@@ -193,6 +214,7 @@
let panelYStart = 0;
const panels = assertExists(this.attrs).panels;
assertTrue(panels.length === this.panelHeights.length);
+ let totalOnCanvas = 0;
for (let i = 0; i < panels.length; i++) {
const panel = panels[i];
const panelHeight = this.panelHeights[i];
@@ -203,6 +225,8 @@
continue;
}
+ totalOnCanvas++;
+
if (!isPanelVNode(panel)) {
throw Error('Vnode passed to panel container is not a panel');
}
@@ -213,10 +237,53 @@
const size = {width: this.parentWidth, height: panelHeight};
clipRect.rect(0, 0, size.width, size.height);
this.ctx.clip(clipRect);
+ const beforeRender = debugNow();
panel.state.renderCanvas(this.ctx, size, panel);
+ this.updatePanelStats(
+ i, panel.state, debugNow() - beforeRender, this.ctx, size);
this.ctx.restore();
panelYStart += panelHeight;
}
+ const redrawDur = debugNow() - redrawStart;
+ this.updatePerfStats(redrawDur, panels.length, totalOnCanvas);
+ }
+
+ private updatePanelStats(
+ panelIndex: number, panel: Panel, renderTime: number,
+ ctx: CanvasRenderingContext2D, size: PanelSize) {
+ if (!perfDebug()) return;
+ let renderStats = this.panelPerfStats.get(panel);
+ if (renderStats === undefined) {
+ renderStats = new RunningStatistics();
+ this.panelPerfStats.set(panel, renderStats);
+ }
+ renderStats.addValue(renderTime);
+
+ const statW = 300;
+ ctx.fillStyle = 'hsl(97, 100%, 96%)';
+ ctx.fillRect(size.width - statW, size.height - 20, statW, 20);
+ ctx.fillStyle = 'hsla(122, 77%, 22%)';
+ const statStr = `Panel ${panelIndex + 1} | ` + runningStatStr(renderStats);
+ ctx.fillText(statStr, size.width - statW, size.height - 10);
+ }
+
+ private updatePerfStats(
+ renderTime: number, totalPanels: number, panelsOnCanvas: number) {
+ if (!perfDebug()) return;
+ this.perfStats.renderStats.addValue(renderTime);
+ this.perfStats.totalPanels = totalPanels;
+ this.perfStats.panelsOnCanvas = panelsOnCanvas;
+ }
+
+ renderPerfStats(index: number) {
+ assertTrue(perfDebug());
+ return [m(
+ 'section',
+ m('div', `Panel Container ${index + 1}`),
+ m('div',
+ `${this.perfStats.totalPanels} panels, ` +
+ `${this.perfStats.panelsOnCanvas} on canvas.`),
+ m('div', runningStatStr(this.perfStats.renderStats)), )];
}
private getCanvasOverdrawHeightPerSide() {
diff --git a/ui/src/frontend/perf.ts b/ui/src/frontend/perf.ts
new file mode 100644
index 0000000..6e4093b
--- /dev/null
+++ b/ui/src/frontend/perf.ts
@@ -0,0 +1,123 @@
+
+// Copyright (C) 2018 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.
+
+import * as m from 'mithril';
+
+import {globals} from './globals';
+import {PanelContainer} from './panel_container';
+
+/**
+ * Shorthand for if globals perf debug mode is on.
+ */
+export const perfDebug = () => globals.frontendLocalState.perfDebug;
+
+/**
+ * Returns performance.now() if perfDebug is enabled, otherwise 0.
+ * This is needed because calling performance.now is generally expensive
+ * and should not be done for every frame.
+ */
+export const debugNow = () => perfDebug() ? performance.now() : 0;
+
+/**
+ * Returns execution time of |fn| if perf debug mode is on. Returns 0 otherwise.
+ */
+export function measure(fn: () => void): number {
+ const start = debugNow();
+ fn();
+ return debugNow() - start;
+}
+
+/**
+ * Stores statistics about samples, and keeps a fixed size buffer of most recent
+ * samples.
+ */
+export class RunningStatistics {
+ private _count = 0;
+ private _mean = 0;
+ private _lastValue = 0;
+
+ private buffer: number[] = [];
+
+ constructor(private _maxBufferSize = 10) {}
+
+ addValue(value: number) {
+ this._lastValue = value;
+ this.buffer.push(value);
+ if (this.buffer.length > this._maxBufferSize) {
+ this.buffer.shift();
+ }
+ this._mean = (this._mean * this._count + value) / (this._count + 1);
+ this._count++;
+ }
+
+ get mean() {
+ return this._mean;
+ }
+ get count() {
+ return this._count;
+ }
+ get bufferMean() {
+ return this.buffer.reduce((sum, v) => sum + v, 0) / this.buffer.length;
+ }
+ get bufferSize() {
+ return this.buffer.length;
+ }
+ get maxBufferSize() {
+ return this._maxBufferSize;
+ }
+ get last() {
+ return this._lastValue;
+ }
+}
+
+/**
+ * Returns a summary string representation of a RunningStatistics object.
+ */
+export function runningStatStr(stat: RunningStatistics) {
+ return `Last: ${stat.last.toFixed(2)}ms | ` +
+ `Avg: ${stat.mean.toFixed(2)}ms | ` +
+ `Avg${stat.maxBufferSize}: ${stat.bufferMean.toFixed(2)}ms`;
+}
+
+/**
+ * Globals singleton class that renders performance stats for the whole app.
+ */
+class PerfDisplay {
+ private containers: PanelContainer[] = [];
+ addContainer(container: PanelContainer) {
+ this.containers.push(container);
+ }
+
+ removeContainer(container: PanelContainer) {
+ const i = this.containers.indexOf(container);
+ this.containers.splice(i, 1);
+ }
+
+ renderPerfStats() {
+ if (!perfDebug()) return;
+ const perfDisplayEl = this.getPerfDisplayEl();
+ if (!perfDisplayEl) return;
+ m.render(perfDisplayEl, [
+ m('section', globals.rafScheduler.renderPerfStats()),
+ this.containers.map((c, i) => m('section', c.renderPerfStats(i)))
+ ]);
+ }
+
+ getPerfDisplayEl() {
+ return document.querySelector('.perf-stats-content');
+ }
+}
+
+export const perfDisplay = new PerfDisplay();
diff --git a/ui/src/frontend/raf_scheduler.ts b/ui/src/frontend/raf_scheduler.ts
index 577ccca..bfe2017 100644
--- a/ui/src/frontend/raf_scheduler.ts
+++ b/ui/src/frontend/raf_scheduler.ts
@@ -12,6 +12,37 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import * as m from 'mithril';
+
+import {assertTrue} from '../base/logging';
+
+import {
+ debugNow,
+ measure,
+ perfDebug,
+ perfDisplay,
+ RunningStatistics,
+ runningStatStr
+} from './perf';
+
+function statTableHeader() {
+ return m(
+ 'tr',
+ m('th', ''),
+ m('th', 'Last (ms)'),
+ m('th', 'Avg (ms)'),
+ m('th', 'Avg-10 (ms)'), );
+}
+
+function statTableRow(title: string, stat: RunningStatistics) {
+ return m(
+ 'tr',
+ m('td', title),
+ m('td', stat.last.toFixed(2)),
+ m('td', stat.mean.toFixed(2)),
+ m('td', stat.bufferMean.toFixed(2)), );
+}
+
export type ActionCallback = (nowMs: number) => void;
export type RedrawCallback = (nowMs: number) => void;
@@ -31,6 +62,14 @@
private requestedFullRedraw = false;
private isRedrawing = false;
+ private perfStats = {
+ rafActions: new RunningStatistics(),
+ rafCanvas: new RunningStatistics(),
+ rafDom: new RunningStatistics(),
+ rafTotal: new RunningStatistics(),
+ domRedraw: new RunningStatistics(),
+ };
+
start(cb: ActionCallback) {
this.actionCallbacks.add(cb);
this.maybeScheduleAnimationFrame();
@@ -61,11 +100,23 @@
this.maybeScheduleAnimationFrame(true);
}
+ syncDomRedraw(nowMs: number) {
+ const redrawStart = debugNow();
+ this._syncDomRedraw(nowMs);
+ if (perfDebug()) {
+ this.perfStats.domRedraw.addValue(debugNow() - redrawStart);
+ }
+ }
+
private syncCanvasRedraw(nowMs: number) {
+ const redrawStart = debugNow();
if (this.isRedrawing) return;
this.isRedrawing = true;
for (const redraw of this.canvasRedrawCallbacks) redraw(nowMs);
this.isRedrawing = false;
+ if (perfDebug()) {
+ this.perfStats.rafCanvas.addValue(debugNow() - redrawStart);
+ }
}
private maybeScheduleAnimationFrame(force = false) {
@@ -77,15 +128,64 @@
}
private onAnimationFrame(nowMs: number) {
+ const rafStart = debugNow();
this.hasScheduledNextFrame = false;
const doFullRedraw = this.requestedFullRedraw;
this.requestedFullRedraw = false;
- for (const action of this.actionCallbacks) action(nowMs);
- if (doFullRedraw) this._syncDomRedraw(nowMs);
- this.syncCanvasRedraw(nowMs);
+ const actionTime = measure(() => {
+ for (const action of this.actionCallbacks) action(nowMs);
+ });
+
+ const domTime = measure(() => {
+ if (doFullRedraw) this.syncDomRedraw(nowMs);
+ });
+ const canvasTime = measure(() => this.syncCanvasRedraw(nowMs));
+
+ const totalRafTime = debugNow() - rafStart;
+ this.updatePerfStats(actionTime, domTime, canvasTime, totalRafTime);
+ perfDisplay.renderPerfStats();
this.maybeScheduleAnimationFrame();
}
+
+ private updatePerfStats(
+ actionsTime: number, domTime: number, canvasTime: number,
+ totalRafTime: number) {
+ if (!perfDebug()) return;
+ this.perfStats.rafActions.addValue(actionsTime);
+ this.perfStats.rafDom.addValue(domTime);
+ this.perfStats.rafCanvas.addValue(canvasTime);
+ this.perfStats.rafTotal.addValue(totalRafTime);
+ }
+
+ renderPerfStats() {
+ assertTrue(perfDebug());
+ return m(
+ 'div',
+ m('div',
+ [
+ m('button',
+ {onclick: () => this.scheduleRedraw()},
+ 'Do Canvas Redraw'),
+ ' | ',
+ m('button',
+ {onclick: () => this.scheduleFullRedraw()},
+ 'Do Full Redraw'),
+ ]),
+ m('div',
+ 'Raf Timing ' +
+ '(Total may not add up due to imprecision)'),
+ m('table',
+ statTableHeader(),
+ statTableRow('Actions', this.perfStats.rafActions),
+ statTableRow('Dom', this.perfStats.rafDom),
+ statTableRow('Canvas', this.perfStats.rafCanvas),
+ statTableRow('Total', this.perfStats.rafTotal), ),
+ m('div',
+ 'Dom redraw: ' +
+ `Count: ${this.perfStats.domRedraw.count} | ` +
+ runningStatStr(this.perfStats.domRedraw)), );
+ }
}
diff --git a/ui/src/frontend/record_page.ts b/ui/src/frontend/record_page.ts
index 694c8f5..79d8417 100644
--- a/ui/src/frontend/record_page.ts
+++ b/ui/src/frontend/record_page.ts
@@ -29,8 +29,12 @@
}
}
-const CodeSample: m.Component<{text: string}, {}> = {
- view({attrs}) {
+interface CodeSampleAttrs {
+ text: string;
+}
+
+class CodeSample implements m.ClassComponent<CodeSampleAttrs> {
+ view({attrs}: m.CVnode<CodeSampleAttrs>) {
return m(
'.example-code',
m('code', attrs.text),
@@ -39,8 +43,8 @@
onclick: () => copyToClipboard(attrs.text),
},
'Copy to clipboard'), );
- },
-};
+ }
+}
export const RecordPage = createPage({
view() {
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index 15b9085..6f8e5d2 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -175,7 +175,7 @@
globals.dispatch(createPermalink());
}
-export const Sidebar: m.Component = {
+export class Sidebar implements m.ClassComponent {
view() {
const vdomSections = [];
for (const section of SECTIONS) {
@@ -206,5 +206,5 @@
m('header', 'Perfetto'),
m('input[type=file]', {onchange: onInputElementFileSelectionChanged}),
...vdomSections);
- },
-};
+ }
+}
diff --git a/ui/src/frontend/topbar.ts b/ui/src/frontend/topbar.ts
index a04d2c6..03b142a 100644
--- a/ui/src/frontend/topbar.ts
+++ b/ui/src/frontend/topbar.ts
@@ -89,13 +89,14 @@
}
-const Omnibox: m.Component = {
- oncreate(vnode) {
+class Omnibox implements m.ClassComponent {
+ oncreate(vnode: m.VnodeDOM) {
const txt = vnode.dom.querySelector('input') as HTMLInputElement;
txt.addEventListener('blur', clearOmniboxResults);
txt.addEventListener('keydown', onKeyDown);
txt.addEventListener('keyup', onKeyUp);
- },
+ }
+
view() {
const msgTTL = globals.state.status.timestamp + 3 - Date.now() / 1e3;
let enginesAreBusy = false;
@@ -131,10 +132,10 @@
`.omnibox${commandMode ? '.command-mode' : ''}`,
m(`input[placeholder=${placeholder[mode]}]`),
m('.omnibox-results', results));
- },
-};
+ }
+}
-export const Topbar: m.Component = {
+export class Topbar implements m.ClassComponent {
view() {
const progBar = [];
const engine: EngineConfig = globals.state.engines['0'];
@@ -142,7 +143,6 @@
(engine !== undefined && !engine.ready)) {
progBar.push(m('.progress'));
}
-
return m('.topbar', m(Omnibox), ...progBar);
- },
-};
+ }
+}
diff --git a/ui/src/frontend/track.ts b/ui/src/frontend/track.ts
index 64dc3b3..c543ad8 100644
--- a/ui/src/frontend/track.ts
+++ b/ui/src/frontend/track.ts
@@ -13,6 +13,7 @@
// limitations under the License.
import {TrackState} from '../common/state';
+import {globals} from './globals';
/**
* This interface forces track implementations to have some static properties.
@@ -32,7 +33,7 @@
/**
* The abstract class that needs to be implemented by all tracks.
*/
-export abstract class Track<Config = {}> {
+export abstract class Track<Config = {}, Data = {}> {
/**
* Receive data published by the TrackController of this track.
*/
@@ -43,6 +44,10 @@
return this.trackState.config as Config;
}
+ data(): Data {
+ return globals.trackDataStore.get(this.trackState.id) as Data;
+ }
+
getHeight(): number {
return 40;
}
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index ccf6041..a12024d 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -32,8 +32,11 @@
return globals.state.pinnedTracks.indexOf(id) !== -1;
}
-const TrackShell = {
- view({attrs}) {
+interface TrackShellAttrs {
+ trackState: TrackState;
+}
+class TrackShell implements m.ClassComponent<TrackShellAttrs> {
+ view({attrs}: m.CVnode<TrackShellAttrs>) {
return m(
'.track-shell',
m('h1', attrs.trackState.name),
@@ -49,11 +52,14 @@
action: toggleTrackPinned(attrs.trackState.id),
i: isPinned(attrs.trackState.id) ? 'star' : 'star_border',
}));
- },
-} as m.Component<{trackState: TrackState}>;
+ }
+}
-const TrackContent = {
- view({attrs}) {
+interface TrackContentAttrs {
+ track: Track;
+}
+class TrackContent implements m.ClassComponent<TrackContentAttrs> {
+ view({attrs}: m.CVnode<TrackContentAttrs>) {
return m('.track-content', {
onmousemove: (e: MouseEvent) => {
attrs.track.onMouseMove({x: e.layerX, y: e.layerY});
@@ -65,19 +71,27 @@
},
}, );
}
-} as m.Component<{track: Track}>;
+}
-const TrackComponent = {
- view({attrs}) {
+interface TrackComponentAttrs {
+ trackState: TrackState;
+ track: Track;
+}
+class TrackComponent implements m.ClassComponent<TrackComponentAttrs> {
+ view({attrs}: m.CVnode<TrackComponentAttrs>) {
return m('.track', [
m(TrackShell, {trackState: attrs.trackState}),
m(TrackContent, {track: attrs.track})
]);
}
-} as m.Component<{trackState: TrackState, track: Track}>;
+}
-const TrackButton = {
- view({attrs}) {
+interface TrackButtonAttrs {
+ action: Action;
+ i: string;
+}
+class TrackButton implements m.ClassComponent<TrackButtonAttrs> {
+ view({attrs}: m.CVnode<TrackButtonAttrs>) {
return m(
'i.material-icons.track-button',
{
@@ -85,11 +99,7 @@
},
attrs.i);
}
-} as m.Component<{
- action: Action,
- i: string,
-},
- {}>;
+}
interface TrackPanelAttrs {
id: string;
@@ -121,6 +131,7 @@
}
renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
+ ctx.save();
ctx.translate(TRACK_SHELL_WIDTH, 0);
drawGridLines(
ctx,
@@ -129,5 +140,6 @@
size.height);
this.track.renderCanvas(ctx);
+ ctx.restore();
}
}
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 94ee184..e801992 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -30,7 +30,7 @@
const MAX_ZOOM_SPAN_SEC = 1e-4; // 0.1 ms.
-const QueryTable: m.Component<{}, {}> = {
+class QueryTable implements m.ClassComponent {
view() {
const resp = globals.queryResults.get('command') as QueryResponse;
if (resp === undefined) {
@@ -58,25 +58,23 @@
resp.error ?
m('.query-error', `SQL error: ${resp.error}`) :
m('table.query-table', m('thead', header), m('tbody', rows)));
- },
-};
+ }
+}
/**
* Top-most level component for the viewer page. Holds tracks, brush timeline,
* panels, and everything else that's part of the main trace viewer page.
*/
-const TraceViewer = {
- oninit() {
- this.width = 0;
- },
+class TraceViewer implements m.ClassComponent {
+ private onResize: () => void = () => {};
+ private zoomContent?: PanAndZoomHandler;
- oncreate(vnode) {
+ oncreate(vnode: m.CVnodeDOM) {
const frontendLocalState = globals.frontendLocalState;
const updateDimensions = () => {
const rect = vnode.dom.getBoundingClientRect();
- this.width = rect.width;
frontendLocalState.timeScale.setLimitsPx(
- 0, this.width - TRACK_SHELL_WIDTH);
+ 0, rect.width - TRACK_SHELL_WIDTH);
};
updateDimensions();
@@ -124,12 +122,12 @@
new TimeSpan(newStartSec, newEndSec));
}
});
- },
+ }
onremove() {
window.removeEventListener('resize', this.onResize);
- this.zoomContent.shutdown();
- },
+ if (this.zoomContent) this.zoomContent.shutdown();
+ }
view() {
const scrollingPanels = globals.state.scrollingTracks.length > 0 ?
@@ -158,15 +156,8 @@
doesScroll: true,
panels: scrollingPanels,
}))));
- },
-
-} as m.Component<{}, {
- onResize: () => void,
- width: number,
- zoomContent: PanAndZoomHandler,
- overviewQueryExecuted: boolean,
- overviewQueryResponse: QueryResponse,
-}>;
+ }
+}
export const ViewerPage = createPage({
view() {
diff --git a/ui/src/tracks/chrome_slices/common.ts b/ui/src/tracks/chrome_slices/common.ts
index b907735..1256dc7 100644
--- a/ui/src/tracks/chrome_slices/common.ts
+++ b/ui/src/tracks/chrome_slices/common.ts
@@ -14,13 +14,13 @@
export const SLICE_TRACK_KIND = 'ChromeSliceTrack';
-export interface ChromeSliceTrackConfig {
+export interface Config {
maxDepth: number;
upid: number;
utid: number;
}
-export interface ChromeSliceTrackData {
+export interface Data {
start: number;
end: number;
resolution: number;
diff --git a/ui/src/tracks/chrome_slices/controller.ts b/ui/src/tracks/chrome_slices/controller.ts
index 7c0c814..186491a 100644
--- a/ui/src/tracks/chrome_slices/controller.ts
+++ b/ui/src/tracks/chrome_slices/controller.ts
@@ -13,20 +13,14 @@
// limitations under the License.
import {fromNs} from '../../common/time';
-import {globals} from '../../controller/globals';
import {
TrackController,
trackControllerRegistry
} from '../../controller/track_controller';
-import {
- ChromeSliceTrackConfig,
- ChromeSliceTrackData,
- SLICE_TRACK_KIND
-} from './common';
+import {Config, Data, SLICE_TRACK_KIND} from './common';
-class ChromeSliceTrackController extends
- TrackController<ChromeSliceTrackConfig> {
+class ChromeSliceTrackController extends TrackController<Config, Data> {
static readonly kind = SLICE_TRACK_KIND;
private busy = false;
@@ -55,7 +49,7 @@
const numRows = +rawResult.numRecords;
- const slices: ChromeSliceTrackData = {
+ const slices: Data = {
start,
end,
resolution,
@@ -89,7 +83,7 @@
if (numRows === LIMIT) {
slices.end = slices.ends[slices.ends.length - 1];
}
- globals.publish('TrackData', {id: this.trackId, data: slices});
+ this.publish(slices);
});
}
}
diff --git a/ui/src/tracks/chrome_slices/frontend.ts b/ui/src/tracks/chrome_slices/frontend.ts
index 6ff9499..aba1e7f 100644
--- a/ui/src/tracks/chrome_slices/frontend.ts
+++ b/ui/src/tracks/chrome_slices/frontend.ts
@@ -18,11 +18,7 @@
import {Track} from '../../frontend/track';
import {trackRegistry} from '../../frontend/track_registry';
-import {
- ChromeSliceTrackConfig,
- ChromeSliceTrackData,
- SLICE_TRACK_KIND,
-} from './common';
+import {Config, Data, SLICE_TRACK_KIND} from './common';
const SLICE_HEIGHT = 30;
const TRACK_PADDING = 5;
@@ -42,7 +38,7 @@
return Math.pow(10, Math.floor(Math.log10(resolution)));
}
-class ChromeSliceTrack extends Track<ChromeSliceTrackConfig> {
+class ChromeSliceTrack extends Track<Config, Data> {
static readonly kind = SLICE_TRACK_KIND;
static create(trackState: TrackState): ChromeSliceTrack {
return new ChromeSliceTrack(trackState);
@@ -69,27 +65,27 @@
// TODO: fonts and colors should come from the CSS and not hardcoded here.
const {timeScale, visibleWindowTime} = globals.frontendLocalState;
- const trackData = this.trackData;
+ const data = this.data();
- // If there aren't enough cached slices data in |trackData| request more to
+ // If there aren't enough cached slices data in |data| request more to
// the controller.
- const inRange = trackData !== undefined &&
- (visibleWindowTime.start >= trackData.start &&
- visibleWindowTime.end <= trackData.end);
- if (!inRange || trackData.resolution > getCurResolution()) {
+ const inRange = data !== undefined &&
+ (visibleWindowTime.start >= data.start &&
+ visibleWindowTime.end <= data.end);
+ if (!inRange || data.resolution > getCurResolution()) {
if (!this.reqPending) {
this.reqPending = true;
setTimeout(() => this.reqDataDeferred(), 50);
}
- if (trackData === undefined) return; // Can't possibly draw anything.
+ if (data === undefined) return; // Can't possibly draw anything.
}
// If the cached trace slices don't fully cover the visible time range,
// show a gray rectangle with a "Loading..." label.
ctx.font = '12px Google Sans';
- if (trackData.start > visibleWindowTime.start) {
+ if (data.start > visibleWindowTime.start) {
const rectWidth =
- timeScale.timeToPx(Math.min(trackData.start, visibleWindowTime.end));
+ timeScale.timeToPx(Math.min(data.start, visibleWindowTime.end));
ctx.fillStyle = '#eee';
ctx.fillRect(0, TRACK_PADDING, rectWidth, SLICE_HEIGHT);
ctx.fillStyle = '#666';
@@ -99,9 +95,9 @@
TRACK_PADDING + SLICE_HEIGHT / 2,
rectWidth);
}
- if (trackData.end < visibleWindowTime.end) {
+ if (data.end < visibleWindowTime.end) {
const rectX =
- timeScale.timeToPx(Math.max(trackData.end, visibleWindowTime.start));
+ timeScale.timeToPx(Math.max(data.end, visibleWindowTime.start));
const rectWidth = timeScale.timeToPx(visibleWindowTime.end) - rectX;
ctx.fillStyle = '#eee';
ctx.fillRect(rectX, TRACK_PADDING, rectWidth, SLICE_HEIGHT);
@@ -120,13 +116,13 @@
const charWidth = ctx.measureText('abcdefghij').width / 10;
const pxEnd = timeScale.timeToPx(visibleWindowTime.end);
- for (let i = 0; i < trackData.starts.length; i++) {
- const tStart = trackData.starts[i];
- const tEnd = trackData.ends[i];
- const depth = trackData.depths[i];
- const cat = trackData.strings[trackData.categories[i]];
- const titleId = trackData.titles[i];
- const title = trackData.strings[titleId];
+ for (let i = 0; i < data.starts.length; i++) {
+ const tStart = data.starts[i];
+ const tEnd = data.ends[i];
+ const depth = data.depths[i];
+ const cat = data.strings[data.categories[i]];
+ const titleId = data.titles[i];
+ const title = data.strings[titleId];
if (tEnd <= visibleWindowTime.start || tStart >= visibleWindowTime.end) {
continue;
}
@@ -162,18 +158,18 @@
}
onMouseMove({x, y}: {x: number, y: number}) {
- const trackData = this.trackData;
+ const data = this.data();
this.hoveredTitleId = -1;
- if (trackData === undefined) return;
+ if (data === undefined) return;
const {timeScale} = globals.frontendLocalState;
if (y < TRACK_PADDING) return;
const t = timeScale.pxToTime(x);
const depth = Math.floor(y / SLICE_HEIGHT);
- for (let i = 0; i < trackData.starts.length; i++) {
- const tStart = trackData.starts[i];
- const tEnd = trackData.ends[i];
- const titleId = trackData.titles[i];
- if (tStart <= t && t <= tEnd && depth === trackData.depths[i]) {
+ for (let i = 0; i < data.starts.length; i++) {
+ const tStart = data.starts[i];
+ const tEnd = data.ends[i];
+ const titleId = data.titles[i];
+ if (tStart <= t && t <= tEnd && depth === data.depths[i]) {
this.hoveredTitleId = titleId;
break;
}
@@ -187,11 +183,6 @@
getHeight() {
return SLICE_HEIGHT * (this.config.maxDepth + 1) + 2 * TRACK_PADDING;
}
-
- private get trackData(): ChromeSliceTrackData {
- return globals.trackDataStore.get(this.trackState.id) as
- ChromeSliceTrackData;
- }
}
trackRegistry.register(ChromeSliceTrack);
diff --git a/ui/src/tracks/cpu_slices/common.ts b/ui/src/tracks/cpu_slices/common.ts
index c6802b4..38844f1 100644
--- a/ui/src/tracks/cpu_slices/common.ts
+++ b/ui/src/tracks/cpu_slices/common.ts
@@ -14,7 +14,7 @@
export const CPU_SLICE_TRACK_KIND = 'CpuSliceTrack';
-export interface CpuSliceTrackData {
+export interface Data {
start: number;
end: number;
resolution: number;
@@ -25,4 +25,4 @@
utids: Uint32Array;
}
-export interface CpuSliceTrackConfig { cpu: number; }
+export interface Config { cpu: number; }
diff --git a/ui/src/tracks/cpu_slices/controller.ts b/ui/src/tracks/cpu_slices/controller.ts
index 2519caa..17a1b0f 100644
--- a/ui/src/tracks/cpu_slices/controller.ts
+++ b/ui/src/tracks/cpu_slices/controller.ts
@@ -13,19 +13,14 @@
// limitations under the License.
import {fromNs} from '../../common/time';
-import {globals} from '../../controller/globals';
import {
TrackController,
trackControllerRegistry
} from '../../controller/track_controller';
-import {
- CPU_SLICE_TRACK_KIND,
- CpuSliceTrackConfig,
- CpuSliceTrackData
-} from './common';
+import {Config, CPU_SLICE_TRACK_KIND, Data} from './common';
-class CpuSliceTrackController extends TrackController<CpuSliceTrackConfig> {
+class CpuSliceTrackController extends TrackController<Config, Data> {
static readonly kind = CPU_SLICE_TRACK_KIND;
private busy = false;
@@ -50,7 +45,7 @@
}
const numRows = +rawResult.numRecords;
- const slices: CpuSliceTrackData = {
+ const slices: Data = {
start,
end,
resolution,
@@ -69,7 +64,7 @@
if (numRows === LIMIT) {
slices.end = slices.ends[slices.ends.length - 1];
}
- globals.publish('TrackData', {id: this.trackId, data: slices});
+ this.publish(slices);
});
}
}
diff --git a/ui/src/tracks/cpu_slices/frontend.ts b/ui/src/tracks/cpu_slices/frontend.ts
index cdfb1c6..34b84d5 100644
--- a/ui/src/tracks/cpu_slices/frontend.ts
+++ b/ui/src/tracks/cpu_slices/frontend.ts
@@ -19,11 +19,7 @@
import {Track} from '../../frontend/track';
import {trackRegistry} from '../../frontend/track_registry';
-import {
- CPU_SLICE_TRACK_KIND,
- CpuSliceTrackConfig,
- CpuSliceTrackData
-} from './common';
+import {Config, CPU_SLICE_TRACK_KIND, Data} from './common';
const MARGIN_TOP = 5;
const RECT_HEIGHT = 30;
@@ -50,7 +46,7 @@
return Math.pow(10, Math.floor(Math.log10(resolution)));
}
-class CpuSliceTrack extends Track<CpuSliceTrackConfig> {
+class CpuSliceTrack extends Track<Config, Data> {
static readonly kind = CPU_SLICE_TRACK_KIND;
static create(trackState: TrackState): CpuSliceTrack {
return new CpuSliceTrack(trackState);
@@ -78,19 +74,19 @@
// TODO: fonts and colors should come from the CSS and not hardcoded here.
const {timeScale, visibleWindowTime} = globals.frontendLocalState;
- const trackData = this.trackData;
+ const data = this.data();
- // If there aren't enough cached slices data in |trackData| request more to
+ // If there aren't enough cached slices data in |data| request more to
// the controller.
- const inRange = trackData !== undefined &&
- (visibleWindowTime.start >= trackData.start &&
- visibleWindowTime.end <= trackData.end);
- if (!inRange || trackData.resolution > getCurResolution()) {
+ const inRange = data !== undefined &&
+ (visibleWindowTime.start >= data.start &&
+ visibleWindowTime.end <= data.end);
+ if (!inRange || data.resolution > getCurResolution()) {
if (!this.reqPending) {
this.reqPending = true;
setTimeout(() => this.reqDataDeferred(), 50);
}
- if (trackData === undefined) return; // Can't possibly draw anything.
+ if (data === undefined) return; // Can't possibly draw anything.
}
ctx.textAlign = 'center';
ctx.font = '12px Google Sans';
@@ -103,18 +99,18 @@
// If the cached trace slices don't fully cover the visible time range,
// show a gray rectangle with a "Loading..." label.
ctx.font = '12px Google Sans';
- if (trackData.start > visibleWindowTime.start) {
+ if (data.start > visibleWindowTime.start) {
const rectWidth =
- timeScale.timeToPx(Math.min(trackData.start, visibleWindowTime.end));
+ timeScale.timeToPx(Math.min(data.start, visibleWindowTime.end));
ctx.fillStyle = '#eee';
ctx.fillRect(0, MARGIN_TOP, rectWidth, RECT_HEIGHT);
ctx.fillStyle = '#666';
ctx.fillText(
'loading...', rectWidth / 2, MARGIN_TOP + RECT_HEIGHT / 2, rectWidth);
}
- if (trackData.end < visibleWindowTime.end) {
+ if (data.end < visibleWindowTime.end) {
const rectX =
- timeScale.timeToPx(Math.max(trackData.end, visibleWindowTime.start));
+ timeScale.timeToPx(Math.max(data.end, visibleWindowTime.start));
const rectWidth = timeScale.timeToPx(visibleWindowTime.end) - rectX;
ctx.fillStyle = '#eee';
ctx.fillRect(rectX, MARGIN_TOP, rectWidth, RECT_HEIGHT);
@@ -126,12 +122,12 @@
rectWidth);
}
- assertTrue(trackData.starts.length === trackData.ends.length);
- assertTrue(trackData.starts.length === trackData.utids.length);
- for (let i = 0; i < trackData.starts.length; i++) {
- const tStart = trackData.starts[i];
- const tEnd = trackData.ends[i];
- const utid = trackData.utids[i];
+ assertTrue(data.starts.length === data.ends.length);
+ assertTrue(data.starts.length === data.utids.length);
+ for (let i = 0; i < data.starts.length; i++) {
+ const tStart = data.starts[i];
+ const tEnd = data.ends[i];
+ const utid = data.utids[i];
if (tEnd <= visibleWindowTime.start || tStart >= visibleWindowTime.end) {
continue;
}
@@ -189,9 +185,9 @@
}
onMouseMove({x, y}: {x: number, y: number}) {
- const trackData = this.trackData;
+ const data = this.data();
this.mouseXpos = x;
- if (trackData === undefined) return;
+ if (data === undefined) return;
const {timeScale} = globals.frontendLocalState;
if (y < MARGIN_TOP || y > MARGIN_TOP + RECT_HEIGHT) {
this.hoveredUtid = -1;
@@ -200,10 +196,10 @@
const t = timeScale.pxToTime(x);
this.hoveredUtid = -1;
- for (let i = 0; i < trackData.starts.length; i++) {
- const tStart = trackData.starts[i];
- const tEnd = trackData.ends[i];
- const utid = trackData.utids[i];
+ for (let i = 0; i < data.starts.length; i++) {
+ const tStart = data.starts[i];
+ const tEnd = data.ends[i];
+ const utid = data.utids[i];
if (tStart <= t && t <= tEnd) {
this.hoveredUtid = utid;
break;
@@ -215,10 +211,6 @@
this.hoveredUtid = -1;
this.mouseXpos = 0;
}
-
- private get trackData(): CpuSliceTrackData {
- return globals.trackDataStore.get(this.trackState.id) as CpuSliceTrackData;
- }
}
trackRegistry.register(CpuSliceTrack);