blob: dfcf2881be28fc2817d418f1c2ff3523cba394dd [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,
20 addChromeSliceTrack,
21 addTrack,
22 navigate,
23 setEngineReady,
24 setTraceTime,
25 setVisibleTraceTime,
26 updateStatus
27} from '../common/actions';
28import {TimeSpan} from '../common/time';
29import {QuantizedLoad, ThreadDesc} from '../frontend/globals';
30import {SLICE_TRACK_KIND} from '../tracks/chrome_slices/common';
31import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices/common';
32
33import {Child, Children, Controller} from './controller';
34import {Engine} from './engine';
35import {globals} from './globals';
36import {QueryController, QueryControllerArgs} from './query_controller';
37import {TrackControllerArgs, trackControllerRegistry} from './track_controller';
38
39type States = 'init'|'loading_trace'|'ready';
40
41
42// TraceController handles handshakes with the frontend for everything that
43// concerns a single trace. It owns the WASM trace processor engine, handles
44// tracks data and SQL queries. There is one TraceController instance for each
45// trace opened in the UI (for now only one trace is supported).
46export class TraceController extends Controller<States> {
47 private readonly engineId: string;
48 private engine?: Engine;
49
50 constructor(engineId: string) {
51 super('init');
52 this.engineId = engineId;
53 }
54
55 run() {
56 const engineCfg = assertExists(globals.state.engines[this.engineId]);
57 switch (this.state) {
58 case 'init':
59 globals.dispatch(setEngineReady(this.engineId, false));
60 this.loadTrace().then(() => {
61 globals.dispatch(setEngineReady(this.engineId, true));
62 });
63 globals.dispatch(updateStatus('Opening trace'));
64 this.setState('loading_trace');
65 break;
66
67 case 'loading_trace':
68 // Stay in this state until loadTrace() returns and marks the engine as
69 // ready.
70 if (this.engine === undefined || !engineCfg.ready) return;
71 this.setState('ready');
72 break;
73
74 case 'ready':
75 // At this point we are ready to serve queries and handle tracks.
76 const engine = assertExists(this.engine);
77 assertTrue(engineCfg.ready);
78 const childControllers: Children = [];
79
80 // Create a TrackController for each track.
81 for (const trackId of Object.keys(globals.state.tracks)) {
82 const trackCfg = globals.state.tracks[trackId];
83 if (trackCfg.engineId !== this.engineId) continue;
84 if (!trackControllerRegistry.has(trackCfg.kind)) continue;
85 const trackCtlFactory = trackControllerRegistry.get(trackCfg.kind);
86 const trackArgs: TrackControllerArgs = {trackId, engine};
87 childControllers.push(Child(trackId, trackCtlFactory, trackArgs));
88 }
89
90 // Create a QueryController for each query.
91 for (const queryId of Object.keys(globals.state.queries)) {
92 const queryArgs: QueryControllerArgs = {queryId, engine};
93 childControllers.push(Child(queryId, QueryController, queryArgs));
94 }
95
96 return childControllers;
97
98 default:
99 throw new Error(`unknown state ${this.state}`);
100 }
101 return;
102 }
103
104 private async loadTrace() {
105 const engineCfg = assertExists(globals.state.engines[this.engineId]);
106 let blob: Blob;
107 if (engineCfg.source instanceof File) {
108 blob = engineCfg.source;
109 } else {
110 globals.dispatch(updateStatus('Fetching trace from network'));
111 blob = await(await fetch(engineCfg.source)).blob();
112 }
113
114 globals.dispatch(updateStatus('Parsing trace'));
115 this.engine = await globals.createEngine(blob);
116 const traceTime = await this.engine.getTraceTimeBounds();
117 const actions = [
118 setTraceTime(traceTime),
119 navigate('/viewer'),
120 ];
121
122 if (globals.state.visibleTraceTime.lastUpdate === 0) {
123 actions.push(setVisibleTraceTime(traceTime));
124 }
125
126 globals.dispatchMultiple(actions);
127
128 await this.listTracks();
129 await this.listThreads();
130 await this.loadTimelineOverview(traceTime);
131 }
132
133 private async listTracks() {
134 globals.dispatch(updateStatus('Loading tracks'));
135 const engine = assertExists<Engine>(this.engine);
136 const addToTrackActions: Action[] = [];
137 const numCpus = await engine.getNumberOfCpus();
138 for (let cpu = 0; cpu < numCpus; cpu++) {
139 addToTrackActions.push(
140 addTrack(this.engineId, CPU_SLICE_TRACK_KIND, cpu));
141 }
142
143 const threadQuery = await engine.rawQuery({
144 sqlQuery: 'select upid, utid, tid, thread.name, max(slices.depth) ' +
145 'from thread inner join slices using(utid) group by utid'
146 });
147 for (let i = 0; i < threadQuery.numRecords; i++) {
148 const upid = threadQuery.columns[0].longValues![i];
149 const utid = threadQuery.columns[1].longValues![i];
150 const threadId = threadQuery.columns[2].longValues![i];
151 let threadName = threadQuery.columns[3].stringValues![i];
152 threadName += `[${threadId}]`;
153 const maxDepth = threadQuery.columns[4].longValues![i];
154 addToTrackActions.push(addChromeSliceTrack(
155 this.engineId,
156 SLICE_TRACK_KIND,
157 upid as number,
158 utid as number,
159 threadName,
160 maxDepth as number));
161 }
162 globals.dispatchMultiple(addToTrackActions);
163 }
164
165 private async listThreads() {
166 globals.dispatch(updateStatus('Reading thread list'));
167 const sqlQuery = 'select utid, tid, pid, thread.name, process.name ' +
168 'from thread inner join process using(upid)';
169 const threadRows = await assertExists(this.engine).rawQuery({sqlQuery});
170 const threads: ThreadDesc[] = [];
171 for (let i = 0; i < threadRows.numRecords; i++) {
172 const utid = threadRows.columns[0].longValues![i] as number;
173 const tid = threadRows.columns[1].longValues![i] as number;
174 const pid = threadRows.columns[2].longValues![i] as number;
175 const threadName = threadRows.columns[3].stringValues![i];
176 const procName = threadRows.columns[4].stringValues![i];
177 threads.push({utid, tid, threadName, pid, procName});
178 } // for (record ...)
179 globals.publish('Threads', threads);
180 }
181
182 private async loadTimelineOverview(traceTime: TimeSpan) {
183 const engine = assertExists<Engine>(this.engine);
184 const numSteps = 100;
185 const stepSec = traceTime.duration / numSteps;
186 for (let step = 0; step < numSteps; step++) {
187 globals.dispatch(updateStatus(
188 'Loading overview ' +
189 `${Math.round((step + 1) / numSteps * 1000) / 10}%`));
190 const startSec = traceTime.start + step * stepSec;
191 const startNs = Math.floor(startSec * 1e9);
192 const endSec = startSec + stepSec;
193 const endNs = Math.ceil(endSec * 1e9);
194
195 // Sched overview.
196 const schedRows = await engine.rawQuery({
197 sqlQuery: `select sum(dur)/${stepSec}/1e9, cpu from sched ` +
198 `where ts >= ${startNs} and ts < ${endNs} ` +
199 'group by cpu order by cpu'
200 });
201 const schedData: {[key: string]: QuantizedLoad} = {};
202 for (let i = 0; i < schedRows.numRecords; i++) {
203 const load = schedRows.columns[0].doubleValues![i];
204 const cpu = schedRows.columns[1].longValues![i] as number;
205 schedData[cpu] = {startSec, endSec, load};
206 } // for (record ...)
207 globals.publish('OverviewData', schedData);
208
209 // Slices overview.
210 const slicesRows = await engine.rawQuery({
211 sqlQuery:
212 `select sum(dur)/${stepSec}/1e9, process.name, process.pid, upid ` +
213 'from slices inner join thread using(utid) ' +
214 'inner join process using(upid) where depth = 0 ' +
215 `and ts >= ${startNs} and ts < ${endNs} ` +
216 'group by upid'
217 });
218 const slicesData: {[key: string]: QuantizedLoad} = {};
219 for (let i = 0; i < slicesRows.numRecords; i++) {
220 const load = slicesRows.columns[0].doubleValues![i];
221 let procName = slicesRows.columns[1].stringValues![i];
222 const pid = slicesRows.columns[2].longValues![i];
223 procName += ` [${pid}]`;
224 slicesData[procName] = {startSec, endSec, load};
225 }
226 globals.publish('OverviewData', slicesData);
227 } // for (step ...)
228 }
229}