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 15import { assertExists, assertTrue } from '../base/logging'; 16import { 17 Actions, 18 DeferredAction, 19} from '../common/actions'; 20import { cacheTrace } from '../common/cache_manager'; 21import { TRACE_MARGIN_TIME_S } from '../common/constants'; 22import { Engine } from '../common/engine'; 23import { featureFlags, Flag, PERF_SAMPLE_FLAG } from '../common/feature_flags'; 24import { HttpRpcEngine } from '../common/http_rpc_engine'; 25import { NUM, NUM_NULL, QueryError, STR, STR_NULL } from '../common/query_result'; 26import { EngineMode } from '../common/state'; 27import { TimeSpan, toNs, toNsCeil, toNsFloor } from '../common/time'; 28import { resetEngineWorker, WasmEngineProxy } from '../common/wasm_engine_proxy'; 29import { 30 globals as frontendGlobals, 31 QuantizedLoad, 32 ThreadDesc 33} from '../frontend/globals'; 34import { 35 publishHasFtrace, 36 publishMetricError, 37 publishOverviewData, 38 publishThreads 39} from '../frontend/publish'; 40import { Router } from '../frontend/router'; 41 42import { 43 CounterAggregationController 44} from './aggregation/counter_aggregation_controller'; 45import { 46 CpuAggregationController 47} from './aggregation/cpu_aggregation_controller'; 48import { 49 CpuByProcessAggregationController 50} from './aggregation/cpu_by_process_aggregation_controller'; 51import { 52 FrameAggregationController 53} from './aggregation/frame_aggregation_controller'; 54import { 55 SliceAggregationController 56} from './aggregation/slice_aggregation_controller'; 57import { 58 ThreadAggregationController 59} from './aggregation/thread_aggregation_controller'; 60import { Child, Children, Controller } from './controller'; 61import { 62 CpuProfileController, 63 CpuProfileControllerArgs 64} from './cpu_profile_controller'; 65import { 66 FlamegraphController, 67 FlamegraphControllerArgs 68} from './flamegraph_controller'; 69import { 70 FlowEventsController, 71 FlowEventsControllerArgs 72} from './flow_events_controller'; 73import { globals } from './globals'; 74import { LoadingManager } from './loading_manager'; 75import { LogsController } from './logs_controller'; 76import { MetricsController } from './metrics_controller'; 77import { 78 PivotTableController, 79 PivotTableControllerArgs 80} from './pivot_table_controller'; 81import {PivotTableReduxController} from './pivot_table_redux_controller'; 82import {QueryController, QueryControllerArgs} from './query_controller'; 83import {SearchController} from './search_controller'; 84import { 85 SelectionController, 86 SelectionControllerArgs 87} from './selection_controller'; 88import { 89 TraceErrorController, 90} from './trace_error_controller'; 91import { 92 TraceBufferStream, 93 TraceFileStream, 94 TraceHttpStream, 95 TraceStream 96} from './trace_stream'; 97import { TrackControllerArgs, trackControllerRegistry } from './track_controller'; 98import { decideTracks } from './track_decider'; 99 100type States = 'init' | 'loading_trace' | 'ready'; 101 102const METRICS = [ 103 'android_startup', 104 'android_ion', 105 'android_lmk', 106 'android_dma_heap', 107 'android_thread_time_in_state', 108 'android_surfaceflinger', 109 'android_batt', 110 'android_sysui_cuj', 111 'android_jank', 112 'android_camera', 113 'android_other_traces', 114 'chrome_dropped_frames', 115 'chrome_long_latency', 116 'trace_metadata', 117 'android_trusty_workqueues', 118]; 119const FLAGGED_METRICS: Array<[Flag, string]> = METRICS.map(m => { 120 const id = `forceMetric${m}`; 121 let name = m.split('_').join(' ') + ' metric'; 122 name = name[0].toUpperCase() + name.slice(1); 123 const flag = featureFlags.register({ 124 id, 125 name, 126 description: `Overrides running the '${m}' metric at import time.`, 127 defaultValue: true, 128 }); 129 return [flag, m]; 130}); 131 132// TraceController handles handshakes with the frontend for everything that 133// concerns a single trace. It owns the WASM trace processor engine, handles 134// tracks data and SQL queries. There is one TraceController instance for each 135// trace opened in the UI (for now only one trace is supported). 136export class TraceController extends Controller<States> { 137 private readonly engineId: string; 138 private engine?: Engine; 139 140 constructor(engineId: string) { 141 super('init'); 142 this.engineId = engineId; 143 } 144 145 run() { 146 const engineCfg = assertExists(globals.state.engines[this.engineId]); 147 switch (this.state) { 148 case 'init': 149 this.loadTrace() 150 .then(mode => { 151 globals.dispatch(Actions.setEngineReady({ 152 engineId: this.engineId, 153 ready: true, 154 mode, 155 })); 156 }) 157 .catch(err => { 158 this.updateStatus(`${err}`); 159 throw err; 160 }); 161 this.updateStatus('Opening trace'); 162 this.setState('loading_trace'); 163 break; 164 165 case 'loading_trace': 166 // Stay in this state until loadTrace() returns and marks the engine as 167 // ready. 168 if (this.engine === undefined || !engineCfg.ready) return; 169 this.setState('ready'); 170 break; 171 172 case 'ready': 173 // At this point we are ready to serve queries and handle tracks. 174 const engine = assertExists(this.engine); 175 const childControllers: Children = []; 176 177 // Create a TrackController for each track. 178 for (const trackId of Object.keys(globals.state.tracks)) { 179 const trackCfg = globals.state.tracks[trackId]; 180 if (trackCfg.engineId !== this.engineId) continue; 181 if (!trackControllerRegistry.has(trackCfg.kind)) continue; 182 const trackCtlFactory = trackControllerRegistry.get(trackCfg.kind); 183 const trackArgs: TrackControllerArgs = { trackId, engine }; 184 childControllers.push(Child(trackId, trackCtlFactory, trackArgs)); 185 } 186 187 // Create a QueryController for each query. 188 for (const queryId of Object.keys(globals.state.queries)) { 189 const queryArgs: QueryControllerArgs = { queryId, engine }; 190 childControllers.push(Child(queryId, QueryController, queryArgs)); 191 } 192 193 const selectionArgs: SelectionControllerArgs = { engine }; 194 childControllers.push( 195 Child('selection', SelectionController, selectionArgs)); 196 197 const flowEventsArgs: FlowEventsControllerArgs = { engine }; 198 childControllers.push( 199 Child('flowEvents', FlowEventsController, flowEventsArgs)); 200 201 const cpuProfileArgs: CpuProfileControllerArgs = { engine }; 202 childControllers.push( 203 Child('cpuProfile', CpuProfileController, cpuProfileArgs)); 204 205 const flamegraphArgs: FlamegraphControllerArgs = { engine }; 206 childControllers.push( 207 Child('flamegraph', FlamegraphController, flamegraphArgs)); 208 childControllers.push(Child( 209 'cpu_aggregation', 210 CpuAggregationController, 211 { engine, kind: 'cpu_aggregation' })); 212 childControllers.push(Child( 213 'thread_aggregation', 214 ThreadAggregationController, 215 { engine, kind: 'thread_state_aggregation' })); 216 childControllers.push(Child( 217 'cpu_process_aggregation', 218 CpuByProcessAggregationController, 219 { engine, kind: 'cpu_by_process_aggregation' })); 220 childControllers.push(Child( 221 'slice_aggregation', 222 SliceAggregationController, 223 { engine, kind: 'slice_aggregation' })); 224 childControllers.push(Child( 225 'counter_aggregation', 226 CounterAggregationController, 227 { engine, kind: 'counter_aggregation' })); 228 childControllers.push(Child( 229 'frame_aggregation', 230 FrameAggregationController, 231 { engine, kind: 'frame_aggregation' })); 232 childControllers.push(Child('search', SearchController, { 233 engine, 234 app: globals, 235 })); 236 childControllers.push( 237 Child('pivot_table_redux', PivotTableReduxController, {engine})); 238 239 childControllers.push(Child('logs', LogsController, { 240 engine, 241 app: globals, 242 })); 243 childControllers.push( 244 Child('traceError', TraceErrorController, { engine })); 245 childControllers.push(Child('metrics', MetricsController, { engine })); 246 247 // Create a PivotTableController for each pivot table. 248 for (const pivotTableId of Object.keys(globals.state.pivotTable)) { 249 const pivotTableArgs: 250 PivotTableControllerArgs = { pivotTableId, engine }; 251 childControllers.push( 252 Child(pivotTableId, PivotTableController, pivotTableArgs)); 253 } 254 255 return childControllers; 256 257 default: 258 throw new Error(`unknown state ${this.state}`); 259 } 260 return; 261 } 262 263 onDestroy() { 264 frontendGlobals.engines.delete(this.engineId); 265 } 266 267 private async loadTrace(): Promise<EngineMode> { 268 this.updateStatus('Creating trace processor'); 269 // Check if there is any instance of the trace_processor_shell running in 270 // HTTP RPC mode (i.e. trace_processor_shell -D). 271 let engineMode: EngineMode; 272 let useRpc = false; 273 if (globals.state.newEngineMode === 'USE_HTTP_RPC_IF_AVAILABLE') { 274 useRpc = (await HttpRpcEngine.checkConnection()).connected; 275 } 276 let engine; 277 if (useRpc) { 278 console.log('Opening trace using native accelerator over HTTP+RPC'); 279 engineMode = 'HTTP_RPC'; 280 engine = new HttpRpcEngine(this.engineId, LoadingManager.getInstance); 281 engine.errorHandler = (err) => { 282 globals.dispatch( 283 Actions.setEngineFailed({ mode: 'HTTP_RPC', failure: `${err}` })); 284 throw err; 285 }; 286 } else { 287 console.log('Opening trace using built-in WASM engine'); 288 engineMode = 'WASM'; 289 const enginePort = resetEngineWorker(); 290 engine = new WasmEngineProxy( 291 this.engineId, enginePort, LoadingManager.getInstance); 292 } 293 this.engine = engine; 294 295 frontendGlobals.engines.set(this.engineId, engine); 296 globals.dispatch(Actions.setEngineReady({ 297 engineId: this.engineId, 298 ready: false, 299 mode: engineMode, 300 })); 301 const engineCfg = assertExists(globals.state.engines[this.engineId]); 302 let traceStream: TraceStream | undefined; 303 if (engineCfg.source.type === 'FILE') { 304 traceStream = new TraceFileStream(engineCfg.source.file); 305 } else if (engineCfg.source.type === 'ARRAY_BUFFER') { 306 traceStream = new TraceBufferStream(engineCfg.source.buffer); 307 } else if (engineCfg.source.type === 'URL') { 308 traceStream = new TraceHttpStream(engineCfg.source.url); 309 } else if (engineCfg.source.type === 'HTTP_RPC') { 310 traceStream = undefined; 311 } else { 312 throw new Error(`Unknown source: ${JSON.stringify(engineCfg.source)}`); 313 } 314 315 // |traceStream| can be undefined in the case when we are using the external 316 // HTTP+RPC endpoint and the trace processor instance has already loaded 317 // a trace (because it was passed as a cmdline argument to 318 // trace_processor_shell). In this case we don't want the UI to load any 319 // file/stream and we just want to jump to the loading phase. 320 if (traceStream !== undefined) { 321 const tStart = performance.now(); 322 for (; ;) { 323 const res = await traceStream.readChunk(); 324 await this.engine.parse(res.data); 325 const elapsed = (performance.now() - tStart) / 1000; 326 let status = 'Loading trace '; 327 if (res.bytesTotal > 0) { 328 const progress = Math.round(res.bytesRead / res.bytesTotal * 100); 329 status += `${progress}%`; 330 } else { 331 status += `${Math.round(res.bytesRead / 1e6)} MB`; 332 } 333 status += ` - ${Math.ceil(res.bytesRead / elapsed / 1e6)} MB/s`; 334 this.updateStatus(status); 335 if (res.eof) break; 336 } 337 await this.engine.notifyEof(); 338 } else { 339 assertTrue(this.engine instanceof HttpRpcEngine); 340 await this.engine.restoreInitialTables(); 341 } 342 343 // traceUuid will be '' if the trace is not cacheable (URL or RPC). 344 const traceUuid = await this.cacheCurrentTrace(); 345 346 const traceTime = await this.engine.getTraceTimeBounds(); 347 let startSec = traceTime.start; 348 let endSec = traceTime.end; 349 startSec -= TRACE_MARGIN_TIME_S; 350 endSec += TRACE_MARGIN_TIME_S; 351 const traceTimeState = { 352 startSec, 353 endSec, 354 }; 355 356 const emptyOmniboxState = { 357 omnibox: '', 358 mode: frontendGlobals.state.frontendLocalState.omniboxState.mode || 359 'SEARCH', 360 lastUpdate: Date.now() / 1000 361 }; 362 363 const actions: DeferredAction[] = [ 364 Actions.setOmnibox(emptyOmniboxState), 365 Actions.setTraceUuid({ traceUuid }), 366 Actions.setTraceTime(traceTimeState) 367 ]; 368 369 let visibleStartSec = startSec; 370 let visibleEndSec = endSec; 371 const mdTime = await this.engine.getTracingMetadataTimeBounds(); 372 // make sure the bounds hold 373 if (Math.max(visibleStartSec, mdTime.start - TRACE_MARGIN_TIME_S) < 374 Math.min(visibleEndSec, mdTime.end + TRACE_MARGIN_TIME_S)) { 375 visibleStartSec = 376 Math.max(visibleStartSec, mdTime.start - TRACE_MARGIN_TIME_S); 377 visibleEndSec = Math.min(visibleEndSec, mdTime.end + TRACE_MARGIN_TIME_S); 378 } 379 380 // We don't know the resolution at this point. However this will be 381 // replaced in 50ms so a guess is fine. 382 const resolution = (visibleStartSec - visibleEndSec) / 1000; 383 actions.push(Actions.setVisibleTraceTime({ 384 startSec: visibleStartSec, 385 endSec: visibleEndSec, 386 lastUpdate: Date.now() / 1000, 387 resolution 388 })); 389 390 globals.dispatchMultiple(actions); 391 Router.navigate(`#!/viewer?local_cache_key=${traceUuid}`); 392 393 // Make sure the helper views are available before we start adding tracks. 394 await this.initialiseHelperViews(); 395 396 { 397 // When we reload from a permalink don't create extra tracks: 398 const { pinnedTracks, tracks } = globals.state; 399 if (!pinnedTracks.length && !Object.keys(tracks).length) { 400 await this.listTracks(); 401 } 402 } 403 404 await this.listThreads(); 405 await this.loadTimelineOverview(traceTime); 406 407 { 408 // A quick heuristic to check if the trace has ftrace events. This is 409 // based on the assumption that most traces that have ftrace either: 410 // - Are proto traces captured via perfetto, in which case traced_probes 411 // emits ftrace per-cpu stats that end up in the stats table. 412 // - Have a raw event with non-zero cpu or utid. 413 // Notes: 414 // - The "+1 > 1" is to avoid pushing down the constraints to the "raw" 415 // table, which would compute a full column filter without being aware 416 // of the limit 1, and instead delegate the filtering to the iterator. 417 const query = `select '_' as _ from raw 418 where cpu + 1 > 1 or utid + 1 > 1 limit 1`; 419 const result = await assertExists(this.engine).query(query); 420 const hasFtrace = result.numRows() > 0; 421 publishHasFtrace(hasFtrace); 422 } 423 424 globals.dispatch(Actions.removeDebugTrack({})); 425 globals.dispatch(Actions.sortThreadTracks({})); 426 427 await this.selectFirstHeapProfile(); 428 if (PERF_SAMPLE_FLAG.get()) { 429 await this.selectPerfSample(); 430 } 431 432 return engineMode; 433 } 434 435 private async selectPerfSample() { 436 const query = `select ts, upid 437 from perf_sample 438 join thread using (utid) 439 order by ts desc limit 1`; 440 const profile = await assertExists(this.engine).query(query); 441 if (profile.numRows() !== 1) return; 442 const row = profile.firstRow({ ts: NUM, upid: NUM }); 443 const ts = row.ts; 444 const upid = row.upid; 445 globals.dispatch( 446 Actions.selectPerfSamples({ id: 0, upid, ts, type: 'perf' })); 447 } 448 449 private async selectFirstHeapProfile() { 450 const query = `select * from 451 (select distinct(ts) as ts, 'native' as type, 452 upid from heap_profile_allocation 453 union 454 select distinct(graph_sample_ts) as ts, 'graph' as type, upid from 455 heap_graph_object) order by ts limit 1`; 456 const profile = await assertExists(this.engine).query(query); 457 if (profile.numRows() !== 1) return; 458 const row = profile.firstRow({ ts: NUM, type: STR, upid: NUM }); 459 const ts = row.ts; 460 const type = row.type; 461 const upid = row.upid; 462 globals.dispatch(Actions.selectHeapProfile({ id: 0, upid, ts, type })); 463 } 464 465 private async listTracks() { 466 this.updateStatus('Loading tracks'); 467 const engine = assertExists<Engine>(this.engine); 468 const actions = await decideTracks(this.engineId, engine); 469 globals.dispatchMultiple(actions); 470 } 471 472 private async listThreads() { 473 this.updateStatus('Reading thread list'); 474 const query = `select 475 utid, 476 tid, 477 pid, 478 ifnull(thread.name, '') as threadName, 479 ifnull( 480 case when length(process.name) > 0 then process.name else null end, 481 thread.name) as procName, 482 process.cmdline as cmdline 483 from (select * from thread order by upid) as thread 484 left join (select * from process order by upid) as process 485 using(upid)`; 486 const result = await assertExists(this.engine).query(query); 487 const threads: ThreadDesc[] = []; 488 const it = result.iter({ 489 utid: NUM, 490 tid: NUM, 491 pid: NUM_NULL, 492 threadName: STR, 493 procName: STR_NULL, 494 cmdline: STR_NULL, 495 }); 496 for (; it.valid(); it.next()) { 497 const utid = it.utid; 498 const tid = it.tid; 499 const pid = it.pid === null ? undefined : it.pid; 500 const threadName = it.threadName; 501 const procName = it.procName === null ? undefined : it.procName; 502 const cmdline = it.cmdline === null ? undefined : it.cmdline; 503 threads.push({ utid, tid, threadName, pid, procName, cmdline }); 504 } 505 publishThreads(threads); 506 } 507 508 private async loadTimelineOverview(traceTime: TimeSpan) { 509 const engine = assertExists<Engine>(this.engine); 510 const numSteps = 100; 511 const stepSec = traceTime.duration / numSteps; 512 let hasSchedOverview = false; 513 for (let step = 0; step < numSteps; step++) { 514 this.updateStatus( 515 'Loading overview ' + 516 `${Math.round((step + 1) / numSteps * 1000) / 10}%`); 517 const startSec = traceTime.start + step * stepSec; 518 const startNs = toNsFloor(startSec); 519 const endSec = startSec + stepSec; 520 const endNs = toNsCeil(endSec); 521 522 // Sched overview. 523 const schedResult = await engine.query( 524 `select sum(dur)/${stepSec}/1e9 as load, cpu from sched ` + 525 `where ts >= ${startNs} and ts < ${endNs} and utid != 0 ` + 526 'group by cpu order by cpu'); 527 const schedData: { [key: string]: QuantizedLoad } = {}; 528 const it = schedResult.iter({ load: NUM, cpu: NUM }); 529 for (; it.valid(); it.next()) { 530 const load = it.load; 531 const cpu = it.cpu; 532 schedData[cpu] = { startSec, endSec, load }; 533 hasSchedOverview = true; 534 } 535 publishOverviewData(schedData); 536 } 537 538 if (hasSchedOverview) { 539 return; 540 } 541 542 // Slices overview. 543 const traceStartNs = toNs(traceTime.start); 544 const stepSecNs = toNs(stepSec); 545 const sliceResult = await engine.query(`select 546 bucket, 547 upid, 548 ifnull(sum(utid_sum) / cast(${stepSecNs} as float), 0) as load 549 from thread 550 inner join ( 551 select 552 ifnull(cast((ts - ${traceStartNs})/${stepSecNs} as int), 0) as bucket, 553 sum(dur) as utid_sum, 554 utid 555 from slice 556 inner join thread_track on slice.track_id = thread_track.id 557 group by bucket, utid 558 ) using(utid) 559 where upid is not null 560 group by bucket, upid`); 561 562 const slicesData: { [key: string]: QuantizedLoad[] } = {}; 563 const it = sliceResult.iter({ bucket: NUM, upid: NUM, load: NUM }); 564 for (; it.valid(); it.next()) { 565 const bucket = it.bucket; 566 const upid = it.upid; 567 const load = it.load; 568 569 const startSec = traceTime.start + stepSec * bucket; 570 const endSec = startSec + stepSec; 571 572 const upidStr = upid.toString(); 573 let loadArray = slicesData[upidStr]; 574 if (loadArray === undefined) { 575 loadArray = slicesData[upidStr] = []; 576 } 577 loadArray.push({ startSec, endSec, load }); 578 } 579 publishOverviewData(slicesData); 580 } 581 582 private async cacheCurrentTrace(): Promise<string> { 583 const engine = assertExists(this.engine); 584 const result = await engine.query(`select str_value as uuid from metadata 585 where name = 'trace_uuid'`); 586 if (result.numRows() === 0) { 587 // One of the cases covered is an empty trace. 588 return ''; 589 } 590 const traceUuid = result.firstRow({ uuid: STR }).uuid; 591 const engineConfig = assertExists(globals.state.engines[engine.id]); 592 if (!(await cacheTrace(engineConfig.source, traceUuid))) { 593 // If the trace is not cacheable (cacheable means it has been opened from 594 // URL or RPC) only append '?local_cache_key' to the URL, without the 595 // local_cache_key value. Doing otherwise would cause an error if the tab 596 // is discarded or the user hits the reload button because the trace is 597 // not in the cache. 598 return ''; 599 } 600 return traceUuid; 601 } 602 603 async initialiseHelperViews() { 604 const engine = assertExists<Engine>(this.engine); 605 606 this.updateStatus('Creating annotation counter track table'); 607 // Create the helper tables for all the annotations related data. 608 // NULL in min/max means "figure it out per track in the usual way". 609 await engine.query(` 610 CREATE TABLE annotation_counter_track( 611 id INTEGER PRIMARY KEY, 612 name STRING, 613 __metric_name STRING, 614 upid INTEGER, 615 min_value DOUBLE, 616 max_value DOUBLE 617 ); 618 `); 619 this.updateStatus('Creating annotation slice track table'); 620 await engine.query(` 621 CREATE TABLE annotation_slice_track( 622 id INTEGER PRIMARY KEY, 623 name STRING, 624 __metric_name STRING, 625 upid INTEGER, 626 group_name STRING 627 ); 628 `); 629 630 this.updateStatus('Creating annotation counter table'); 631 await engine.query(` 632 CREATE TABLE annotation_counter( 633 id BIG INT, 634 track_id INT, 635 ts BIG INT, 636 value DOUBLE, 637 PRIMARY KEY (track_id, ts) 638 ) WITHOUT ROWID; 639 `); 640 this.updateStatus('Creating annotation slice table'); 641 await engine.query(` 642 CREATE TABLE annotation_slice( 643 id INTEGER PRIMARY KEY, 644 track_id INT, 645 ts BIG INT, 646 dur BIG INT, 647 depth INT, 648 cat STRING, 649 name STRING, 650 UNIQUE(track_id, ts) 651 ); 652 `); 653 654 655 const availableMetrics = []; 656 const metricsResult = await engine.query('select name from trace_metrics'); 657 for (const it = metricsResult.iter({name: STR}); it.valid(); it.next()) { 658 availableMetrics.push(it.name); 659 } 660 globals.dispatch(Actions.setAvailableMetrics({availableMetrics})); 661 662 const availableMetricsSet = new Set<string>(availableMetrics); 663 for (const [flag, metric] of FLAGGED_METRICS) { 664 if (!flag.get() || !availableMetricsSet.has(metric)) { 665 continue; 666 } 667 668 this.updateStatus(`Computing ${metric} metric`); 669 try { 670 // We don't care about the actual result of metric here as we are just 671 // interested in the annotation tracks. 672 await engine.computeMetric([metric]); 673 } catch (e) { 674 if (e instanceof QueryError) { 675 publishMetricError('MetricError: ' + e.message); 676 continue; 677 } else { 678 throw e; 679 } 680 } 681 682 this.updateStatus(`Inserting data for ${metric} metric`); 683 try { 684 const result = await engine.query(`pragma table_info(${metric}_event)`); 685 let hasSliceName = false; 686 let hasDur = false; 687 let hasUpid = false; 688 let hasValue = false; 689 let hasGroupName = false; 690 const it = result.iter({ name: STR }); 691 for (; it.valid(); it.next()) { 692 const name = it.name; 693 hasSliceName = hasSliceName || name === 'slice_name'; 694 hasDur = hasDur || name === 'dur'; 695 hasUpid = hasUpid || name === 'upid'; 696 hasValue = hasValue || name === 'value'; 697 hasGroupName = hasGroupName || name === 'group_name'; 698 } 699 700 const upidColumnSelect = hasUpid ? 'upid' : '0 AS upid'; 701 const upidColumnWhere = hasUpid ? 'upid' : '0'; 702 const groupNameColumn = 703 hasGroupName ? 'group_name' : 'NULL AS group_name'; 704 if (hasSliceName && hasDur) { 705 await engine.query(` 706 INSERT INTO annotation_slice_track( 707 name, __metric_name, upid, group_name) 708 SELECT DISTINCT 709 track_name, 710 '${metric}' as metric_name, 711 ${upidColumnSelect}, 712 ${groupNameColumn} 713 FROM ${metric}_event 714 WHERE track_type = 'slice' 715 `); 716 await engine.query(` 717 INSERT INTO annotation_slice(track_id, ts, dur, depth, cat, name) 718 SELECT 719 t.id AS track_id, 720 ts, 721 dur, 722 0 AS depth, 723 a.track_name as cat, 724 slice_name AS name 725 FROM ${metric}_event a 726 JOIN annotation_slice_track t 727 ON a.track_name = t.name AND t.__metric_name = '${metric}' 728 ORDER BY t.id, ts 729 `); 730 } 731 732 if (hasValue) { 733 const minMax = await engine.query(` 734 SELECT 735 IFNULL(MIN(value), 0) as minValue, 736 IFNULL(MAX(value), 0) as maxValue 737 FROM ${metric}_event 738 WHERE ${upidColumnWhere} != 0`); 739 const row = minMax.firstRow({ minValue: NUM, maxValue: NUM }); 740 await engine.query(` 741 INSERT INTO annotation_counter_track( 742 name, __metric_name, min_value, max_value, upid) 743 SELECT DISTINCT 744 track_name, 745 '${metric}' as metric_name, 746 CASE ${upidColumnWhere} WHEN 0 THEN NULL ELSE ${row.minValue} END, 747 CASE ${upidColumnWhere} WHEN 0 THEN NULL ELSE ${row.maxValue} END, 748 ${upidColumnSelect} 749 FROM ${metric}_event 750 WHERE track_type = 'counter' 751 `); 752 await engine.query(` 753 INSERT INTO annotation_counter(id, track_id, ts, value) 754 SELECT 755 -1 as id, 756 t.id AS track_id, 757 ts, 758 value 759 FROM ${metric}_event a 760 JOIN annotation_counter_track t 761 ON a.track_name = t.name AND t.__metric_name = '${metric}' 762 ORDER BY t.id, ts 763 `); 764 } 765 } catch (e) { 766 if (e instanceof QueryError) { 767 publishMetricError('MetricError: ' + e.message); 768 } else { 769 throw e; 770 } 771 } 772 } 773 } 774 775 private updateStatus(msg: string): void { 776 globals.dispatch(Actions.updateStatus({ 777 msg, 778 timestamp: Date.now() / 1000, 779 })); 780 } 781} 782