Primiano Tucci | e36ca63 | 2018-08-21 14:32:23 +0200 | [diff] [blame^] | 1 | // 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 | |
| 15 | import '../tracks/all_controller'; |
| 16 | |
| 17 | import {assertExists, assertTrue} from '../base/logging'; |
| 18 | import { |
| 19 | Action, |
| 20 | addChromeSliceTrack, |
| 21 | addTrack, |
| 22 | navigate, |
| 23 | setEngineReady, |
| 24 | setTraceTime, |
| 25 | setVisibleTraceTime, |
| 26 | updateStatus |
| 27 | } from '../common/actions'; |
| 28 | import {TimeSpan} from '../common/time'; |
| 29 | import {QuantizedLoad, ThreadDesc} from '../frontend/globals'; |
| 30 | import {SLICE_TRACK_KIND} from '../tracks/chrome_slices/common'; |
| 31 | import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices/common'; |
| 32 | |
| 33 | import {Child, Children, Controller} from './controller'; |
| 34 | import {Engine} from './engine'; |
| 35 | import {globals} from './globals'; |
| 36 | import {QueryController, QueryControllerArgs} from './query_controller'; |
| 37 | import {TrackControllerArgs, trackControllerRegistry} from './track_controller'; |
| 38 | |
| 39 | type 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). |
| 46 | export 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 | } |