blob: cbefcf5cc18ebc9f0ddb1946c7bae78ad0e0fe03 [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
17import {assertExists, assertTrue} from '../base/logging';
18import {
Hector Dearman4d8d73d2018-10-04 13:16:29 +010019 Actions,
Hector Dearman5ae82472018-10-03 08:30:35 +010020 DeferredAction,
Primiano Tuccie36ca632018-08-21 14:32:23 +020021} from '../common/actions';
22import {TimeSpan} from '../common/time';
23import {QuantizedLoad, ThreadDesc} from '../frontend/globals';
24import {SLICE_TRACK_KIND} from '../tracks/chrome_slices/common';
25import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices/common';
26
27import {Child, Children, Controller} from './controller';
28import {Engine} from './engine';
29import {globals} from './globals';
30import {QueryController, QueryControllerArgs} from './query_controller';
31import {TrackControllerArgs, trackControllerRegistry} from './track_controller';
32
33type States = 'init'|'loading_trace'|'ready';
34
35
Primiano Tucci7e330292018-08-24 19:10:52 +020036declare interface FileReaderSync { readAsArrayBuffer(blob: Blob): ArrayBuffer; }
37
38declare var FileReaderSync:
39 {prototype: FileReaderSync; new (): FileReaderSync;};
40
Primiano Tuccie36ca632018-08-21 14:32:23 +020041// TraceController handles handshakes with the frontend for everything that
42// concerns a single trace. It owns the WASM trace processor engine, handles
43// tracks data and SQL queries. There is one TraceController instance for each
44// trace opened in the UI (for now only one trace is supported).
45export class TraceController extends Controller<States> {
46 private readonly engineId: string;
47 private engine?: Engine;
48
49 constructor(engineId: string) {
50 super('init');
51 this.engineId = engineId;
52 }
53
Primiano Tucci7e330292018-08-24 19:10:52 +020054 onDestroy() {
55 if (this.engine !== undefined) globals.destroyEngine(this.engine.id);
56 }
57
Primiano Tuccie36ca632018-08-21 14:32:23 +020058 run() {
59 const engineCfg = assertExists(globals.state.engines[this.engineId]);
60 switch (this.state) {
61 case 'init':
Hector Dearman4d8d73d2018-10-04 13:16:29 +010062 globals.dispatch(Actions.setEngineReady({
63 engineId: this.engineId,
64 ready: false,
65 }));
Primiano Tuccie36ca632018-08-21 14:32:23 +020066 this.loadTrace().then(() => {
Hector Dearman4d8d73d2018-10-04 13:16:29 +010067 globals.dispatch(Actions.setEngineReady({
68 engineId: this.engineId,
69 ready: true,
70 }));
Primiano Tuccie36ca632018-08-21 14:32:23 +020071 });
Hector Dearman4d8d73d2018-10-04 13:16:29 +010072 this.updateStatus('Opening trace');
Primiano Tuccie36ca632018-08-21 14:32:23 +020073 this.setState('loading_trace');
74 break;
75
76 case 'loading_trace':
77 // Stay in this state until loadTrace() returns and marks the engine as
78 // ready.
79 if (this.engine === undefined || !engineCfg.ready) return;
80 this.setState('ready');
81 break;
82
83 case 'ready':
84 // At this point we are ready to serve queries and handle tracks.
85 const engine = assertExists(this.engine);
86 assertTrue(engineCfg.ready);
87 const childControllers: Children = [];
88
89 // Create a TrackController for each track.
90 for (const trackId of Object.keys(globals.state.tracks)) {
91 const trackCfg = globals.state.tracks[trackId];
92 if (trackCfg.engineId !== this.engineId) continue;
93 if (!trackControllerRegistry.has(trackCfg.kind)) continue;
94 const trackCtlFactory = trackControllerRegistry.get(trackCfg.kind);
95 const trackArgs: TrackControllerArgs = {trackId, engine};
96 childControllers.push(Child(trackId, trackCtlFactory, trackArgs));
97 }
98
99 // Create a QueryController for each query.
100 for (const queryId of Object.keys(globals.state.queries)) {
101 const queryArgs: QueryControllerArgs = {queryId, engine};
102 childControllers.push(Child(queryId, QueryController, queryArgs));
103 }
104
105 return childControllers;
106
107 default:
108 throw new Error(`unknown state ${this.state}`);
109 }
110 return;
111 }
112
113 private async loadTrace() {
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100114 this.updateStatus('Creating trace processor');
Primiano Tuccie36ca632018-08-21 14:32:23 +0200115 const engineCfg = assertExists(globals.state.engines[this.engineId]);
Hector Dearman03159542018-09-19 18:37:00 +0100116 this.engine = globals.createEngine();
Primiano Tucci7e330292018-08-24 19:10:52 +0200117
118 const statusHeader = 'Opening trace';
Primiano Tuccie36ca632018-08-21 14:32:23 +0200119 if (engineCfg.source instanceof File) {
Primiano Tucci7e330292018-08-24 19:10:52 +0200120 const blob = engineCfg.source as Blob;
121 const reader = new FileReaderSync();
122 const SLICE_SIZE = 1024 * 1024;
123 for (let off = 0; off < blob.size; off += SLICE_SIZE) {
124 const slice = blob.slice(off, off + SLICE_SIZE);
125 const arrBuf = reader.readAsArrayBuffer(slice);
126 await this.engine.parse(new Uint8Array(arrBuf));
127 const progress = Math.round((off + slice.size) / blob.size * 100);
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100128 this.updateStatus(`${statusHeader} ${progress} %`);
Primiano Tucci7e330292018-08-24 19:10:52 +0200129 }
Primiano Tuccie36ca632018-08-21 14:32:23 +0200130 } else {
Primiano Tucci7e330292018-08-24 19:10:52 +0200131 const resp = await fetch(engineCfg.source);
132 if (resp.status !== 200) {
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100133 this.updateStatus(`HTTP error ${resp.status}`);
Primiano Tucci7e330292018-08-24 19:10:52 +0200134 throw new Error(`fetch() failed with HTTP error ${resp.status}`);
135 }
136 // tslint:disable-next-line no-any
137 const rd = (resp.body as any).getReader() as ReadableStreamReader;
138 const tStartMs = performance.now();
139 let tLastUpdateMs = 0;
140 for (let off = 0;;) {
141 const readRes = await rd.read() as {value: Uint8Array, done: boolean};
142 if (readRes.value !== undefined) {
143 off += readRes.value.length;
144 await this.engine.parse(readRes.value);
145 }
146 // For traces loaded from the network there doesn't seem to be a
147 // reliable way to compute the %. The content-length exposed by GCS is
148 // before compression (which is handled transparently by the browser).
149 const nowMs = performance.now();
150 if (nowMs - tLastUpdateMs > 100) {
151 tLastUpdateMs = nowMs;
152 const mb = off / 1e6;
153 const tElapsed = (nowMs - tStartMs) / 1e3;
154 let status = `${statusHeader} ${mb.toFixed(1)} MB `;
155 status += `(${(mb / tElapsed).toFixed(1)} MB/s)`;
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100156 this.updateStatus(status);
Primiano Tucci7e330292018-08-24 19:10:52 +0200157 }
158 if (readRes.done) break;
159 }
Primiano Tuccie36ca632018-08-21 14:32:23 +0200160 }
161
Hector Dearman1d289212018-09-05 14:05:29 +0100162 await this.engine.notifyEof();
163
Primiano Tuccie36ca632018-08-21 14:32:23 +0200164 const traceTime = await this.engine.getTraceTimeBounds();
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100165 const traceTimeState = {
166 startSec: traceTime.start,
167 endSec: traceTime.end,
168 lastUpdate: Date.now() / 1000,
169 };
Primiano Tuccie36ca632018-08-21 14:32:23 +0200170 const actions = [
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100171 Actions.setTraceTime(traceTimeState),
172 Actions.navigate({route: '/viewer'}),
Primiano Tuccie36ca632018-08-21 14:32:23 +0200173 ];
174
175 if (globals.state.visibleTraceTime.lastUpdate === 0) {
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100176 actions.push(Actions.setVisibleTraceTime(traceTimeState));
Primiano Tuccie36ca632018-08-21 14:32:23 +0200177 }
178
179 globals.dispatchMultiple(actions);
180
Hector Dearmane702a682018-10-16 16:29:28 +0100181 {
182 // When we reload from a permalink don't create extra tracks:
183 const {pinnedTracks, scrollingTracks} = globals.state;
Hector Dearmanbb55c9e2018-10-17 16:08:39 +0100184 if (!pinnedTracks.length && !scrollingTracks.length) {
Hector Dearmane702a682018-10-16 16:29:28 +0100185 await this.listTracks();
186 }
187 }
188
Primiano Tuccie36ca632018-08-21 14:32:23 +0200189 await this.listThreads();
190 await this.loadTimelineOverview(traceTime);
191 }
192
193 private async listTracks() {
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100194 this.updateStatus('Loading tracks');
Hector Dearmane702a682018-10-16 16:29:28 +0100195
Primiano Tuccie36ca632018-08-21 14:32:23 +0200196 const engine = assertExists<Engine>(this.engine);
Hector Dearman5ae82472018-10-03 08:30:35 +0100197 const addToTrackActions: DeferredAction[] = [];
Primiano Tuccie36ca632018-08-21 14:32:23 +0200198 const numCpus = await engine.getNumberOfCpus();
Hector Dearmane27a9742018-10-12 09:31:34 +0100199
200 // TODO(hjd): Move this code out of TraceController.
201 for (const counterName of ['VSYNC-sf', 'VSYNC-app']) {
202 const hasVsync =
203 !!(await engine.query(
204 `select ts from counters where name like "${
205 counterName
206 }" limit 1`))
207 .numRecords;
208 if (!hasVsync) continue;
209 addToTrackActions.push(Actions.addTrack({
210 engineId: this.engineId,
211 kind: 'VsyncTrack',
212 name: `${counterName}`,
213 config: {
214 counterName,
215 }
216 }));
217 }
218
Primiano Tuccie36ca632018-08-21 14:32:23 +0200219 for (let cpu = 0; cpu < numCpus; cpu++) {
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100220 addToTrackActions.push(Actions.addTrack({
221 engineId: this.engineId,
222 kind: CPU_SLICE_TRACK_KIND,
223 name: `Cpu ${cpu}`,
224 config: {
225 cpu,
226 }
227 }));
Primiano Tuccie36ca632018-08-21 14:32:23 +0200228 }
229
Hector Dearmandbf46342018-10-04 17:28:38 +0100230 const threadQuery = await engine.query(`
231 select upid, utid, tid, thread.name, depth
232 from thread inner join (
233 select utid, max(slices.depth) as depth
234 from slices
235 group by utid
236 ) using(utid)`);
Primiano Tuccie36ca632018-08-21 14:32:23 +0200237 for (let i = 0; i < threadQuery.numRecords; i++) {
238 const upid = threadQuery.columns[0].longValues![i];
239 const utid = threadQuery.columns[1].longValues![i];
240 const threadId = threadQuery.columns[2].longValues![i];
241 let threadName = threadQuery.columns[3].stringValues![i];
242 threadName += `[${threadId}]`;
243 const maxDepth = threadQuery.columns[4].longValues![i];
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100244 addToTrackActions.push(Actions.addTrack({
245 engineId: this.engineId,
246 kind: SLICE_TRACK_KIND,
247 name: threadName,
248 config: {
249 upid: upid as number,
250 utid: utid as number,
251 maxDepth: maxDepth as number,
252 }
253 }));
Primiano Tuccie36ca632018-08-21 14:32:23 +0200254 }
255 globals.dispatchMultiple(addToTrackActions);
256 }
257
258 private async listThreads() {
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100259 this.updateStatus('Reading thread list');
Primiano Tuccie36ca632018-08-21 14:32:23 +0200260 const sqlQuery = 'select utid, tid, pid, thread.name, process.name ' +
261 'from thread inner join process using(upid)';
Hector Dearmane6dfe412018-09-20 14:12:13 +0100262 const threadRows = await assertExists(this.engine).query(sqlQuery);
Primiano Tuccie36ca632018-08-21 14:32:23 +0200263 const threads: ThreadDesc[] = [];
264 for (let i = 0; i < threadRows.numRecords; i++) {
265 const utid = threadRows.columns[0].longValues![i] as number;
266 const tid = threadRows.columns[1].longValues![i] as number;
267 const pid = threadRows.columns[2].longValues![i] as number;
268 const threadName = threadRows.columns[3].stringValues![i];
269 const procName = threadRows.columns[4].stringValues![i];
270 threads.push({utid, tid, threadName, pid, procName});
271 } // for (record ...)
272 globals.publish('Threads', threads);
273 }
274
275 private async loadTimelineOverview(traceTime: TimeSpan) {
276 const engine = assertExists<Engine>(this.engine);
277 const numSteps = 100;
278 const stepSec = traceTime.duration / numSteps;
279 for (let step = 0; step < numSteps; step++) {
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100280 this.updateStatus(
Primiano Tuccie36ca632018-08-21 14:32:23 +0200281 'Loading overview ' +
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100282 `${Math.round((step + 1) / numSteps * 1000) / 10}%`);
Primiano Tuccie36ca632018-08-21 14:32:23 +0200283 const startSec = traceTime.start + step * stepSec;
284 const startNs = Math.floor(startSec * 1e9);
285 const endSec = startSec + stepSec;
286 const endNs = Math.ceil(endSec * 1e9);
287
288 // Sched overview.
Hector Dearmane6dfe412018-09-20 14:12:13 +0100289 const schedRows = await engine.query(
290 `select sum(dur)/${stepSec}/1e9, cpu from sched ` +
291 `where ts >= ${startNs} and ts < ${endNs} and utid != 0 ` +
292 'group by cpu order by cpu');
Primiano Tuccie36ca632018-08-21 14:32:23 +0200293 const schedData: {[key: string]: QuantizedLoad} = {};
294 for (let i = 0; i < schedRows.numRecords; i++) {
295 const load = schedRows.columns[0].doubleValues![i];
296 const cpu = schedRows.columns[1].longValues![i] as number;
297 schedData[cpu] = {startSec, endSec, load};
298 } // for (record ...)
299 globals.publish('OverviewData', schedData);
300
301 // Slices overview.
Hector Dearmane6dfe412018-09-20 14:12:13 +0100302 const slicesRows = await engine.query(
303 `select sum(dur)/${stepSec}/1e9, process.name, process.pid, upid ` +
304 'from slices inner join thread using(utid) ' +
305 'inner join process using(upid) where depth = 0 ' +
306 `and ts >= ${startNs} and ts < ${endNs} ` +
307 'group by upid');
Primiano Tuccie36ca632018-08-21 14:32:23 +0200308 const slicesData: {[key: string]: QuantizedLoad} = {};
309 for (let i = 0; i < slicesRows.numRecords; i++) {
310 const load = slicesRows.columns[0].doubleValues![i];
311 let procName = slicesRows.columns[1].stringValues![i];
312 const pid = slicesRows.columns[2].longValues![i];
313 procName += ` [${pid}]`;
314 slicesData[procName] = {startSec, endSec, load};
315 }
316 globals.publish('OverviewData', slicesData);
317 } // for (step ...)
318 }
Hector Dearman4d8d73d2018-10-04 13:16:29 +0100319
320 private updateStatus(msg: string): void {
321 globals.dispatch(Actions.updateStatus({
322 msg,
323 timestamp: Date.now() / 1000,
324 }));
325 }
Primiano Tuccie36ca632018-08-21 14:32:23 +0200326}