1// Copyright (C) 2020 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 {Duration} from '../../base/time'; 16import {ColumnDef, Sorting} from '../../public/aggregation'; 17import {AreaSelection} from '../../public/selection'; 18import {COUNTER_TRACK_KIND} from '../../public/track_kinds'; 19import {Engine} from '../../trace_processor/engine'; 20import {AreaSelectionAggregator} from '../../public/selection'; 21import {LONG, NUM} from '../../trace_processor/query_result'; 22 23export class CounterSelectionAggregator implements AreaSelectionAggregator { 24 readonly id = 'counter_aggregation'; 25 26 // This just describes which counters we match, we don't actually use the 27 // resulting datasets, but it's a useful too to show what we actually match. 28 readonly trackKind = COUNTER_TRACK_KIND; 29 readonly schema = { 30 id: NUM, 31 ts: LONG, 32 value: NUM, 33 }; 34 35 async createAggregateView(engine: Engine, area: AreaSelection) { 36 const trackIds: (string | number)[] = []; 37 for (const trackInfo of area.tracks) { 38 if (trackInfo?.tags?.kind === COUNTER_TRACK_KIND) { 39 trackInfo.tags?.trackIds && trackIds.push(...trackInfo.tags.trackIds); 40 } 41 } 42 if (trackIds.length === 0) return false; 43 const duration = area.end - area.start; 44 const durationSec = Duration.toSeconds(duration); 45 46 await engine.query(`include perfetto module counters.intervals`); 47 48 // TODO(lalitm): Rewrite this query in a way that is both simpler and faster 49 let query; 50 if (trackIds.length === 1) { 51 // Optimized query for the special case where there is only 1 track id. 52 query = `CREATE OR REPLACE PERFETTO TABLE ${this.id} AS 53 WITH 54 res AS ( 55 select c.* 56 from counter_leading_intervals!(( 57 SELECT counter.* 58 FROM counter 59 WHERE counter.track_id = ${trackIds[0]} 60 AND counter.ts <= ${area.end} 61 )) c 62 WHERE c.ts + c.dur >= ${area.start} 63 ), 64 aggregated AS ( 65 SELECT 66 COUNT(1) AS count, 67 ROUND(SUM( 68 (MIN(ts + dur, ${area.end}) - MAX(ts,${area.start}))*value)/${duration}, 69 2 70 ) AS avg_value, 71 value_at_max_ts(ts, value) AS last_value, 72 value_at_max_ts(-ts, value) AS first_value, 73 MIN(value) AS min_value, 74 MAX(value) AS max_value 75 FROM res 76 ) 77 SELECT 78 (SELECT name FROM counter_track WHERE id = ${trackIds[0]}) AS name, 79 *, 80 MAX(last_value) - MIN(first_value) AS delta_value, 81 ROUND((MAX(last_value) - MIN(first_value))/${durationSec}, 2) AS rate 82 FROM aggregated`; 83 } else { 84 // Slower, but general purspose query that can aggregate multiple tracks 85 query = `CREATE OR REPLACE PERFETTO TABLE ${this.id} AS 86 WITH 87 res AS ( 88 select c.* 89 from counter_leading_intervals!(( 90 SELECT counter.* 91 FROM counter 92 WHERE counter.track_id in (${trackIds}) 93 AND counter.ts <= ${area.end} 94 )) c 95 where c.ts + c.dur >= ${area.start} 96 ), 97 aggregated AS ( 98 SELECT track_id, 99 COUNT(1) AS count, 100 ROUND(SUM( 101 (MIN(ts + dur, ${area.end}) - MAX(ts,${area.start}))*value)/${duration}, 102 2 103 ) AS avg_value, 104 value_at_max_ts(-ts, value) AS first, 105 value_at_max_ts(ts, value) AS last, 106 MIN(value) AS min_value, 107 MAX(value) AS max_value 108 FROM res 109 GROUP BY track_id 110 ) 111 SELECT 112 name, 113 count, 114 avg_value, 115 last AS last_value, 116 first AS first_value, 117 last - first AS delta_value, 118 ROUND((last - first)/${durationSec}, 2) AS rate, 119 min_value, 120 max_value 121 FROM aggregated JOIN counter_track ON 122 track_id = counter_track.id 123 GROUP BY track_id`; 124 } 125 await engine.query(query); 126 return true; 127 } 128 129 getColumnDefinitions(): ColumnDef[] { 130 return [ 131 { 132 title: 'Name', 133 kind: 'STRING', 134 columnConstructor: Uint16Array, 135 columnId: 'name', 136 }, 137 { 138 title: 'Delta value', 139 kind: 'NUMBER', 140 columnConstructor: Float64Array, 141 columnId: 'delta_value', 142 }, 143 { 144 title: 'Rate /s', 145 kind: 'Number', 146 columnConstructor: Float64Array, 147 columnId: 'rate', 148 }, 149 { 150 title: 'Weighted avg value', 151 kind: 'Number', 152 columnConstructor: Float64Array, 153 columnId: 'avg_value', 154 }, 155 { 156 title: 'Count', 157 kind: 'Number', 158 columnConstructor: Float64Array, 159 columnId: 'count', 160 sum: true, 161 }, 162 { 163 title: 'First value', 164 kind: 'NUMBER', 165 columnConstructor: Float64Array, 166 columnId: 'first_value', 167 }, 168 { 169 title: 'Last value', 170 kind: 'NUMBER', 171 columnConstructor: Float64Array, 172 columnId: 'last_value', 173 }, 174 { 175 title: 'Min value', 176 kind: 'NUMBER', 177 columnConstructor: Float64Array, 178 columnId: 'min_value', 179 }, 180 { 181 title: 'Max value', 182 kind: 'NUMBER', 183 columnConstructor: Float64Array, 184 columnId: 'max_value', 185 }, 186 ]; 187 } 188 189 async getExtra() {} 190 191 getTabName() { 192 return 'Counters'; 193 } 194 195 getDefaultSorting(): Sorting { 196 return {column: 'name', direction: 'DESC'}; 197 } 198} 199