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 {NUM} from '../../common/query_result'; 16import {fromNs, toNs} from '../../common/time'; 17import {LIMIT} from '../../common/track_data'; 18import { 19 TrackController, 20 trackControllerRegistry 21} from '../../controller/track_controller'; 22 23import { 24 Config, 25 Data, 26 PROCESS_SUMMARY_TRACK, 27} from './common'; 28 29// This is the summary displayed when a process only contains chrome slices 30// and no cpu scheduling. 31 32class ProcessSummaryTrackController extends TrackController<Config, Data> { 33 static readonly kind = PROCESS_SUMMARY_TRACK; 34 private setup = false; 35 36 async onBoundsChange(start: number, end: number, resolution: number): 37 Promise<Data> { 38 const startNs = toNs(start); 39 const endNs = toNs(end); 40 41 if (this.setup === false) { 42 await this.query( 43 `create virtual table ${this.tableName('window')} using window;`); 44 45 let utids = [this.config.utid]; 46 if (this.config.upid) { 47 const threadQuery = await this.query( 48 `select utid from thread where upid=${this.config.upid}`); 49 utids = []; 50 for (const it = threadQuery.iter({utid: NUM}); it.valid(); it.next()) { 51 utids.push(it.utid); 52 } 53 } 54 55 const trackQuery = await this.query( 56 `select id from thread_track where utid in (${utids.join(',')})`); 57 const tracks = []; 58 for (const it = trackQuery.iter({id: NUM}); it.valid(); it.next()) { 59 tracks.push(it.id); 60 } 61 62 const processSliceView = this.tableName('process_slice_view'); 63 await this.query( 64 `create view ${processSliceView} as ` + 65 // 0 as cpu is a dummy column to perform span join on. 66 `select ts, dur/${utids.length} as dur ` + 67 `from slice s ` + 68 `where depth = 0 and track_id in ` + 69 `(${tracks.join(',')})`); 70 await this.query(`create virtual table ${this.tableName('span')} 71 using span_join(${processSliceView}, 72 ${this.tableName('window')});`); 73 this.setup = true; 74 } 75 76 // |resolution| is in s/px we want # ns for 10px window: 77 // Max value with 1 so we don't end up with resolution 0. 78 const bucketSizeNs = Math.max(1, Math.round(resolution * 10 * 1e9)); 79 const windowStartNs = Math.floor(startNs / bucketSizeNs) * bucketSizeNs; 80 const windowDurNs = Math.max(1, endNs - windowStartNs); 81 82 await this.query(`update ${this.tableName('window')} set 83 window_start=${windowStartNs}, 84 window_dur=${windowDurNs}, 85 quantum=${bucketSizeNs} 86 where rowid = 0;`); 87 88 return this.computeSummary( 89 fromNs(windowStartNs), end, resolution, bucketSizeNs); 90 } 91 92 private async computeSummary( 93 start: number, end: number, resolution: number, 94 bucketSizeNs: number): Promise<Data> { 95 const startNs = toNs(start); 96 const endNs = toNs(end); 97 const numBuckets = 98 Math.min(Math.ceil((endNs - startNs) / bucketSizeNs), LIMIT); 99 100 const query = `select 101 quantum_ts as bucket, 102 sum(dur)/cast(${bucketSizeNs} as float) as utilization 103 from ${this.tableName('span')} 104 group by quantum_ts 105 limit ${LIMIT}`; 106 107 const summary: Data = { 108 start, 109 end, 110 resolution, 111 length: numBuckets, 112 bucketSizeSeconds: fromNs(bucketSizeNs), 113 utilizations: new Float64Array(numBuckets), 114 }; 115 116 const queryRes = await this.query(query); 117 const it = queryRes.iter({bucket: NUM, utilization: NUM}); 118 for (; it.valid(); it.next()) { 119 const bucket = it.bucket; 120 if (bucket > numBuckets) { 121 continue; 122 } 123 summary.utilizations[bucket] = it.utilization; 124 } 125 126 return summary; 127 } 128 129 onDestroy(): void { 130 if (this.setup) { 131 this.query(`drop table ${this.tableName('window')}`); 132 this.query(`drop table ${this.tableName('span')}`); 133 this.setup = false; 134 } 135 } 136} 137 138trackControllerRegistry.register(ProcessSummaryTrackController); 139