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 COUNTER_TRACK_KIND, 24 Data, 25} from './common'; 26 27class CounterTrackController extends TrackController<Config, Data> { 28 static readonly kind = COUNTER_TRACK_KIND; 29 private busy = false; 30 private setup = false; 31 private maximumValueSeen = 0; 32 private minimumValueSeen = 0; 33 34 onBoundsChange(start: number, end: number, resolution: number): void { 35 this.update(start, end, resolution); 36 } 37 38 private async update(start: number, end: number, resolution: number): 39 Promise<void> { 40 // TODO: we should really call TraceProcessor.Interrupt() at this point. 41 if (this.busy) return; 42 43 const startNs = Math.round(start * 1e9); 44 const endNs = Math.round(end * 1e9); 45 46 this.busy = true; 47 if (!this.setup) { 48 const result = await this.query(` 49 select max(value), min(value) from 50 counters where name = '${this.config.name}' 51 and ref = ${this.config.ref}`); 52 this.maximumValueSeen = +result.columns[0].doubleValues![0]; 53 this.minimumValueSeen = +result.columns[1].doubleValues![0]; 54 await this.query( 55 `create virtual table ${this.tableName('window')} using window;`); 56 57 await this.query(`create view ${this.tableName('counter_view')} as 58 select ts, 59 lead(ts) over (partition by ref_type order by ts) - ts as dur, 60 value, name, ref 61 from counters 62 where name = '${this.config.name}' and ref = ${this.config.ref};`); 63 64 await this.query(`create virtual table ${this.tableName('span')} using 65 span_join(${this.tableName('counter_view')}, 66 ${this.tableName('window')});`); 67 this.setup = true; 68 } 69 70 const isQuantized = this.shouldSummarize(resolution); 71 // |resolution| is in s/px we want # ns for 10px window: 72 const bucketSizeNs = Math.round(resolution * 10 * 1e9); 73 let windowStartNs = startNs; 74 if (isQuantized) { 75 windowStartNs = Math.floor(windowStartNs / bucketSizeNs) * bucketSizeNs; 76 } 77 const windowDurNs = Math.max(1, endNs - windowStartNs); 78 79 this.query(`update ${this.tableName('window')} set 80 window_start=${windowStartNs}, 81 window_dur=${windowDurNs}, 82 quantum=${isQuantized ? bucketSizeNs : 0}`); 83 84 let query = `select min(ts) as ts, 85 max(value) as value 86 from ${this.tableName('span')} 87 group by quantum_ts;`; 88 89 if (!isQuantized) { 90 // TODO(hjd): Implement window clipping. 91 query = `select ts, value 92 from (select 93 ts, 94 lead(ts) over (partition by ref_type order by ts) as ts_end, 95 value 96 from counters 97 where name = '${this.config.name}' and ref = ${this.config.ref}) 98 where ts <= ${endNs} and ${startNs} <= ts_end;`; 99 } 100 101 const rawResult = await this.query(query); 102 103 const numRows = +rawResult.numRecords; 104 105 const data: Data = { 106 start, 107 end, 108 isQuantized, 109 maximumValue: this.maximumValue(), 110 minimumValue: this.minimumValue(), 111 resolution, 112 timestamps: new Float64Array(numRows), 113 values: new Float64Array(numRows), 114 }; 115 116 const cols = rawResult.columns; 117 for (let row = 0; row < numRows; row++) { 118 const startSec = fromNs(+cols[0].longValues![row]); 119 const value = +cols[1].doubleValues![row]; 120 data.timestamps[row] = startSec; 121 data.values[row] = value; 122 } 123 124 this.publish(data); 125 this.busy = false; 126 } 127 128 private maximumValue() { 129 return Math.max(this.config.maximumValue || 0, this.maximumValueSeen); 130 } 131 132 private minimumValue() { 133 return Math.min(this.config.minimumValue || 0, this.minimumValueSeen); 134 } 135 136 private async query(query: string) { 137 const result = await this.engine.query(query); 138 if (result.error) { 139 console.error(`Query error "${query}": ${result.error}`); 140 throw new Error(`Query error "${query}": ${result.error}`); 141 } 142 return result; 143 } 144} 145 146trackControllerRegistry.register(CounterTrackController); 147