| // Copyright (C) 2018 The Android Open Source Project | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //      http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 |  | 
 | import '../tracks/all_controller'; | 
 |  | 
 | import {assertExists, assertTrue} from '../base/logging'; | 
 | import { | 
 |   Actions, | 
 |   DeferredAction, | 
 | } from '../common/actions'; | 
 | import {TRACE_MARGIN_TIME_S} from '../common/constants'; | 
 | import {Engine, QueryError} from '../common/engine'; | 
 | import {HttpRpcEngine} from '../common/http_rpc_engine'; | 
 | import {slowlyCountRows} from '../common/query_iterator'; | 
 | import {EngineMode} from '../common/state'; | 
 | import {toNs, toNsCeil, toNsFloor} from '../common/time'; | 
 | import {TimeSpan} from '../common/time'; | 
 | import { | 
 |   createWasmEngine, | 
 |   destroyWasmEngine, | 
 |   WasmEngineProxy | 
 | } from '../common/wasm_engine_proxy'; | 
 | import {QuantizedLoad, ThreadDesc} from '../frontend/globals'; | 
 |  | 
 | import { | 
 |   CounterAggregationController | 
 | } from './aggregation/counter_aggregation_controller'; | 
 | import { | 
 |   CpuAggregationController | 
 | } from './aggregation/cpu_aggregation_controller'; | 
 | import { | 
 |   CpuByProcessAggregationController | 
 | } from './aggregation/cpu_by_process_aggregation_controller'; | 
 | import { | 
 |   SliceAggregationController | 
 | } from './aggregation/slice_aggregation_controller'; | 
 | import { | 
 |   ThreadAggregationController | 
 | } from './aggregation/thread_aggregation_controller'; | 
 | import {Child, Children, Controller} from './controller'; | 
 | import { | 
 |   CpuProfileController, | 
 |   CpuProfileControllerArgs | 
 | } from './cpu_profile_controller'; | 
 | import { | 
 |   FlowEventsController, | 
 |   FlowEventsControllerArgs | 
 | } from './flow_events_controller'; | 
 | import {globals} from './globals'; | 
 | import { | 
 |   HeapProfileController, | 
 |   HeapProfileControllerArgs | 
 | } from './heap_profile_controller'; | 
 | import {LoadingManager} from './loading_manager'; | 
 | import {LogsController} from './logs_controller'; | 
 | import {MetricsController} from './metrics_controller'; | 
 | import {QueryController, QueryControllerArgs} from './query_controller'; | 
 | import {SearchController} from './search_controller'; | 
 | import { | 
 |   SelectionController, | 
 |   SelectionControllerArgs | 
 | } from './selection_controller'; | 
 | import { | 
 |   TraceErrorController, | 
 | } from './trace_error_controller'; | 
 | import { | 
 |   TraceBufferStream, | 
 |   TraceFileStream, | 
 |   TraceHttpStream, | 
 |   TraceStream | 
 | } from './trace_stream'; | 
 | import {TrackControllerArgs, trackControllerRegistry} from './track_controller'; | 
 | import {decideTracks} from './track_decider'; | 
 |  | 
 | type States = 'init'|'loading_trace'|'ready'; | 
 |  | 
 | // TraceController handles handshakes with the frontend for everything that | 
 | // concerns a single trace. It owns the WASM trace processor engine, handles | 
 | // tracks data and SQL queries. There is one TraceController instance for each | 
 | // trace opened in the UI (for now only one trace is supported). | 
 | export class TraceController extends Controller<States> { | 
 |   private readonly engineId: string; | 
 |   private engine?: Engine; | 
 |  | 
 |   constructor(engineId: string) { | 
 |     super('init'); | 
 |     this.engineId = engineId; | 
 |   } | 
 |  | 
 |   onDestroy() { | 
 |     if (this.engine instanceof WasmEngineProxy) { | 
 |       destroyWasmEngine(this.engine.id); | 
 |     } | 
 |   } | 
 |  | 
 |   run() { | 
 |     const engineCfg = assertExists(globals.state.engines[this.engineId]); | 
 |     switch (this.state) { | 
 |       case 'init': | 
 |         this.loadTrace() | 
 |             .then(mode => { | 
 |               globals.dispatch(Actions.setEngineReady({ | 
 |                 engineId: this.engineId, | 
 |                 ready: true, | 
 |                 mode, | 
 |               })); | 
 |             }) | 
 |             .catch(err => { | 
 |               this.updateStatus(`${err}`); | 
 |               throw err; | 
 |             }); | 
 |         this.updateStatus('Opening trace'); | 
 |         this.setState('loading_trace'); | 
 |         break; | 
 |  | 
 |       case 'loading_trace': | 
 |         // Stay in this state until loadTrace() returns and marks the engine as | 
 |         // ready. | 
 |         if (this.engine === undefined || !engineCfg.ready) return; | 
 |         this.setState('ready'); | 
 |         break; | 
 |  | 
 |       case 'ready': | 
 |         // At this point we are ready to serve queries and handle tracks. | 
 |         const engine = assertExists(this.engine); | 
 |         assertTrue(engineCfg.ready); | 
 |         const childControllers: Children = []; | 
 |  | 
 |         // Create a TrackController for each track. | 
 |         for (const trackId of Object.keys(globals.state.tracks)) { | 
 |           const trackCfg = globals.state.tracks[trackId]; | 
 |           if (trackCfg.engineId !== this.engineId) continue; | 
 |           if (!trackControllerRegistry.has(trackCfg.kind)) continue; | 
 |           const trackCtlFactory = trackControllerRegistry.get(trackCfg.kind); | 
 |           const trackArgs: TrackControllerArgs = {trackId, engine}; | 
 |           childControllers.push(Child(trackId, trackCtlFactory, trackArgs)); | 
 |         } | 
 |  | 
 |         // Create a QueryController for each query. | 
 |         for (const queryId of Object.keys(globals.state.queries)) { | 
 |           const queryArgs: QueryControllerArgs = {queryId, engine}; | 
 |           childControllers.push(Child(queryId, QueryController, queryArgs)); | 
 |         } | 
 |  | 
 |         const selectionArgs: SelectionControllerArgs = {engine}; | 
 |         childControllers.push( | 
 |             Child('selection', SelectionController, selectionArgs)); | 
 |  | 
 |         const flowEventsArgs: FlowEventsControllerArgs = {engine}; | 
 |         childControllers.push( | 
 |             Child('flowEvents', FlowEventsController, flowEventsArgs)); | 
 |  | 
 |         const cpuProfileArgs: CpuProfileControllerArgs = {engine}; | 
 |         childControllers.push( | 
 |             Child('cpuProfile', CpuProfileController, cpuProfileArgs)); | 
 |  | 
 |         const heapProfileArgs: HeapProfileControllerArgs = {engine}; | 
 |         childControllers.push( | 
 |             Child('heapProfile', HeapProfileController, heapProfileArgs)); | 
 |         childControllers.push(Child( | 
 |             'cpu_aggregation', | 
 |             CpuAggregationController, | 
 |             {engine, kind: 'cpu_aggregation'})); | 
 |         childControllers.push(Child( | 
 |             'thread_aggregation', | 
 |             ThreadAggregationController, | 
 |             {engine, kind: 'thread_state_aggregation'})); | 
 |         childControllers.push(Child( | 
 |             'cpu_process_aggregation', | 
 |             CpuByProcessAggregationController, | 
 |             {engine, kind: 'cpu_by_process_aggregation'})); | 
 |         childControllers.push(Child( | 
 |             'slice_aggregation', | 
 |             SliceAggregationController, | 
 |             {engine, kind: 'slice_aggregation'})); | 
 |         childControllers.push(Child( | 
 |             'counter_aggregation', | 
 |             CounterAggregationController, | 
 |             {engine, kind: 'counter_aggregation'})); | 
 |         childControllers.push(Child('search', SearchController, { | 
 |           engine, | 
 |           app: globals, | 
 |         })); | 
 |  | 
 |         childControllers.push(Child('logs', LogsController, { | 
 |           engine, | 
 |           app: globals, | 
 |         })); | 
 |         childControllers.push( | 
 |             Child('traceError', TraceErrorController, {engine})); | 
 |         childControllers.push(Child('metrics', MetricsController, {engine})); | 
 |         return childControllers; | 
 |  | 
 |       default: | 
 |         throw new Error(`unknown state ${this.state}`); | 
 |     } | 
 |     return; | 
 |   } | 
 |  | 
 |   private async loadTrace(): Promise<EngineMode> { | 
 |     this.updateStatus('Creating trace processor'); | 
 |     // Check if there is any instance of the trace_processor_shell running in | 
 |     // HTTP RPC mode (i.e. trace_processor_shell -D). | 
 |     let engineMode: EngineMode; | 
 |     let useRpc = false; | 
 |     if (globals.state.newEngineMode === 'USE_HTTP_RPC_IF_AVAILABLE') { | 
 |       useRpc = (await HttpRpcEngine.checkConnection()).connected; | 
 |     } | 
 |     if (useRpc) { | 
 |       console.log('Opening trace using native accelerator over HTTP+RPC'); | 
 |       engineMode = 'HTTP_RPC'; | 
 |       const engine = | 
 |           new HttpRpcEngine(this.engineId, LoadingManager.getInstance); | 
 |       engine.errorHandler = (err) => { | 
 |         globals.dispatch( | 
 |             Actions.setEngineFailed({mode: 'HTTP_RPC', failure: `${err}`})); | 
 |         throw err; | 
 |       }; | 
 |       this.engine = engine; | 
 |     } else { | 
 |       console.log('Opening trace using built-in WASM engine'); | 
 |       engineMode = 'WASM'; | 
 |       this.engine = new WasmEngineProxy( | 
 |           this.engineId, | 
 |           createWasmEngine(this.engineId), | 
 |           LoadingManager.getInstance); | 
 |     } | 
 |  | 
 |     globals.dispatch(Actions.setEngineReady({ | 
 |       engineId: this.engineId, | 
 |       ready: false, | 
 |       mode: engineMode, | 
 |     })); | 
 |     const engineCfg = assertExists(globals.state.engines[this.engineId]); | 
 |     let traceStream: TraceStream|undefined; | 
 |     if (engineCfg.source.type === 'FILE') { | 
 |       traceStream = new TraceFileStream(engineCfg.source.file); | 
 |     } else if (engineCfg.source.type === 'ARRAY_BUFFER') { | 
 |       traceStream = new TraceBufferStream(engineCfg.source.buffer); | 
 |     } else if (engineCfg.source.type === 'URL') { | 
 |       traceStream = new TraceHttpStream(engineCfg.source.url); | 
 |     } else if (engineCfg.source.type === 'HTTP_RPC') { | 
 |       traceStream = undefined; | 
 |     } else { | 
 |       throw new Error(`Unknown source: ${JSON.stringify(engineCfg.source)}`); | 
 |     } | 
 |  | 
 |     // |traceStream| can be undefined in the case when we are using the external | 
 |     // HTTP+RPC endpoint and the trace processor instance has already loaded | 
 |     // a trace (because it was passed as a cmdline argument to | 
 |     // trace_processor_shell). In this case we don't want the UI to load any | 
 |     // file/stream and we just want to jump to the loading phase. | 
 |     if (traceStream !== undefined) { | 
 |       const tStart = performance.now(); | 
 |       for (;;) { | 
 |         const res = await traceStream.readChunk(); | 
 |         await this.engine.parse(res.data); | 
 |         const elapsed = (performance.now() - tStart) / 1000; | 
 |         let status = 'Loading trace '; | 
 |         if (res.bytesTotal > 0) { | 
 |           const progress = Math.round(res.bytesRead / res.bytesTotal * 100); | 
 |           status += `${progress}%`; | 
 |         } else { | 
 |           status += `${Math.round(res.bytesRead / 1e6)} MB`; | 
 |         } | 
 |         status += ` - ${Math.ceil(res.bytesRead / elapsed / 1e6)} MB/s`; | 
 |         this.updateStatus(status); | 
 |         if (res.eof) break; | 
 |       } | 
 |       await this.engine.notifyEof(); | 
 |     } else { | 
 |       assertTrue(this.engine instanceof HttpRpcEngine); | 
 |       await this.engine.restoreInitialTables(); | 
 |     } | 
 |  | 
 |     const traceTime = await this.engine.getTraceTimeBounds(); | 
 |     let startSec = traceTime.start; | 
 |     let endSec = traceTime.end; | 
 |     startSec -= TRACE_MARGIN_TIME_S; | 
 |     endSec += TRACE_MARGIN_TIME_S; | 
 |     const traceTimeState = { | 
 |       startSec, | 
 |       endSec, | 
 |     }; | 
 |     const actions: DeferredAction[] = [ | 
 |       Actions.setTraceTime(traceTimeState), | 
 |       Actions.navigate({route: '/viewer'}), | 
 |     ]; | 
 |  | 
 |     let visibleStartSec = startSec; | 
 |     let visibleEndSec = endSec; | 
 |     const mdTime = await this.engine.getTracingMetadataTimeBounds(); | 
 |     // make sure the bounds hold | 
 |     if (Math.max(visibleStartSec, mdTime.start - TRACE_MARGIN_TIME_S) < | 
 |         Math.min(visibleEndSec, mdTime.end + TRACE_MARGIN_TIME_S)) { | 
 |       visibleStartSec = | 
 |           Math.max(visibleStartSec, mdTime.start - TRACE_MARGIN_TIME_S); | 
 |       visibleEndSec = Math.min(visibleEndSec, mdTime.end + TRACE_MARGIN_TIME_S); | 
 |     } | 
 |  | 
 |     // We don't know the resolution at this point. However this will be | 
 |     // replaced in 50ms so a guess is fine. | 
 |     const resolution = (visibleStartSec - visibleEndSec) / 1000; | 
 |     actions.push(Actions.setVisibleTraceTime({ | 
 |       startSec: visibleStartSec, | 
 |       endSec: visibleEndSec, | 
 |       lastUpdate: Date.now() / 1000, | 
 |       resolution | 
 |     })); | 
 |  | 
 |     globals.dispatchMultiple(actions); | 
 |  | 
 |     // Make sure the helper views are available before we start adding tracks. | 
 |     await this.initialiseHelperViews(); | 
 |  | 
 |     { | 
 |       // When we reload from a permalink don't create extra tracks: | 
 |       const {pinnedTracks, tracks} = globals.state; | 
 |       if (!pinnedTracks.length && !Object.keys(tracks).length) { | 
 |         await this.listTracks(); | 
 |       } | 
 |     } | 
 |  | 
 |     await this.listThreads(); | 
 |     await this.loadTimelineOverview(traceTime); | 
 |  | 
 |     { | 
 |       const query = 'select to_ftrace(id) from raw limit 1'; | 
 |       const result = await assertExists(this.engine).query(query); | 
 |       const hasFtrace = !!slowlyCountRows(result); | 
 |       globals.publish('HasFtrace', hasFtrace); | 
 |     } | 
 |  | 
 |     globals.dispatch(Actions.sortThreadTracks({})); | 
 |     await this.selectFirstHeapProfile(); | 
 |  | 
 |     return engineMode; | 
 |   } | 
 |  | 
 |   private async selectFirstHeapProfile() { | 
 |     const query = `select * from | 
 |     (select distinct(ts) as ts, 'native' as type, | 
 |         upid from heap_profile_allocation | 
 |         union | 
 |         select distinct(graph_sample_ts) as ts, 'graph' as type, upid from | 
 |         heap_graph_object) order by ts limit 1`; | 
 |     const profile = await assertExists(this.engine).query(query); | 
 |     if (profile.numRecords !== 1) return; | 
 |     const ts = profile.columns[0].longValues![0]; | 
 |     const type = profile.columns[1].stringValues![0]; | 
 |     const upid = profile.columns[2].longValues![0]; | 
 |     globals.dispatch(Actions.selectHeapProfile({id: 0, upid, ts, type})); | 
 |   } | 
 |  | 
 |   private async listTracks() { | 
 |     this.updateStatus('Loading tracks'); | 
 |     const engine = assertExists<Engine>(this.engine); | 
 |     const actions = await decideTracks(this.engineId, engine); | 
 |     globals.dispatchMultiple(actions); | 
 |   } | 
 |  | 
 |   private async listThreads() { | 
 |     this.updateStatus('Reading thread list'); | 
 |     const sqlQuery = `select utid, tid, pid, thread.name, | 
 |         ifnull( | 
 |           case when length(process.name) > 0 then process.name else null end, | 
 |           thread.name), | 
 |         process.cmdline | 
 |         from (select * from thread order by upid) as thread | 
 |         left join (select * from process order by upid) as process | 
 |         using(upid)`; | 
 |     const threadRows = await assertExists(this.engine).query(sqlQuery); | 
 |     const threads: ThreadDesc[] = []; | 
 |     for (let i = 0; i < slowlyCountRows(threadRows); i++) { | 
 |       const utid = threadRows.columns[0].longValues![i]; | 
 |       const tid = threadRows.columns[1].longValues![i]; | 
 |       const pid = threadRows.columns[2].longValues![i]; | 
 |       const threadName = threadRows.columns[3].stringValues![i]; | 
 |       const procName = threadRows.columns[4].stringValues![i]; | 
 |       const cmdline = threadRows.columns[5].stringValues![i]; | 
 |       threads.push({utid, tid, threadName, pid, procName, cmdline}); | 
 |     }  // for (record ...) | 
 |     globals.publish('Threads', threads); | 
 |   } | 
 |  | 
 |   private async loadTimelineOverview(traceTime: TimeSpan) { | 
 |     const engine = assertExists<Engine>(this.engine); | 
 |     const numSteps = 100; | 
 |     const stepSec = traceTime.duration / numSteps; | 
 |     let hasSchedOverview = false; | 
 |     for (let step = 0; step < numSteps; step++) { | 
 |       this.updateStatus( | 
 |           'Loading overview ' + | 
 |           `${Math.round((step + 1) / numSteps * 1000) / 10}%`); | 
 |       const startSec = traceTime.start + step * stepSec; | 
 |       const startNs = toNsFloor(startSec); | 
 |       const endSec = startSec + stepSec; | 
 |       const endNs = toNsCeil(endSec); | 
 |  | 
 |       // Sched overview. | 
 |       const schedRows = await engine.query( | 
 |           `select sum(dur)/${stepSec}/1e9, cpu from sched ` + | 
 |           `where ts >= ${startNs} and ts < ${endNs} and utid != 0 ` + | 
 |           'group by cpu order by cpu'); | 
 |       const schedData: {[key: string]: QuantizedLoad} = {}; | 
 |       for (let i = 0; i < slowlyCountRows(schedRows); i++) { | 
 |         const load = schedRows.columns[0].doubleValues![i]; | 
 |         const cpu = schedRows.columns[1].longValues![i]; | 
 |         schedData[cpu] = {startSec, endSec, load}; | 
 |         hasSchedOverview = true; | 
 |       }  // for (record ...) | 
 |       globals.publish('OverviewData', schedData); | 
 |     }  // for (step ...) | 
 |  | 
 |     if (hasSchedOverview) { | 
 |       return; | 
 |     } | 
 |  | 
 |     // Slices overview. | 
 |     const traceStartNs = toNs(traceTime.start); | 
 |     const stepSecNs = toNs(stepSec); | 
 |     const sliceSummaryQuery = await engine.query(`select | 
 |            bucket, | 
 |            upid, | 
 |            sum(utid_sum) / cast(${stepSecNs} as float) as upid_sum | 
 |          from thread | 
 |          inner join ( | 
 |            select | 
 |              cast((ts - ${traceStartNs})/${stepSecNs} as int) as bucket, | 
 |              sum(dur) as utid_sum, | 
 |              utid | 
 |            from slice | 
 |            inner join thread_track on slice.track_id = thread_track.id | 
 |            group by bucket, utid | 
 |          ) using(utid) | 
 |          group by bucket, upid`); | 
 |  | 
 |     const slicesData: {[key: string]: QuantizedLoad[]} = {}; | 
 |     for (let i = 0; i < slowlyCountRows(sliceSummaryQuery); i++) { | 
 |       const bucket = sliceSummaryQuery.columns[0].longValues![i]; | 
 |       const upid = sliceSummaryQuery.columns[1].longValues![i]; | 
 |       const load = sliceSummaryQuery.columns[2].doubleValues![i]; | 
 |  | 
 |       const startSec = traceTime.start + stepSec * bucket; | 
 |       const endSec = startSec + stepSec; | 
 |  | 
 |       const upidStr = upid.toString(); | 
 |       let loadArray = slicesData[upidStr]; | 
 |       if (loadArray === undefined) { | 
 |         loadArray = slicesData[upidStr] = []; | 
 |       } | 
 |       loadArray.push({startSec, endSec, load}); | 
 |     } | 
 |     globals.publish('OverviewData', slicesData); | 
 |   } | 
 |  | 
 |   async initialiseHelperViews() { | 
 |     const engine = assertExists<Engine>(this.engine); | 
 |  | 
 |     this.updateStatus('Creating annotation counter track table'); | 
 |     // Create the helper tables for all the annotations related data. | 
 |     // NULL in min/max means "figure it out per track in the usual way". | 
 |     await engine.query(` | 
 |       CREATE TABLE annotation_counter_track( | 
 |         id INTEGER PRIMARY KEY, | 
 |         name STRING, | 
 |         __metric_name STRING, | 
 |         upid INTEGER, | 
 |         min_value DOUBLE, | 
 |         max_value DOUBLE | 
 |       ); | 
 |     `); | 
 |     this.updateStatus('Creating annotation slice track table'); | 
 |     await engine.query(` | 
 |       CREATE TABLE annotation_slice_track( | 
 |         id INTEGER PRIMARY KEY, | 
 |         name STRING, | 
 |         __metric_name STRING, | 
 |         upid INTEGER | 
 |       ); | 
 |     `); | 
 |  | 
 |     this.updateStatus('Creating annotation counter table'); | 
 |     await engine.query(` | 
 |       CREATE TABLE annotation_counter( | 
 |         id BIG INT, | 
 |         track_id INT, | 
 |         ts BIG INT, | 
 |         value DOUBLE, | 
 |         PRIMARY KEY (track_id, ts) | 
 |       ) WITHOUT ROWID; | 
 |     `); | 
 |     this.updateStatus('Creating annotation slice table'); | 
 |     await engine.query(` | 
 |       CREATE TABLE annotation_slice( | 
 |         id INTEGER PRIMARY KEY, | 
 |         track_id INT, | 
 |         ts BIG INT, | 
 |         dur BIG INT, | 
 |         depth INT, | 
 |         cat STRING, | 
 |         name STRING, | 
 |         UNIQUE(track_id, ts) | 
 |       ); | 
 |     `); | 
 |  | 
 |     for (const metric | 
 |              of ['android_startup', | 
 |                  'android_ion', | 
 |                  'android_dma_heap', | 
 |                  'android_thread_time_in_state', | 
 |                  'android_surfaceflinger', | 
 |                  'android_batt', | 
 |                  'android_sysui_cuj', | 
 |                  'android_jank']) { | 
 |       this.updateStatus(`Computing ${metric} metric`); | 
 |       try { | 
 |         // We don't care about the actual result of metric here as we are just | 
 |         // interested in the annotation tracks. | 
 |         await engine.computeMetric([metric]); | 
 |       } catch (e) { | 
 |         if (e instanceof QueryError) { | 
 |           globals.publish('MetricError', 'MetricError: ' + e.message); | 
 |           continue; | 
 |         } else { | 
 |           throw e; | 
 |         } | 
 |       } | 
 |  | 
 |       this.updateStatus(`Inserting data for ${metric} metric`); | 
 |       try { | 
 |         const result = await engine.query(`pragma table_info(${metric}_event)`); | 
 |         let hasSliceName = false; | 
 |         let hasDur = false; | 
 |         let hasUpid = false; | 
 |         let hasValue = false; | 
 |         for (let i = 0; i < slowlyCountRows(result); i++) { | 
 |           const name = result.columns[1].stringValues![i]; | 
 |           hasSliceName = hasSliceName || name === 'slice_name'; | 
 |           hasDur = hasDur || name === 'dur'; | 
 |           hasUpid = hasUpid || name === 'upid'; | 
 |           hasValue = hasValue || name === 'value'; | 
 |         } | 
 |  | 
 |         const upidColumnSelect = hasUpid ? 'upid' : '0 AS upid'; | 
 |         const upidColumnWhere = hasUpid ? 'upid' : '0'; | 
 |         if (hasSliceName && hasDur) { | 
 |           await engine.query(` | 
 |             INSERT INTO annotation_slice_track(name, __metric_name, upid) | 
 |             SELECT DISTINCT | 
 |               track_name, | 
 |               '${metric}' as metric_name, | 
 |               ${upidColumnSelect} | 
 |             FROM ${metric}_event | 
 |             WHERE track_type = 'slice' | 
 |           `); | 
 |           await engine.query(` | 
 |             INSERT INTO annotation_slice(track_id, ts, dur, depth, cat, name) | 
 |             SELECT | 
 |               t.id AS track_id, | 
 |               ts, | 
 |               dur, | 
 |               0 AS depth, | 
 |               a.track_name as cat, | 
 |               slice_name AS name | 
 |             FROM ${metric}_event a | 
 |             JOIN annotation_slice_track t | 
 |             ON a.track_name = t.name AND t.__metric_name = '${metric}' | 
 |             ORDER BY t.id, ts | 
 |           `); | 
 |         } | 
 |  | 
 |         if (hasValue) { | 
 |           const minMax = await engine.query(` | 
 |           SELECT MIN(value) as min_value, MAX(value) as max_value | 
 |           FROM ${metric}_event | 
 |           WHERE ${upidColumnWhere} != 0`); | 
 |           const min = minMax.columns[0].longValues![0]; | 
 |           const max = minMax.columns[1].longValues![0]; | 
 |           await engine.query(` | 
 |           INSERT INTO annotation_counter_track( | 
 |             name, __metric_name, min_value, max_value, upid) | 
 |           SELECT DISTINCT | 
 |             track_name, | 
 |             '${metric}' as metric_name, | 
 |             CASE ${upidColumnWhere} WHEN 0 THEN NULL ELSE ${min} END, | 
 |             CASE ${upidColumnWhere} WHEN 0 THEN NULL ELSE ${max} END, | 
 |             ${upidColumnSelect} | 
 |           FROM ${metric}_event | 
 |           WHERE track_type = 'counter' | 
 |         `); | 
 |           await engine.query(` | 
 |           INSERT INTO annotation_counter(id, track_id, ts, value) | 
 |           SELECT | 
 |             -1 as id, | 
 |             t.id AS track_id, | 
 |             ts, | 
 |             value | 
 |           FROM ${metric}_event a | 
 |           JOIN annotation_counter_track t | 
 |           ON a.track_name = t.name AND t.__metric_name = '${metric}' | 
 |           ORDER BY t.id, ts | 
 |         `); | 
 |         } | 
 |       } catch (e) { | 
 |         if (e instanceof QueryError) { | 
 |           globals.publish('MetricError', 'MetricError: ' + e.message); | 
 |         } else { | 
 |           throw e; | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   private updateStatus(msg: string): void { | 
 |     globals.dispatch(Actions.updateStatus({ | 
 |       msg, | 
 |       timestamp: Date.now() / 1000, | 
 |     })); | 
 |   } | 
 | } | 
 |  | 
 |  |