blob: fd12b15993a1a8b45118792d01274df00630274a [file] [log] [blame]
Primiano Tuccie36ca632018-08-21 14:32:23 +02001// Copyright (C) 2018 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import '../tracks/all_controller';
16
Deepanjan Roy32ad0d72018-10-18 14:50:16 -040017import * as uuidv4 from 'uuid/v4';
18
Primiano Tuccie36ca632018-08-21 14:32:23 +020019import {assertExists, assertTrue} from '../base/logging';
20import {
Hector Dearman4d8d73d2018-10-04 13:16:29 +010021 Actions,
Hector Dearman5ae82472018-10-03 08:30:35 +010022 DeferredAction,
Primiano Tuccie36ca632018-08-21 14:32:23 +020023} from '../common/actions';
Deepanjan Roy32ad0d72018-10-18 14:50:16 -040024import {SCROLLING_TRACK_GROUP} from '../common/state';
Primiano Tuccie36ca632018-08-21 14:32:23 +020025import {TimeSpan} from '../common/time';
26import {QuantizedLoad, ThreadDesc} from '../frontend/globals';
27import {SLICE_TRACK_KIND} from '../tracks/chrome_slices/common';
28import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices/common';
Deepanjan Roy32ad0d72018-10-18 14:50:16 -040029import {PROCESS_SUMMARY_TRACK} from '../tracks/process_summary/common';
Primiano Tuccie36ca632018-08-21 14:32:23 +020030
31import {Child, Children, Controller} from './controller';
32import {Engine} from './engine';
33import {globals} from './globals';
34import {QueryController, QueryControllerArgs} from './query_controller';
35import {TrackControllerArgs, trackControllerRegistry} from './track_controller';
36
37type States = 'init'|'loading_trace'|'ready';
38
39
Primiano Tucci7e330292018-08-24 19:10:52 +020040declare interface FileReaderSync { readAsArrayBuffer(blob: Blob): ArrayBuffer; }
41
42declare var FileReaderSync:
43 {prototype: FileReaderSync; new (): FileReaderSync;};
44
Primiano Tuccie36ca632018-08-21 14:32:23 +020045// TraceController handles handshakes with the frontend for everything that
46// concerns a single trace. It owns the WASM trace processor engine, handles
47// tracks data and SQL queries. There is one TraceController instance for each
48// trace opened in the UI (for now only one trace is supported).
49export class TraceController extends Controller<States> {
50 private readonly engineId: string;
51 private engine?: Engine;
52
53 constructor(engineId: string) {
54 super('init');
55 this.engineId = engineId;
56 }
57
Primiano Tucci7e330292018-08-24 19:10:52 +020058 onDestroy() {
59 if (this.engine !== undefined) globals.destroyEngine(this.engine.id);
60 }
61
Primiano Tuccie36ca632018-08-21 14:32:23 +020062 run() {
63 const engineCfg = assertExists(globals.state.engines[this.engineId]);
64 switch (this.state) {
65 case 'init':
Hector Dearman4d8d73d2018-10-04 13:16:29 +010066 globals.dispatch(Actions.setEngineReady({
67 engineId: this.engineId,
68 ready: false,
69 }));
Primiano Tuccie36ca632018-08-21 14:32:23 +020070 this.loadTrace().then(() => {
Hector Dearman4d8d73d2018-10-04 13:16:29 +010071 globals.dispatch(Actions.setEngineReady({
72 engineId: this.engineId,
73 ready: true,
74 }));
Primiano Tuccie36ca632018-08-21 14:32:23 +020075 });
Hector Dearman4d8d73d2018-10-04 13:16:29 +010076 this.updateStatus('Opening trace');
Primiano Tuccie36ca632018-08-21 14:32:23 +020077 this.setState('loading_trace');
78 break;
79
80 case 'loading_trace':
81 // Stay in this state until loadTrace() returns and marks the engine as
82 // ready.
83 if (this.engine === undefined || !engineCfg.ready) return;
84 this.setState('ready');
85 break;
86
87 case 'ready':
88 // At this point we are ready to serve queries and handle tracks.
89 const engine = assertExists(this.engine);
90 assertTrue(engineCfg.ready);
91 const childControllers: Children = [];
92
93 // Create a TrackController for each track.
94 for (const trackId of Object.keys(globals.state.tracks)) {
95 const trackCfg = globals.state.tracks[trackId];
96 if (trackCfg.engineId !== this.engineId) continue;
97 if (!trackControllerRegistry.has(trackCfg.kind)) continue;
98 const trackCtlFactory = trackControllerRegistry.get(trackCfg.kind);
99 const trackArgs: TrackControllerArgs = {trackId, engine};
100 childControllers.push(Child(trackId, trackCtlFactory, trackArgs));
101 }
102
103 // Create a QueryController for each query.
104 for (const queryId of Object.keys(globals.state.queries)) {
105 const queryArgs: QueryControllerArgs = {queryId, engine};
106 childControllers.push(Child(queryId, QueryController, queryArgs));
107 }
108
109 return childControllers;
110
111 default:
112 throw new Error(`unknown state ${this.state}`);
113 }
114 return;
115 }
116
117 private async loadTrace() {
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100118 this.updateStatus('Creating trace processor');
Primiano Tuccie36ca632018-08-21 14:32:23 +0200119 const engineCfg = assertExists(globals.state.engines[this.engineId]);
Hector Dearman03159542018-09-19 18:37:00 +0100120 this.engine = globals.createEngine();
Primiano Tucci7e330292018-08-24 19:10:52 +0200121
122 const statusHeader = 'Opening trace';
Primiano Tuccie36ca632018-08-21 14:32:23 +0200123 if (engineCfg.source instanceof File) {
Primiano Tucci7e330292018-08-24 19:10:52 +0200124 const blob = engineCfg.source as Blob;
125 const reader = new FileReaderSync();
126 const SLICE_SIZE = 1024 * 1024;
127 for (let off = 0; off < blob.size; off += SLICE_SIZE) {
128 const slice = blob.slice(off, off + SLICE_SIZE);
129 const arrBuf = reader.readAsArrayBuffer(slice);
130 await this.engine.parse(new Uint8Array(arrBuf));
131 const progress = Math.round((off + slice.size) / blob.size * 100);
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100132 this.updateStatus(`${statusHeader} ${progress} %`);
Primiano Tucci7e330292018-08-24 19:10:52 +0200133 }
Primiano Tuccie36ca632018-08-21 14:32:23 +0200134 } else {
Primiano Tucci7e330292018-08-24 19:10:52 +0200135 const resp = await fetch(engineCfg.source);
136 if (resp.status !== 200) {
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100137 this.updateStatus(`HTTP error ${resp.status}`);
Primiano Tucci7e330292018-08-24 19:10:52 +0200138 throw new Error(`fetch() failed with HTTP error ${resp.status}`);
139 }
140 // tslint:disable-next-line no-any
141 const rd = (resp.body as any).getReader() as ReadableStreamReader;
142 const tStartMs = performance.now();
143 let tLastUpdateMs = 0;
144 for (let off = 0;;) {
145 const readRes = await rd.read() as {value: Uint8Array, done: boolean};
146 if (readRes.value !== undefined) {
147 off += readRes.value.length;
148 await this.engine.parse(readRes.value);
149 }
150 // For traces loaded from the network there doesn't seem to be a
151 // reliable way to compute the %. The content-length exposed by GCS is
152 // before compression (which is handled transparently by the browser).
153 const nowMs = performance.now();
154 if (nowMs - tLastUpdateMs > 100) {
155 tLastUpdateMs = nowMs;
156 const mb = off / 1e6;
157 const tElapsed = (nowMs - tStartMs) / 1e3;
158 let status = `${statusHeader} ${mb.toFixed(1)} MB `;
159 status += `(${(mb / tElapsed).toFixed(1)} MB/s)`;
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100160 this.updateStatus(status);
Primiano Tucci7e330292018-08-24 19:10:52 +0200161 }
162 if (readRes.done) break;
163 }
Primiano Tuccie36ca632018-08-21 14:32:23 +0200164 }
165
Hector Dearman1d289212018-09-05 14:05:29 +0100166 await this.engine.notifyEof();
167
Primiano Tuccie36ca632018-08-21 14:32:23 +0200168 const traceTime = await this.engine.getTraceTimeBounds();
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100169 const traceTimeState = {
170 startSec: traceTime.start,
171 endSec: traceTime.end,
172 lastUpdate: Date.now() / 1000,
173 };
Primiano Tuccie36ca632018-08-21 14:32:23 +0200174 const actions = [
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100175 Actions.setTraceTime(traceTimeState),
176 Actions.navigate({route: '/viewer'}),
Primiano Tuccie36ca632018-08-21 14:32:23 +0200177 ];
178
179 if (globals.state.visibleTraceTime.lastUpdate === 0) {
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100180 actions.push(Actions.setVisibleTraceTime(traceTimeState));
Primiano Tuccie36ca632018-08-21 14:32:23 +0200181 }
182
183 globals.dispatchMultiple(actions);
184
Hector Dearmane702a682018-10-16 16:29:28 +0100185 {
186 // When we reload from a permalink don't create extra tracks:
187 const {pinnedTracks, scrollingTracks} = globals.state;
Hector Dearmanbb55c9e2018-10-17 16:08:39 +0100188 if (!pinnedTracks.length && !scrollingTracks.length) {
Hector Dearmane702a682018-10-16 16:29:28 +0100189 await this.listTracks();
190 }
191 }
192
Primiano Tuccie36ca632018-08-21 14:32:23 +0200193 await this.listThreads();
194 await this.loadTimelineOverview(traceTime);
195 }
196
197 private async listTracks() {
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100198 this.updateStatus('Loading tracks');
Hector Dearmane702a682018-10-16 16:29:28 +0100199
Primiano Tuccie36ca632018-08-21 14:32:23 +0200200 const engine = assertExists<Engine>(this.engine);
Hector Dearman5ae82472018-10-03 08:30:35 +0100201 const addToTrackActions: DeferredAction[] = [];
Primiano Tuccie36ca632018-08-21 14:32:23 +0200202 const numCpus = await engine.getNumberOfCpus();
Hector Dearmane27a9742018-10-12 09:31:34 +0100203
204 // TODO(hjd): Move this code out of TraceController.
205 for (const counterName of ['VSYNC-sf', 'VSYNC-app']) {
206 const hasVsync =
207 !!(await engine.query(
208 `select ts from counters where name like "${
209 counterName
210 }" limit 1`))
211 .numRecords;
212 if (!hasVsync) continue;
213 addToTrackActions.push(Actions.addTrack({
214 engineId: this.engineId,
215 kind: 'VsyncTrack',
216 name: `${counterName}`,
217 config: {
218 counterName,
219 }
220 }));
221 }
222
Primiano Tuccie36ca632018-08-21 14:32:23 +0200223 for (let cpu = 0; cpu < numCpus; cpu++) {
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100224 addToTrackActions.push(Actions.addTrack({
225 engineId: this.engineId,
226 kind: CPU_SLICE_TRACK_KIND,
227 name: `Cpu ${cpu}`,
Deepanjan Roy32ad0d72018-10-18 14:50:16 -0400228 trackGroup: SCROLLING_TRACK_GROUP,
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100229 config: {
230 cpu,
231 }
232 }));
Primiano Tuccie36ca632018-08-21 14:32:23 +0200233 }
234
Deepanjan Roy32ad0d72018-10-18 14:50:16 -0400235 // Local experiments shows getting maxDepth separately is ~2x faster than
236 // joining with threads and processes.
237 const maxDepthQuery =
238 await engine.query('select utid, max(depth) from slices group by utid');
239
240 const utidToMaxDepth = new Map<number, number>();
241 for (let i = 0; i < maxDepthQuery.numRecords; i++) {
242 const utid = maxDepthQuery.columns[0].longValues![i] as number;
243 const maxDepth = maxDepthQuery.columns[1].longValues![i] as number;
244 utidToMaxDepth.set(utid, maxDepth);
245 }
246
247 const threadQuery = await engine.query(
248 'select utid, tid, upid, pid, thread.name, process.name ' +
249 'from thread inner join process using(upid)');
250
251 const upidToUuid = new Map<number, string>();
252 const addSummaryTrackActions: DeferredAction[] = [];
253 const addTrackGroupActions: DeferredAction[] = [];
Primiano Tuccie36ca632018-08-21 14:32:23 +0200254 for (let i = 0; i < threadQuery.numRecords; i++) {
Deepanjan Roy32ad0d72018-10-18 14:50:16 -0400255 const utid = threadQuery.columns[0].longValues![i] as number;
256
257 const maxDepth = utidToMaxDepth.get(utid);
258 if (maxDepth === undefined) {
259 // This thread does not have stackable slices.
260 continue;
261 }
262
263 const tid = threadQuery.columns[1].longValues![i] as number;
264 const upid = threadQuery.columns[2].longValues![i] as number;
265 const pid = threadQuery.columns[3].longValues![i] as number;
266 const threadName = threadQuery.columns[4].stringValues![i];
267 const processName = threadQuery.columns[5].stringValues![i];
268
269 let pUuid = upidToUuid.get(upid);
270 if (pUuid === undefined) {
271 pUuid = uuidv4();
272 const summaryTrackId = uuidv4();
273 upidToUuid.set(upid, pUuid);
274 addSummaryTrackActions.push(Actions.addTrack({
275 id: summaryTrackId,
276 engineId: this.engineId,
277 kind: PROCESS_SUMMARY_TRACK,
278 name: `${pid} summary`,
279 config: {upid, pid, maxDepth, utid},
280 }));
281 addTrackGroupActions.push(Actions.addTrackGroup({
282 engineId: this.engineId,
283 summaryTrackId,
284 name: `${processName} ${pid}`,
285 id: pUuid,
286 collapsed: true,
287 }));
288 }
289
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100290 addToTrackActions.push(Actions.addTrack({
291 engineId: this.engineId,
292 kind: SLICE_TRACK_KIND,
Deepanjan Roy32ad0d72018-10-18 14:50:16 -0400293 name: threadName + `[${tid}]`,
294 trackGroup: pUuid,
295 config: {upid, utid, maxDepth},
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100296 }));
Primiano Tuccie36ca632018-08-21 14:32:23 +0200297 }
Deepanjan Roy32ad0d72018-10-18 14:50:16 -0400298 const allActions =
299 addSummaryTrackActions.concat(addTrackGroupActions, addToTrackActions);
300 globals.dispatchMultiple(allActions);
Primiano Tuccie36ca632018-08-21 14:32:23 +0200301 }
302
303 private async listThreads() {
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100304 this.updateStatus('Reading thread list');
Primiano Tuccie36ca632018-08-21 14:32:23 +0200305 const sqlQuery = 'select utid, tid, pid, thread.name, process.name ' +
306 'from thread inner join process using(upid)';
Hector Dearmane6dfe412018-09-20 14:12:13 +0100307 const threadRows = await assertExists(this.engine).query(sqlQuery);
Primiano Tuccie36ca632018-08-21 14:32:23 +0200308 const threads: ThreadDesc[] = [];
309 for (let i = 0; i < threadRows.numRecords; i++) {
310 const utid = threadRows.columns[0].longValues![i] as number;
311 const tid = threadRows.columns[1].longValues![i] as number;
312 const pid = threadRows.columns[2].longValues![i] as number;
313 const threadName = threadRows.columns[3].stringValues![i];
314 const procName = threadRows.columns[4].stringValues![i];
315 threads.push({utid, tid, threadName, pid, procName});
316 } // for (record ...)
317 globals.publish('Threads', threads);
318 }
319
320 private async loadTimelineOverview(traceTime: TimeSpan) {
321 const engine = assertExists<Engine>(this.engine);
322 const numSteps = 100;
323 const stepSec = traceTime.duration / numSteps;
324 for (let step = 0; step < numSteps; step++) {
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100325 this.updateStatus(
Primiano Tuccie36ca632018-08-21 14:32:23 +0200326 'Loading overview ' +
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100327 `${Math.round((step + 1) / numSteps * 1000) / 10}%`);
Primiano Tuccie36ca632018-08-21 14:32:23 +0200328 const startSec = traceTime.start + step * stepSec;
329 const startNs = Math.floor(startSec * 1e9);
330 const endSec = startSec + stepSec;
331 const endNs = Math.ceil(endSec * 1e9);
332
333 // Sched overview.
Hector Dearmane6dfe412018-09-20 14:12:13 +0100334 const schedRows = await engine.query(
335 `select sum(dur)/${stepSec}/1e9, cpu from sched ` +
336 `where ts >= ${startNs} and ts < ${endNs} and utid != 0 ` +
337 'group by cpu order by cpu');
Primiano Tuccie36ca632018-08-21 14:32:23 +0200338 const schedData: {[key: string]: QuantizedLoad} = {};
339 for (let i = 0; i < schedRows.numRecords; i++) {
340 const load = schedRows.columns[0].doubleValues![i];
341 const cpu = schedRows.columns[1].longValues![i] as number;
342 schedData[cpu] = {startSec, endSec, load};
343 } // for (record ...)
344 globals.publish('OverviewData', schedData);
345
346 // Slices overview.
Hector Dearmane6dfe412018-09-20 14:12:13 +0100347 const slicesRows = await engine.query(
348 `select sum(dur)/${stepSec}/1e9, process.name, process.pid, upid ` +
349 'from slices inner join thread using(utid) ' +
350 'inner join process using(upid) where depth = 0 ' +
351 `and ts >= ${startNs} and ts < ${endNs} ` +
352 'group by upid');
Primiano Tuccie36ca632018-08-21 14:32:23 +0200353 const slicesData: {[key: string]: QuantizedLoad} = {};
354 for (let i = 0; i < slicesRows.numRecords; i++) {
355 const load = slicesRows.columns[0].doubleValues![i];
356 let procName = slicesRows.columns[1].stringValues![i];
357 const pid = slicesRows.columns[2].longValues![i];
358 procName += ` [${pid}]`;
359 slicesData[procName] = {startSec, endSec, load};
360 }
361 globals.publish('OverviewData', slicesData);
362 } // for (step ...)
363 }
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100364
365 private updateStatus(msg: string): void {
366 globals.dispatch(Actions.updateStatus({
367 msg,
368 timestamp: Date.now() / 1000,
369 }));
370 }
Primiano Tuccie36ca632018-08-21 14:32:23 +0200371}