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 {BigintMath} from '../base/bigint_math'; 16import {assertExists, assertTrue} from '../base/logging'; 17import { 18 Actions, 19 DeferredAction, 20} from '../common/actions'; 21import {cacheTrace} from '../common/cache_manager'; 22import {Engine} from '../common/engine'; 23import {featureFlags, Flag, PERF_SAMPLE_FLAG} from '../common/feature_flags'; 24import { 25 HighPrecisionTime, 26 HighPrecisionTimeSpan, 27} from '../common/high_precision_time'; 28import {HttpRpcEngine} from '../common/http_rpc_engine'; 29import { 30 getEnabledMetatracingCategories, 31 isMetatracingEnabled, 32} from '../common/metatracing'; 33import { 34 LONG, 35 NUM, 36 NUM_NULL, 37 QueryError, 38 STR, 39 STR_NULL, 40} from '../common/query_result'; 41import {onSelectionChanged} from '../common/selection_observer'; 42import {defaultTraceTime, EngineMode, ProfileType} from '../common/state'; 43import {Span} from '../common/time'; 44import { 45 TPTime, 46 TPTimeSpan, 47} from '../common/time'; 48import {resetEngineWorker, WasmEngineProxy} from '../common/wasm_engine_proxy'; 49import {BottomTabList} from '../frontend/bottom_tab'; 50import { 51 FtraceStat, 52 globals, 53 QuantizedLoad, 54 ThreadDesc, 55} from '../frontend/globals'; 56import {showModal} from '../frontend/modal'; 57import { 58 clearOverviewData, 59 publishFtraceCounters, 60 publishMetricError, 61 publishOverviewData, 62 publishThreads, 63} from '../frontend/publish'; 64import {Router} from '../frontend/router'; 65 66import { 67 CounterAggregationController, 68} from './aggregation/counter_aggregation_controller'; 69import { 70 CpuAggregationController, 71} from './aggregation/cpu_aggregation_controller'; 72import { 73 CpuByProcessAggregationController, 74} from './aggregation/cpu_by_process_aggregation_controller'; 75import { 76 FrameAggregationController, 77} from './aggregation/frame_aggregation_controller'; 78import { 79 SliceAggregationController, 80} from './aggregation/slice_aggregation_controller'; 81import { 82 ThreadAggregationController, 83} from './aggregation/thread_aggregation_controller'; 84import {Child, Children, Controller} from './controller'; 85import { 86 CpuProfileController, 87 CpuProfileControllerArgs, 88} from './cpu_profile_controller'; 89import { 90 FlamegraphController, 91 FlamegraphControllerArgs, 92 profileType, 93} from './flamegraph_controller'; 94import { 95 FlowEventsController, 96 FlowEventsControllerArgs, 97} from './flow_events_controller'; 98import {FtraceController} from './ftrace_controller'; 99import {LoadingManager} from './loading_manager'; 100import {LogsController} from './logs_controller'; 101import {MetricsController} from './metrics_controller'; 102import { 103 PIVOT_TABLE_REDUX_FLAG, 104 PivotTableController, 105} from './pivot_table_controller'; 106import {SearchController} from './search_controller'; 107import { 108 SelectionController, 109 SelectionControllerArgs, 110} from './selection_controller'; 111import { 112 TraceErrorController, 113} from './trace_error_controller'; 114import { 115 TraceBufferStream, 116 TraceFileStream, 117 TraceHttpStream, 118 TraceStream, 119} from './trace_stream'; 120import {TrackControllerArgs, trackControllerRegistry} from './track_controller'; 121import {decideTracks} from './track_decider'; 122import {VisualisedArgController} from './visualised_args_controller'; 123 124type States = 'init' | 'loading_trace' | 'ready'; 125 126const METRICS = [ 127 'android_startup', 128 'android_ion', 129 'android_lmk', 130 'android_dma_heap', 131 'android_surfaceflinger', 132 'android_batt', 133 'android_other_traces', 134 'chrome_dropped_frames', 135 'chrome_long_latency', 136 'trace_metadata', 137 'android_trusty_workqueues', 138]; 139const FLAGGED_METRICS: Array<[Flag, string]> = METRICS.map((m) => { 140 const id = `forceMetric${m}`; 141 let name = m.split('_').join(' '); 142 name = name[0].toUpperCase() + name.slice(1); 143 name = 'Metric: ' + name; 144 const flag = featureFlags.register({ 145 id, 146 name, 147 description: `Overrides running the '${m}' metric at import time.`, 148 defaultValue: true, 149 }); 150 return [flag, m]; 151}); 152 153const ENABLE_CHROME_RELIABLE_RANGE_ZOOM_FLAG = featureFlags.register({ 154 id: 'enableChromeReliableRangeZoom', 155 name: 'Enable Chrome reliable range zoom', 156 description: 'Automatically zoom into the reliable range for Chrome traces', 157 defaultValue: false, 158}); 159 160const ENABLE_CHROME_RELIABLE_RANGE_ANNOTATION_FLAG = featureFlags.register({ 161 id: 'enableChromeReliableRangeAnnotation', 162 name: 'Enable Chrome reliable range annotation', 163 description: 'Automatically adds an annotation for the reliable range start', 164 defaultValue: false, 165}); 166 167// The following flags control TraceProcessor Config. 168const CROP_TRACK_EVENTS_FLAG = featureFlags.register({ 169 id: 'cropTrackEvents', 170 name: 'Crop track events', 171 description: 'Ignores track events outside of the range of interest', 172 defaultValue: false, 173}); 174const INGEST_FTRACE_IN_RAW_TABLE_FLAG = featureFlags.register({ 175 id: 'ingestFtraceInRawTable', 176 name: 'Ingest ftrace in raw table', 177 description: 'Enables ingestion of typed ftrace events into the raw table', 178 defaultValue: true, 179}); 180const ANALYZE_TRACE_PROTO_CONTENT_FLAG = featureFlags.register({ 181 id: 'analyzeTraceProtoContent', 182 name: 'Analyze trace proto content', 183 description: 'Enables trace proto content analysis', 184 defaultValue: false, 185}); 186 187// A local storage key where the indication that JSON warning has been shown is 188// stored. 189const SHOWN_JSON_WARNING_KEY = 'shownJsonWarning'; 190 191function showJsonWarning() { 192 showModal({ 193 title: 'Warning', 194 content: 195 m('div', 196 m('span', 197 'Perfetto UI features are limited for JSON traces. ', 198 'We recommend recording ', 199 m('a', 200 {href: 'https://perfetto.dev/docs/quickstart/chrome-tracing'}, 201 'proto-format traces'), 202 ' from Chrome.'), 203 m('br')), 204 buttons: [], 205 }); 206} 207 208// TraceController handles handshakes with the frontend for everything that 209// concerns a single trace. It owns the WASM trace processor engine, handles 210// tracks data and SQL queries. There is one TraceController instance for each 211// trace opened in the UI (for now only one trace is supported). 212export class TraceController extends Controller<States> { 213 private readonly engineId: string; 214 private engine?: Engine; 215 216 constructor(engineId: string) { 217 super('init'); 218 this.engineId = engineId; 219 } 220 221 run() { 222 const engineCfg = assertExists(globals.state.engine); 223 switch (this.state) { 224 case 'init': 225 this.loadTrace() 226 .then((mode) => { 227 globals.dispatch(Actions.setEngineReady({ 228 engineId: this.engineId, 229 ready: true, 230 mode, 231 })); 232 }) 233 .catch((err) => { 234 this.updateStatus(`${err}`); 235 throw err; 236 }); 237 this.updateStatus('Opening trace'); 238 this.setState('loading_trace'); 239 break; 240 241 case 'loading_trace': 242 // Stay in this state until loadTrace() returns and marks the engine as 243 // ready. 244 if (this.engine === undefined || !engineCfg.ready) return; 245 this.setState('ready'); 246 break; 247 248 case 'ready': 249 // At this point we are ready to serve queries and handle tracks. 250 const engine = assertExists(this.engine); 251 const childControllers: Children = []; 252 253 // Create a TrackController for each track. 254 for (const trackId of Object.keys(globals.state.tracks)) { 255 const trackCfg = globals.state.tracks[trackId]; 256 if (trackCfg.engineId !== this.engineId) continue; 257 if (!trackControllerRegistry.has(trackCfg.kind)) continue; 258 const trackCtlFactory = trackControllerRegistry.get(trackCfg.kind); 259 const trackArgs: TrackControllerArgs = {trackId, engine}; 260 childControllers.push(Child(trackId, trackCtlFactory, trackArgs)); 261 } 262 263 for (const argName of globals.state.visualisedArgs) { 264 childControllers.push( 265 Child(argName, VisualisedArgController, {argName, engine})); 266 } 267 268 const selectionArgs: SelectionControllerArgs = {engine}; 269 childControllers.push( 270 Child('selection', SelectionController, selectionArgs)); 271 272 const flowEventsArgs: FlowEventsControllerArgs = {engine}; 273 childControllers.push( 274 Child('flowEvents', FlowEventsController, flowEventsArgs)); 275 276 const cpuProfileArgs: CpuProfileControllerArgs = {engine}; 277 childControllers.push( 278 Child('cpuProfile', CpuProfileController, cpuProfileArgs)); 279 280 const flamegraphArgs: FlamegraphControllerArgs = {engine}; 281 childControllers.push( 282 Child('flamegraph', FlamegraphController, flamegraphArgs)); 283 childControllers.push(Child( 284 'cpu_aggregation', 285 CpuAggregationController, 286 {engine, kind: 'cpu_aggregation'})); 287 childControllers.push(Child( 288 'thread_aggregation', 289 ThreadAggregationController, 290 {engine, kind: 'thread_state_aggregation'})); 291 childControllers.push(Child( 292 'cpu_process_aggregation', 293 CpuByProcessAggregationController, 294 {engine, kind: 'cpu_by_process_aggregation'})); 295 if (!PIVOT_TABLE_REDUX_FLAG.get()) { 296 // Pivot table is supposed to handle the use cases the slice 297 // aggregation panel is used right now. When a flag to use pivot 298 // tables is enabled, do not add slice aggregation controller. 299 childControllers.push(Child( 300 'slice_aggregation', 301 SliceAggregationController, 302 {engine, kind: 'slice_aggregation'})); 303 } 304 childControllers.push(Child( 305 'counter_aggregation', 306 CounterAggregationController, 307 {engine, kind: 'counter_aggregation'})); 308 childControllers.push(Child( 309 'frame_aggregation', 310 FrameAggregationController, 311 {engine, kind: 'frame_aggregation'})); 312 childControllers.push(Child('search', SearchController, { 313 engine, 314 app: globals, 315 })); 316 childControllers.push( 317 Child('pivot_table', PivotTableController, {engine})); 318 319 childControllers.push(Child('logs', LogsController, { 320 engine, 321 app: globals, 322 })); 323 324 childControllers.push( 325 Child('ftrace', FtraceController, {engine, app: globals})); 326 327 childControllers.push( 328 Child('traceError', TraceErrorController, {engine})); 329 childControllers.push(Child('metrics', MetricsController, {engine})); 330 331 return childControllers; 332 333 default: 334 throw new Error(`unknown state ${this.state}`); 335 } 336 return; 337 } 338 339 onDestroy() { 340 globals.engines.delete(this.engineId); 341 } 342 343 private async loadTrace(): Promise<EngineMode> { 344 this.updateStatus('Creating trace processor'); 345 // Check if there is any instance of the trace_processor_shell running in 346 // HTTP RPC mode (i.e. trace_processor_shell -D). 347 let engineMode: EngineMode; 348 let useRpc = false; 349 if (globals.state.newEngineMode === 'USE_HTTP_RPC_IF_AVAILABLE') { 350 useRpc = (await HttpRpcEngine.checkConnection()).connected; 351 } 352 let engine; 353 if (useRpc) { 354 console.log('Opening trace using native accelerator over HTTP+RPC'); 355 engineMode = 'HTTP_RPC'; 356 engine = new HttpRpcEngine(this.engineId, LoadingManager.getInstance); 357 engine.errorHandler = (err) => { 358 globals.dispatch( 359 Actions.setEngineFailed({mode: 'HTTP_RPC', failure: `${err}`})); 360 throw err; 361 }; 362 } else { 363 console.log('Opening trace using built-in WASM engine'); 364 engineMode = 'WASM'; 365 const enginePort = resetEngineWorker(); 366 engine = new WasmEngineProxy( 367 this.engineId, enginePort, LoadingManager.getInstance); 368 engine.resetTraceProcessor({ 369 cropTrackEvents: CROP_TRACK_EVENTS_FLAG.get(), 370 ingestFtraceInRawTable: INGEST_FTRACE_IN_RAW_TABLE_FLAG.get(), 371 analyzeTraceProtoContent: ANALYZE_TRACE_PROTO_CONTENT_FLAG.get(), 372 }); 373 } 374 this.engine = engine; 375 376 if (isMetatracingEnabled()) { 377 this.engine.enableMetatrace( 378 assertExists(getEnabledMetatracingCategories())); 379 } 380 globals.bottomTabList = new BottomTabList(engine.getProxy('BottomTabList')); 381 382 globals.engines.set(this.engineId, engine); 383 globals.dispatch(Actions.setEngineReady({ 384 engineId: this.engineId, 385 ready: false, 386 mode: engineMode, 387 })); 388 const engineCfg = assertExists(globals.state.engine); 389 assertTrue(engineCfg.id === this.engineId); 390 let traceStream: TraceStream | undefined; 391 if (engineCfg.source.type === 'FILE') { 392 traceStream = new TraceFileStream(engineCfg.source.file); 393 } else if (engineCfg.source.type === 'ARRAY_BUFFER') { 394 traceStream = new TraceBufferStream(engineCfg.source.buffer); 395 } else if (engineCfg.source.type === 'URL') { 396 traceStream = new TraceHttpStream(engineCfg.source.url); 397 } else if (engineCfg.source.type === 'HTTP_RPC') { 398 traceStream = undefined; 399 } else { 400 throw new Error(`Unknown source: ${JSON.stringify(engineCfg.source)}`); 401 } 402 403 // |traceStream| can be undefined in the case when we are using the external 404 // HTTP+RPC endpoint and the trace processor instance has already loaded 405 // a trace (because it was passed as a cmdline argument to 406 // trace_processor_shell). In this case we don't want the UI to load any 407 // file/stream and we just want to jump to the loading phase. 408 if (traceStream !== undefined) { 409 const tStart = performance.now(); 410 for (; ;) { 411 const res = await traceStream.readChunk(); 412 await this.engine.parse(res.data); 413 const elapsed = (performance.now() - tStart) / 1000; 414 let status = 'Loading trace '; 415 if (res.bytesTotal > 0) { 416 const progress = Math.round(res.bytesRead / res.bytesTotal * 100); 417 status += `${progress}%`; 418 } else { 419 status += `${Math.round(res.bytesRead / 1e6)} MB`; 420 } 421 status += ` - ${Math.ceil(res.bytesRead / elapsed / 1e6)} MB/s`; 422 this.updateStatus(status); 423 if (res.eof) break; 424 } 425 await this.engine.notifyEof(); 426 } else { 427 assertTrue(this.engine instanceof HttpRpcEngine); 428 await this.engine.restoreInitialTables(); 429 } 430 431 // traceUuid will be '' if the trace is not cacheable (URL or RPC). 432 const traceUuid = await this.cacheCurrentTrace(); 433 434 const traceTime = await this.engine.getTraceTimeBounds(); 435 const start = traceTime.start; 436 const end = traceTime.end; 437 const traceTimeState = { 438 start, 439 end, 440 }; 441 442 const shownJsonWarning = 443 window.localStorage.getItem(SHOWN_JSON_WARNING_KEY) !== null; 444 445 // Show warning if the trace is in JSON format. 446 const query = `select str_value from metadata where name = 'trace_type'`; 447 const result = await assertExists(this.engine).query(query); 448 const traceType = result.firstRow({str_value: STR}).str_value; 449 const isJsonTrace = traceType == 'json'; 450 if (!shownJsonWarning) { 451 // When in embedded mode, the host app will control which trace format 452 // it passes to Perfetto, so we don't need to show this warning. 453 if (isJsonTrace && !globals.embeddedMode) { 454 showJsonWarning(); 455 // Save that the warning has been shown. Value is irrelevant since only 456 // the presence of key is going to be checked. 457 window.localStorage.setItem(SHOWN_JSON_WARNING_KEY, 'true'); 458 } 459 } 460 461 const emptyOmniboxState = { 462 omnibox: '', 463 mode: globals.state.omniboxState.mode || 'SEARCH', 464 }; 465 466 const actions: DeferredAction[] = [ 467 Actions.setOmnibox(emptyOmniboxState), 468 Actions.setTraceUuid({traceUuid}), 469 Actions.setTraceTime(traceTimeState), 470 ]; 471 472 const visibleTimeSpan = await computeVisibleTime( 473 traceTime.start, traceTime.end, isJsonTrace, this.engine); 474 // We don't know the resolution at this point. However this will be 475 // replaced in 50ms so a guess is fine. 476 const resolution = visibleTimeSpan.duration.divide(1000).toTPTime(); 477 actions.push(Actions.setVisibleTraceTime({ 478 start: visibleTimeSpan.start.toTPTime(), 479 end: visibleTimeSpan.end.toTPTime(), 480 lastUpdate: Date.now() / 1000, 481 resolution: BigintMath.max(resolution, 1n), 482 })); 483 484 globals.dispatchMultiple(actions); 485 Router.navigate(`#!/viewer?local_cache_key=${traceUuid}`); 486 487 // Make sure the helper views are available before we start adding tracks. 488 await this.initialiseHelperViews(); 489 490 { 491 // When we reload from a permalink don't create extra tracks: 492 const {pinnedTracks, tracks} = globals.state; 493 if (!pinnedTracks.length && !Object.keys(tracks).length) { 494 await this.listTracks(); 495 } 496 } 497 498 await this.listThreads(); 499 await this.loadTimelineOverview(traceTime); 500 501 { 502 // Pull out the counts ftrace events by name 503 const query = `select 504 name, 505 count(name) as cnt 506 from ftrace_event 507 group by name 508 order by cnt desc`; 509 const result = await assertExists(this.engine).query(query); 510 const counters: FtraceStat[] = []; 511 const it = result.iter({name: STR, cnt: NUM}); 512 for (let row = 0; it.valid(); it.next(), row++) { 513 counters.push({name: it.name, count: it.cnt}); 514 } 515 publishFtraceCounters(counters); 516 } 517 518 globals.dispatch(Actions.sortThreadTracks({})); 519 globals.dispatch(Actions.maybeExpandOnlyTrackGroup({})); 520 521 await this.selectFirstHeapProfile(); 522 if (PERF_SAMPLE_FLAG.get()) { 523 await this.selectPerfSample(); 524 } 525 526 // If the trace was shared via a permalink, it might already have a 527 // selection. Emit onSelectionChanged to ensure that the components (like 528 // current selection details) react to it. 529 if (globals.state.currentSelection !== null) { 530 onSelectionChanged(globals.state.currentSelection, undefined); 531 } 532 533 // Trace Processor doesn't support the reliable range feature for JSON 534 // traces. 535 if (!isJsonTrace && ENABLE_CHROME_RELIABLE_RANGE_ANNOTATION_FLAG.get()) { 536 const reliableRangeStart = await computeTraceReliableRangeStart(engine); 537 if (reliableRangeStart > 0) { 538 globals.dispatch(Actions.addAutomaticNote({ 539 timestamp: reliableRangeStart, 540 color: '#ff0000', 541 text: 'Reliable Range Start', 542 })); 543 } 544 } 545 546 return engineMode; 547 } 548 549 private async selectPerfSample() { 550 const query = `select upid 551 from perf_sample 552 join thread using (utid) 553 where callsite_id is not null 554 order by ts desc limit 1`; 555 const profile = await assertExists(this.engine).query(query); 556 if (profile.numRows() !== 1) return; 557 const row = profile.firstRow({upid: NUM}); 558 const upid = row.upid; 559 const leftTs = globals.state.traceTime.start; 560 const rightTs = globals.state.traceTime.end; 561 globals.dispatch(Actions.selectPerfSamples( 562 {id: 0, upid, leftTs, rightTs, type: ProfileType.PERF_SAMPLE})); 563 } 564 565 private async selectFirstHeapProfile() { 566 const query = `select * from ( 567 select 568 min(ts) AS ts, 569 'heap_profile:' || group_concat(distinct heap_name) AS type, 570 upid 571 from heap_profile_allocation 572 group by upid 573 union 574 select distinct graph_sample_ts as ts, 'graph' as type, upid 575 from heap_graph_object) 576 order by ts limit 1`; 577 const profile = await assertExists(this.engine).query(query); 578 if (profile.numRows() !== 1) return; 579 const row = profile.firstRow({ts: LONG, type: STR, upid: NUM}); 580 const ts = row.ts; 581 const type = profileType(row.type); 582 const upid = row.upid; 583 globals.dispatch(Actions.selectHeapProfile({id: 0, upid, ts, type})); 584 } 585 586 private async listTracks() { 587 this.updateStatus('Loading tracks'); 588 const engine = assertExists<Engine>(this.engine); 589 const actions = await decideTracks(this.engineId, engine); 590 globals.dispatchMultiple(actions); 591 } 592 593 private async listThreads() { 594 this.updateStatus('Reading thread list'); 595 const query = `select 596 utid, 597 tid, 598 pid, 599 ifnull(thread.name, '') as threadName, 600 ifnull( 601 case when length(process.name) > 0 then process.name else null end, 602 thread.name) as procName, 603 process.cmdline as cmdline 604 from (select * from thread order by upid) as thread 605 left join (select * from process order by upid) as process 606 using(upid)`; 607 const result = await assertExists(this.engine).query(query); 608 const threads: ThreadDesc[] = []; 609 const it = result.iter({ 610 utid: NUM, 611 tid: NUM, 612 pid: NUM_NULL, 613 threadName: STR, 614 procName: STR_NULL, 615 cmdline: STR_NULL, 616 }); 617 for (; it.valid(); it.next()) { 618 const utid = it.utid; 619 const tid = it.tid; 620 const pid = it.pid === null ? undefined : it.pid; 621 const threadName = it.threadName; 622 const procName = it.procName === null ? undefined : it.procName; 623 const cmdline = it.cmdline === null ? undefined : it.cmdline; 624 threads.push({utid, tid, threadName, pid, procName, cmdline}); 625 } 626 publishThreads(threads); 627 } 628 629 private async loadTimelineOverview(trace: Span<TPTime>) { 630 clearOverviewData(); 631 632 const engine = assertExists<Engine>(this.engine); 633 const stepSize = BigintMath.max(1n, trace.duration / 100n); 634 let hasSchedOverview = false; 635 for (let start = trace.start; start < trace.end; start += stepSize) { 636 const progress = start - trace.start; 637 const ratio = Number(progress) / Number(trace.duration); 638 this.updateStatus( 639 'Loading overview ' + 640 `${Math.round(ratio * 100)}%`); 641 const end = start + stepSize; 642 643 // Sched overview. 644 const schedResult = await engine.query( 645 `select cast(sum(dur) as float)/${ 646 stepSize} as load, cpu from sched ` + 647 `where ts >= ${start} and ts < ${end} and utid != 0 ` + 648 'group by cpu order by cpu'); 649 const schedData: {[key: string]: QuantizedLoad} = {}; 650 const it = schedResult.iter({load: NUM, cpu: NUM}); 651 for (; it.valid(); it.next()) { 652 const load = it.load; 653 const cpu = it.cpu; 654 schedData[cpu] = {start, end, load}; 655 hasSchedOverview = true; 656 } 657 publishOverviewData(schedData); 658 } 659 660 if (hasSchedOverview) { 661 return; 662 } 663 664 // Slices overview. 665 const sliceResult = await engine.query(`select 666 bucket, 667 upid, 668 ifnull(sum(utid_sum) / cast(${stepSize} as float), 0) as load 669 from thread 670 inner join ( 671 select 672 ifnull(cast((ts - ${trace.start})/${ 673 stepSize} as int), 0) as bucket, 674 sum(dur) as utid_sum, 675 utid 676 from slice 677 inner join thread_track on slice.track_id = thread_track.id 678 group by bucket, utid 679 ) using(utid) 680 where upid is not null 681 group by bucket, upid`); 682 683 const slicesData: {[key: string]: QuantizedLoad[]} = {}; 684 const it = sliceResult.iter({bucket: LONG, upid: NUM, load: NUM}); 685 for (; it.valid(); it.next()) { 686 const bucket = it.bucket; 687 const upid = it.upid; 688 const load = it.load; 689 690 const start = trace.start + stepSize * bucket; 691 const end = start + stepSize; 692 693 const upidStr = upid.toString(); 694 let loadArray = slicesData[upidStr]; 695 if (loadArray === undefined) { 696 loadArray = slicesData[upidStr] = []; 697 } 698 loadArray.push({start, end, load}); 699 } 700 publishOverviewData(slicesData); 701 } 702 703 private async cacheCurrentTrace(): Promise<string> { 704 const engine = assertExists(this.engine); 705 const result = await engine.query(`select str_value as uuid from metadata 706 where name = 'trace_uuid'`); 707 if (result.numRows() === 0) { 708 // One of the cases covered is an empty trace. 709 return ''; 710 } 711 const traceUuid = result.firstRow({uuid: STR}).uuid; 712 const engineConfig = assertExists(globals.state.engine); 713 assertTrue(engineConfig.id === this.engineId); 714 if (!(await cacheTrace(engineConfig.source, traceUuid))) { 715 // If the trace is not cacheable (cacheable means it has been opened from 716 // URL or RPC) only append '?local_cache_key' to the URL, without the 717 // local_cache_key value. Doing otherwise would cause an error if the tab 718 // is discarded or the user hits the reload button because the trace is 719 // not in the cache. 720 return ''; 721 } 722 return traceUuid; 723 } 724 725 async initialiseHelperViews() { 726 const engine = assertExists<Engine>(this.engine); 727 728 this.updateStatus('Creating annotation counter track table'); 729 // Create the helper tables for all the annotations related data. 730 // NULL in min/max means "figure it out per track in the usual way". 731 await engine.query(` 732 CREATE TABLE annotation_counter_track( 733 id INTEGER PRIMARY KEY, 734 name STRING, 735 __metric_name STRING, 736 upid INTEGER, 737 min_value DOUBLE, 738 max_value DOUBLE 739 ); 740 `); 741 this.updateStatus('Creating annotation slice track table'); 742 await engine.query(` 743 CREATE TABLE annotation_slice_track( 744 id INTEGER PRIMARY KEY, 745 name STRING, 746 __metric_name STRING, 747 upid INTEGER, 748 group_name STRING 749 ); 750 `); 751 752 this.updateStatus('Creating annotation counter table'); 753 await engine.query(` 754 CREATE TABLE annotation_counter( 755 id BIGINT, 756 track_id INT, 757 ts BIGINT, 758 value DOUBLE, 759 PRIMARY KEY (track_id, ts) 760 ) WITHOUT ROWID; 761 `); 762 this.updateStatus('Creating annotation slice table'); 763 await engine.query(` 764 CREATE TABLE annotation_slice( 765 id INTEGER PRIMARY KEY, 766 track_id INT, 767 ts BIGINT, 768 dur BIGINT, 769 thread_dur BIGINT, 770 depth INT, 771 cat STRING, 772 name STRING, 773 UNIQUE(track_id, ts) 774 ); 775 `); 776 777 778 const availableMetrics = []; 779 const metricsResult = await engine.query('select name from trace_metrics'); 780 for (const it = metricsResult.iter({name: STR}); it.valid(); it.next()) { 781 availableMetrics.push(it.name); 782 } 783 globals.dispatch(Actions.setAvailableMetrics({availableMetrics})); 784 785 const availableMetricsSet = new Set<string>(availableMetrics); 786 for (const [flag, metric] of FLAGGED_METRICS) { 787 if (!flag.get() || !availableMetricsSet.has(metric)) { 788 continue; 789 } 790 791 this.updateStatus(`Computing ${metric} metric`); 792 try { 793 // We don't care about the actual result of metric here as we are just 794 // interested in the annotation tracks. 795 await engine.computeMetric([metric]); 796 } catch (e) { 797 if (e instanceof QueryError) { 798 publishMetricError('MetricError: ' + e.message); 799 continue; 800 } else { 801 throw e; 802 } 803 } 804 805 this.updateStatus(`Inserting data for ${metric} metric`); 806 try { 807 const result = await engine.query(`pragma table_info(${metric}_event)`); 808 let hasSliceName = false; 809 let hasDur = false; 810 let hasUpid = false; 811 let hasValue = false; 812 let hasGroupName = false; 813 const it = result.iter({name: STR}); 814 for (; it.valid(); it.next()) { 815 const name = it.name; 816 hasSliceName = hasSliceName || name === 'slice_name'; 817 hasDur = hasDur || name === 'dur'; 818 hasUpid = hasUpid || name === 'upid'; 819 hasValue = hasValue || name === 'value'; 820 hasGroupName = hasGroupName || name === 'group_name'; 821 } 822 823 const upidColumnSelect = hasUpid ? 'upid' : '0 AS upid'; 824 const upidColumnWhere = hasUpid ? 'upid' : '0'; 825 const groupNameColumn = 826 hasGroupName ? 'group_name' : 'NULL AS group_name'; 827 if (hasSliceName && hasDur) { 828 await engine.query(` 829 INSERT INTO annotation_slice_track( 830 name, __metric_name, upid, group_name) 831 SELECT DISTINCT 832 track_name, 833 '${metric}' as metric_name, 834 ${upidColumnSelect}, 835 ${groupNameColumn} 836 FROM ${metric}_event 837 WHERE track_type = 'slice' 838 `); 839 await engine.query(` 840 INSERT INTO annotation_slice( 841 track_id, ts, dur, thread_dur, depth, cat, name 842 ) 843 SELECT 844 t.id AS track_id, 845 ts, 846 dur, 847 NULL as thread_dur, 848 0 AS depth, 849 a.track_name as cat, 850 slice_name AS name 851 FROM ${metric}_event a 852 JOIN annotation_slice_track t 853 ON a.track_name = t.name AND t.__metric_name = '${metric}' 854 ORDER BY t.id, ts 855 `); 856 } 857 858 if (hasValue) { 859 const minMax = await engine.query(` 860 SELECT 861 IFNULL(MIN(value), 0) as minValue, 862 IFNULL(MAX(value), 0) as maxValue 863 FROM ${metric}_event 864 WHERE ${upidColumnWhere} != 0`); 865 const row = minMax.firstRow({minValue: NUM, maxValue: NUM}); 866 await engine.query(` 867 INSERT INTO annotation_counter_track( 868 name, __metric_name, min_value, max_value, upid) 869 SELECT DISTINCT 870 track_name, 871 '${metric}' as metric_name, 872 CASE ${upidColumnWhere} WHEN 0 THEN NULL ELSE ${row.minValue} END, 873 CASE ${upidColumnWhere} WHEN 0 THEN NULL ELSE ${row.maxValue} END, 874 ${upidColumnSelect} 875 FROM ${metric}_event 876 WHERE track_type = 'counter' 877 `); 878 await engine.query(` 879 INSERT INTO annotation_counter(id, track_id, ts, value) 880 SELECT 881 -1 as id, 882 t.id AS track_id, 883 ts, 884 value 885 FROM ${metric}_event a 886 JOIN annotation_counter_track t 887 ON a.track_name = t.name AND t.__metric_name = '${metric}' 888 ORDER BY t.id, ts 889 `); 890 } 891 } catch (e) { 892 if (e instanceof QueryError) { 893 publishMetricError('MetricError: ' + e.message); 894 } else { 895 throw e; 896 } 897 } 898 } 899 } 900 901 private updateStatus(msg: string): void { 902 globals.dispatch(Actions.updateStatus({ 903 msg, 904 timestamp: Date.now() / 1000, 905 })); 906 } 907} 908 909async function computeTraceReliableRangeStart(engine: Engine): Promise<TPTime> { 910 const result = 911 await engine.query(`SELECT RUN_METRIC('chrome/chrome_reliable_range.sql'); 912 SELECT start FROM chrome_reliable_range`); 913 const bounds = result.firstRow({start: LONG}); 914 return bounds.start; 915} 916 917async function computeVisibleTime( 918 traceStart: TPTime, traceEnd: TPTime, isJsonTrace: boolean, engine: Engine): 919 Promise<Span<HighPrecisionTime>> { 920 // if we have non-default visible state, update the visible time to it 921 const previousVisibleState = globals.stateVisibleTime(); 922 const defaultTraceSpan = 923 new TPTimeSpan(defaultTraceTime.start, defaultTraceTime.end); 924 if (!(previousVisibleState.start === defaultTraceSpan.start && 925 previousVisibleState.end === defaultTraceSpan.end) && 926 (previousVisibleState.start >= traceStart && 927 previousVisibleState.end <= traceEnd)) { 928 return HighPrecisionTimeSpan.fromTpTime( 929 previousVisibleState.start, previousVisibleState.end); 930 } 931 932 // initialise visible time to the trace time bounds 933 let visibleStartSec = traceStart; 934 let visibleEndSec = traceEnd; 935 936 // compare start and end with metadata computed by the trace processor 937 const mdTime = await engine.getTracingMetadataTimeBounds(); 938 // make sure the bounds hold 939 if (BigintMath.max(visibleStartSec, mdTime.start) < 940 BigintMath.min(visibleEndSec, mdTime.end)) { 941 visibleStartSec = BigintMath.max(visibleStartSec, mdTime.start); 942 visibleEndSec = BigintMath.min(visibleEndSec, mdTime.end); 943 } 944 945 // Trace Processor doesn't support the reliable range feature for JSON 946 // traces. 947 if (!isJsonTrace && ENABLE_CHROME_RELIABLE_RANGE_ZOOM_FLAG.get()) { 948 const reliableRangeStart = await computeTraceReliableRangeStart(engine); 949 visibleStartSec = BigintMath.max(visibleStartSec, reliableRangeStart); 950 } 951 952 return HighPrecisionTimeSpan.fromTpTime(visibleStartSec, visibleEndSec); 953} 954