blob: 06f570f83aa1d87662f76f6568f332fb8a12ba0c [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 {
19 Action,
Primiano Tuccie36ca632018-08-21 14:32:23 +020020 addTrack,
21 navigate,
22 setEngineReady,
23 setTraceTime,
24 setVisibleTraceTime,
25 updateStatus
26} from '../common/actions';
27import {TimeSpan} from '../common/time';
28import {QuantizedLoad, ThreadDesc} from '../frontend/globals';
29import {SLICE_TRACK_KIND} from '../tracks/chrome_slices/common';
30import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices/common';
31
32import {Child, Children, Controller} from './controller';
33import {Engine} from './engine';
34import {globals} from './globals';
35import {QueryController, QueryControllerArgs} from './query_controller';
36import {TrackControllerArgs, trackControllerRegistry} from './track_controller';
37
38type States = 'init'|'loading_trace'|'ready';
39
40
Primiano Tucci7e330292018-08-24 19:10:52 +020041declare interface FileReaderSync { readAsArrayBuffer(blob: Blob): ArrayBuffer; }
42
43declare var FileReaderSync:
44 {prototype: FileReaderSync; new (): FileReaderSync;};
45
Primiano Tuccie36ca632018-08-21 14:32:23 +020046// TraceController handles handshakes with the frontend for everything that
47// concerns a single trace. It owns the WASM trace processor engine, handles
48// tracks data and SQL queries. There is one TraceController instance for each
49// trace opened in the UI (for now only one trace is supported).
50export class TraceController extends Controller<States> {
51 private readonly engineId: string;
52 private engine?: Engine;
53
54 constructor(engineId: string) {
55 super('init');
56 this.engineId = engineId;
57 }
58
Primiano Tucci7e330292018-08-24 19:10:52 +020059 onDestroy() {
60 if (this.engine !== undefined) globals.destroyEngine(this.engine.id);
61 }
62
Primiano Tuccie36ca632018-08-21 14:32:23 +020063 run() {
64 const engineCfg = assertExists(globals.state.engines[this.engineId]);
65 switch (this.state) {
66 case 'init':
67 globals.dispatch(setEngineReady(this.engineId, false));
68 this.loadTrace().then(() => {
69 globals.dispatch(setEngineReady(this.engineId, true));
70 });
71 globals.dispatch(updateStatus('Opening trace'));
72 this.setState('loading_trace');
73 break;
74
75 case 'loading_trace':
76 // Stay in this state until loadTrace() returns and marks the engine as
77 // ready.
78 if (this.engine === undefined || !engineCfg.ready) return;
79 this.setState('ready');
80 break;
81
82 case 'ready':
83 // At this point we are ready to serve queries and handle tracks.
84 const engine = assertExists(this.engine);
85 assertTrue(engineCfg.ready);
86 const childControllers: Children = [];
87
88 // Create a TrackController for each track.
89 for (const trackId of Object.keys(globals.state.tracks)) {
90 const trackCfg = globals.state.tracks[trackId];
91 if (trackCfg.engineId !== this.engineId) continue;
92 if (!trackControllerRegistry.has(trackCfg.kind)) continue;
93 const trackCtlFactory = trackControllerRegistry.get(trackCfg.kind);
94 const trackArgs: TrackControllerArgs = {trackId, engine};
95 childControllers.push(Child(trackId, trackCtlFactory, trackArgs));
96 }
97
98 // Create a QueryController for each query.
99 for (const queryId of Object.keys(globals.state.queries)) {
100 const queryArgs: QueryControllerArgs = {queryId, engine};
101 childControllers.push(Child(queryId, QueryController, queryArgs));
102 }
103
104 return childControllers;
105
106 default:
107 throw new Error(`unknown state ${this.state}`);
108 }
109 return;
110 }
111
112 private async loadTrace() {
Primiano Tucci7e330292018-08-24 19:10:52 +0200113 globals.dispatch(updateStatus('Creating trace processor'));
Primiano Tuccie36ca632018-08-21 14:32:23 +0200114 const engineCfg = assertExists(globals.state.engines[this.engineId]);
Hector Dearman03159542018-09-19 18:37:00 +0100115 this.engine = globals.createEngine();
Primiano Tucci7e330292018-08-24 19:10:52 +0200116
117 const statusHeader = 'Opening trace';
Primiano Tuccie36ca632018-08-21 14:32:23 +0200118 if (engineCfg.source instanceof File) {
Primiano Tucci7e330292018-08-24 19:10:52 +0200119 const blob = engineCfg.source as Blob;
120 const reader = new FileReaderSync();
121 const SLICE_SIZE = 1024 * 1024;
122 for (let off = 0; off < blob.size; off += SLICE_SIZE) {
123 const slice = blob.slice(off, off + SLICE_SIZE);
124 const arrBuf = reader.readAsArrayBuffer(slice);
125 await this.engine.parse(new Uint8Array(arrBuf));
126 const progress = Math.round((off + slice.size) / blob.size * 100);
127 globals.dispatch(updateStatus(`${statusHeader} ${progress} %`));
128 }
Primiano Tuccie36ca632018-08-21 14:32:23 +0200129 } else {
Primiano Tucci7e330292018-08-24 19:10:52 +0200130 const resp = await fetch(engineCfg.source);
131 if (resp.status !== 200) {
132 globals.dispatch(updateStatus(`HTTP error ${resp.status}`));
133 throw new Error(`fetch() failed with HTTP error ${resp.status}`);
134 }
135 // tslint:disable-next-line no-any
136 const rd = (resp.body as any).getReader() as ReadableStreamReader;
137 const tStartMs = performance.now();
138 let tLastUpdateMs = 0;
139 for (let off = 0;;) {
140 const readRes = await rd.read() as {value: Uint8Array, done: boolean};
141 if (readRes.value !== undefined) {
142 off += readRes.value.length;
143 await this.engine.parse(readRes.value);
144 }
145 // For traces loaded from the network there doesn't seem to be a
146 // reliable way to compute the %. The content-length exposed by GCS is
147 // before compression (which is handled transparently by the browser).
148 const nowMs = performance.now();
149 if (nowMs - tLastUpdateMs > 100) {
150 tLastUpdateMs = nowMs;
151 const mb = off / 1e6;
152 const tElapsed = (nowMs - tStartMs) / 1e3;
153 let status = `${statusHeader} ${mb.toFixed(1)} MB `;
154 status += `(${(mb / tElapsed).toFixed(1)} MB/s)`;
155 globals.dispatch(updateStatus(status));
156 }
157 if (readRes.done) break;
158 }
Primiano Tuccie36ca632018-08-21 14:32:23 +0200159 }
160
Hector Dearman1d289212018-09-05 14:05:29 +0100161 await this.engine.notifyEof();
162
Primiano Tuccie36ca632018-08-21 14:32:23 +0200163 const traceTime = await this.engine.getTraceTimeBounds();
164 const actions = [
165 setTraceTime(traceTime),
166 navigate('/viewer'),
167 ];
168
169 if (globals.state.visibleTraceTime.lastUpdate === 0) {
170 actions.push(setVisibleTraceTime(traceTime));
171 }
172
173 globals.dispatchMultiple(actions);
174
175 await this.listTracks();
176 await this.listThreads();
177 await this.loadTimelineOverview(traceTime);
178 }
179
180 private async listTracks() {
181 globals.dispatch(updateStatus('Loading tracks'));
182 const engine = assertExists<Engine>(this.engine);
183 const addToTrackActions: Action[] = [];
184 const numCpus = await engine.getNumberOfCpus();
185 for (let cpu = 0; cpu < numCpus; cpu++) {
186 addToTrackActions.push(
Hector Dearman87aec0f2018-09-19 13:46:15 +0100187 addTrack(this.engineId, CPU_SLICE_TRACK_KIND, `Cpu ${cpu}`, {
188 cpu,
189 }));
Primiano Tuccie36ca632018-08-21 14:32:23 +0200190 }
191
Hector Dearmane6dfe412018-09-20 14:12:13 +0100192 const threadQuery = await engine.query(
193 'select upid, utid, tid, thread.name, max(slices.depth) ' +
194 'from thread inner join slices using(utid) group by utid');
Primiano Tuccie36ca632018-08-21 14:32:23 +0200195 for (let i = 0; i < threadQuery.numRecords; i++) {
196 const upid = threadQuery.columns[0].longValues![i];
197 const utid = threadQuery.columns[1].longValues![i];
198 const threadId = threadQuery.columns[2].longValues![i];
199 let threadName = threadQuery.columns[3].stringValues![i];
200 threadName += `[${threadId}]`;
201 const maxDepth = threadQuery.columns[4].longValues![i];
Hector Dearman87aec0f2018-09-19 13:46:15 +0100202 addToTrackActions.push(
203 addTrack(this.engineId, SLICE_TRACK_KIND, threadName, {
204 upid: upid as number,
205 utid: utid as number,
206 maxDepth: maxDepth as number,
207 }));
Primiano Tuccie36ca632018-08-21 14:32:23 +0200208 }
209 globals.dispatchMultiple(addToTrackActions);
210 }
211
212 private async listThreads() {
213 globals.dispatch(updateStatus('Reading thread list'));
214 const sqlQuery = 'select utid, tid, pid, thread.name, process.name ' +
215 'from thread inner join process using(upid)';
Hector Dearmane6dfe412018-09-20 14:12:13 +0100216 const threadRows = await assertExists(this.engine).query(sqlQuery);
Primiano Tuccie36ca632018-08-21 14:32:23 +0200217 const threads: ThreadDesc[] = [];
218 for (let i = 0; i < threadRows.numRecords; i++) {
219 const utid = threadRows.columns[0].longValues![i] as number;
220 const tid = threadRows.columns[1].longValues![i] as number;
221 const pid = threadRows.columns[2].longValues![i] as number;
222 const threadName = threadRows.columns[3].stringValues![i];
223 const procName = threadRows.columns[4].stringValues![i];
224 threads.push({utid, tid, threadName, pid, procName});
225 } // for (record ...)
226 globals.publish('Threads', threads);
227 }
228
229 private async loadTimelineOverview(traceTime: TimeSpan) {
230 const engine = assertExists<Engine>(this.engine);
231 const numSteps = 100;
232 const stepSec = traceTime.duration / numSteps;
233 for (let step = 0; step < numSteps; step++) {
234 globals.dispatch(updateStatus(
235 'Loading overview ' +
236 `${Math.round((step + 1) / numSteps * 1000) / 10}%`));
237 const startSec = traceTime.start + step * stepSec;
238 const startNs = Math.floor(startSec * 1e9);
239 const endSec = startSec + stepSec;
240 const endNs = Math.ceil(endSec * 1e9);
241
242 // Sched overview.
Hector Dearmane6dfe412018-09-20 14:12:13 +0100243 const schedRows = await engine.query(
244 `select sum(dur)/${stepSec}/1e9, cpu from sched ` +
245 `where ts >= ${startNs} and ts < ${endNs} and utid != 0 ` +
246 'group by cpu order by cpu');
Primiano Tuccie36ca632018-08-21 14:32:23 +0200247 const schedData: {[key: string]: QuantizedLoad} = {};
248 for (let i = 0; i < schedRows.numRecords; i++) {
249 const load = schedRows.columns[0].doubleValues![i];
250 const cpu = schedRows.columns[1].longValues![i] as number;
251 schedData[cpu] = {startSec, endSec, load};
252 } // for (record ...)
253 globals.publish('OverviewData', schedData);
254
255 // Slices overview.
Hector Dearmane6dfe412018-09-20 14:12:13 +0100256 const slicesRows = await engine.query(
257 `select sum(dur)/${stepSec}/1e9, process.name, process.pid, upid ` +
258 'from slices inner join thread using(utid) ' +
259 'inner join process using(upid) where depth = 0 ' +
260 `and ts >= ${startNs} and ts < ${endNs} ` +
261 'group by upid');
Primiano Tuccie36ca632018-08-21 14:32:23 +0200262 const slicesData: {[key: string]: QuantizedLoad} = {};
263 for (let i = 0; i < slicesRows.numRecords; i++) {
264 const load = slicesRows.columns[0].doubleValues![i];
265 let procName = slicesRows.columns[1].stringValues![i];
266 const pid = slicesRows.columns[2].longValues![i];
267 procName += ` [${pid}]`;
268 slicesData[procName] = {startSec, endSec, load};
269 }
270 globals.publish('OverviewData', slicesData);
271 } // for (step ...)
272 }
273}