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 {fromNs} from '../../common/time'; 16import { 17 TrackController, 18 trackControllerRegistry 19} from '../../controller/track_controller'; 20 21import { 22 Config, 23 Data, 24 PROCESS_SUMMARY_TRACK, 25} from './common'; 26 27class ProcessSummaryTrackController extends TrackController<Config, Data> { 28 static readonly kind = PROCESS_SUMMARY_TRACK; 29 private busy = false; 30 private setup = false; 31 32 onBoundsChange(start: number, end: number, resolution: number): void { 33 this.update(start, end, resolution); 34 } 35 36 private async update(start: number, end: number, resolution: number): 37 Promise<void> { 38 // TODO: we should really call TraceProcessor.Interrupt() at this point. 39 if (this.busy) return; 40 this.busy = true; 41 42 const startNs = Math.round(start * 1e9); 43 const endNs = Math.round(end * 1e9); 44 45 if (this.setup === false) { 46 await this.query( 47 `create virtual table ${this.tableName('window')} using window;`); 48 49 let utids = [this.config.utid]; 50 if (this.config.upid) { 51 const threadQuery = await this.query( 52 `select utid from thread where upid=${this.config.upid}`); 53 utids = threadQuery.columns[0].longValues! as number[]; 54 } 55 56 const processSliceView = this.tableName('process_slice_view'); 57 await this.query( 58 `create view ${processSliceView} as ` + 59 // 0 as cpu is a dummy column to perform span join on. 60 `select ts, dur/${utids.length} as dur ` + 61 `from slices where depth = 0 and utid in ` + 62 // TODO(dproy): This query is faster if we write it as x < utid < y. 63 `(${utids.join(',')})`); 64 await this.query(`create virtual table ${this.tableName('span')} 65 using span_join(${processSliceView}, 66 ${this.tableName('window')});`); 67 this.setup = true; 68 } 69 70 // |resolution| is in s/px we want # ns for 10px window: 71 const bucketSizeNs = Math.round(resolution * 10 * 1e9); 72 const windowStartNs = Math.floor(startNs / bucketSizeNs) * bucketSizeNs; 73 const windowDurNs = Math.max(1, endNs - windowStartNs); 74 75 this.query(`update ${this.tableName('window')} set 76 window_start=${windowStartNs}, 77 window_dur=${windowDurNs}, 78 quantum=${bucketSizeNs} 79 where rowid = 0;`); 80 81 this.publish(await this.computeSummary( 82 fromNs(windowStartNs), end, resolution, bucketSizeNs)); 83 this.busy = false; 84 } 85 86 private async computeSummary( 87 start: number, end: number, resolution: number, 88 bucketSizeNs: number): Promise<Data> { 89 const startNs = Math.round(start * 1e9); 90 const endNs = Math.round(end * 1e9); 91 const numBuckets = Math.ceil((endNs - startNs) / bucketSizeNs); 92 93 const query = `select 94 quantum_ts as bucket, 95 sum(dur)/cast(${bucketSizeNs} as float) as utilization 96 from ${this.tableName('span')} 97 group by quantum_ts`; 98 99 const rawResult = await this.query(query); 100 const numRows = +rawResult.numRecords; 101 102 const summary: Data = { 103 start, 104 end, 105 resolution, 106 bucketSizeSeconds: fromNs(bucketSizeNs), 107 utilizations: new Float64Array(numBuckets), 108 }; 109 const cols = rawResult.columns; 110 for (let row = 0; row < numRows; row++) { 111 const bucket = +cols[0].longValues![row]; 112 summary.utilizations[bucket] = +cols[1].doubleValues![row]; 113 } 114 return summary; 115 } 116 117 // TODO(dproy); Dedup with other controllers. 118 private async query(query: string) { 119 const result = await this.engine.query(query); 120 if (result.error) { 121 console.error(`Query error "${query}": ${result.error}`); 122 throw new Error(`Query error "${query}": ${result.error}`); 123 } 124 return result; 125 } 126 127 onDestroy(): void { 128 if (this.setup) { 129 this.query(`drop table ${this.tableName('window')}`); 130 this.query(`drop table ${this.tableName('span')}`); 131 this.setup = false; 132 } 133 } 134} 135 136trackControllerRegistry.register(ProcessSummaryTrackController); 137