1// Copyright (C) 2024 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 {exists} from '../../base/utils'; 16import {ColumnDef, Sorting} from '../../public/aggregation'; 17import {AreaSelection} from '../../public/selection'; 18import {Engine} from '../../trace_processor/engine'; 19import {CPU_SLICE_TRACK_KIND} from '../../public/track_kinds'; 20import {AreaSelectionAggregator} from '../../public/selection'; 21 22export class WattsonThreadSelectionAggregator 23 implements AreaSelectionAggregator 24{ 25 readonly id = 'wattson_thread_aggregation'; 26 27 async createAggregateView(engine: Engine, area: AreaSelection) { 28 await engine.query(`drop view if exists ${this.id};`); 29 30 const selectedCpus: number[] = []; 31 for (const trackInfo of area.tracks) { 32 if (trackInfo?.tags?.kind === CPU_SLICE_TRACK_KIND) { 33 exists(trackInfo.tags.cpu) && selectedCpus.push(trackInfo.tags.cpu); 34 } 35 } 36 if (selectedCpus.length === 0) return false; 37 38 const duration = area.end - area.start; 39 const cpusCsv = `(` + selectedCpus.join() + `)`; 40 engine.query(` 41 INCLUDE PERFETTO MODULE viz.summary.threads_w_processes; 42 INCLUDE PERFETTO MODULE wattson.curves.idle_attribution; 43 INCLUDE PERFETTO MODULE wattson.curves.estimates; 44 45 CREATE OR REPLACE PERFETTO TABLE _ui_selection_window AS 46 SELECT 47 ${area.start} as ts, 48 ${duration} as dur; 49 50 -- Processes filtered by CPU within the UI defined time window 51 DROP TABLE IF EXISTS _windowed_summary; 52 CREATE VIRTUAL TABLE _windowed_summary 53 USING 54 SPAN_JOIN(_ui_selection_window, _sched_w_thread_process_package_summary); 55 56 -- Only get idle attribution in user defined window and filter by selected 57 -- CPUs and GROUP BY thread 58 CREATE OR REPLACE PERFETTO TABLE _per_thread_idle_attribution AS 59 SELECT 60 ROUND(SUM(idle_cost_mws), 2) as idle_cost_mws, 61 utid 62 FROM _filter_idle_attribution(${area.start}, ${duration}) 63 WHERE cpu in ${cpusCsv} 64 GROUP BY utid; 65 `); 66 this.runEstimateThreadsQuery(engine, selectedCpus, duration); 67 68 return true; 69 } 70 71 // This function returns a query that gets the average and estimate from 72 // Wattson for the selection in the UI window based on thread. The grouping by 73 // thread needs to 'remove' 2 dimensions; the threads need to be grouped over 74 // time and the threads need to be grouped over CPUs. 75 // 1. Window and associate thread with proper Wattson estimate slice 76 // 2. Group all threads over time on a per CPU basis 77 // 3. Group all threads over all CPUs 78 runEstimateThreadsQuery( 79 engine: Engine, 80 selectedCpu: number[], 81 duration: bigint, 82 ) { 83 // Estimate and total per UTID per CPU 84 selectedCpu.forEach((cpu) => { 85 engine.query(` 86 -- Packages filtered by CPU 87 CREATE OR REPLACE PERFETTO VIEW _windowed_summary_per_cpu${cpu} AS 88 SELECT * 89 FROM _windowed_summary WHERE cpu = ${cpu}; 90 91 -- CPU specific track with slices for curves 92 CREATE OR REPLACE PERFETTO VIEW _per_cpu${cpu}_curve AS 93 SELECT ts, dur, cpu${cpu}_curve 94 FROM _system_state_curves; 95 96 -- Filter out track when threads are available 97 DROP TABLE IF EXISTS _windowed_thread_curve${cpu}; 98 CREATE VIRTUAL TABLE _windowed_thread_curve${cpu} 99 USING 100 SPAN_JOIN(_per_cpu${cpu}_curve, _windowed_summary_per_cpu${cpu}); 101 102 -- Total estimate per UTID per CPU 103 CREATE OR REPLACE PERFETTO VIEW _total_per_cpu${cpu} AS 104 SELECT 105 SUM(cpu${cpu}_curve * dur) as total_pws, 106 SUM(dur) as dur, 107 tid, 108 pid, 109 uid, 110 utid, 111 upid, 112 thread_name, 113 process_name, 114 package_name 115 FROM _windowed_thread_curve${cpu} 116 GROUP BY utid; 117 `); 118 }); 119 120 // Estimate and total per UTID, removing CPU dimension 121 let query = `CREATE OR REPLACE PERFETTO TABLE _unioned_per_cpu_total AS `; 122 selectedCpu.forEach((cpu, i) => { 123 query += i != 0 ? `UNION ALL\n` : ``; 124 query += `SELECT * from _total_per_cpu${cpu}\n`; 125 }); 126 query += ` 127 ; 128 129 -- Grouped again by UTID, but this time to make it CPU agnostic 130 CREATE VIEW ${this.id} AS 131 SELECT 132 ROUND(SUM(total_pws) / ${duration}, 2) as active_mw, 133 ROUND(SUM(total_pws) / 1000000000, 2) as active_mws, 134 COALESCE(idle_cost_mws, 0) as idle_cost_mws, 135 ROUND( 136 COALESCE(idle_cost_mws, 0) + SUM(total_pws) / 1000000000, 137 2 138 ) as total_mws, 139 thread_name, 140 utid, 141 tid, 142 pid 143 FROM _unioned_per_cpu_total 144 LEFT JOIN _per_thread_idle_attribution USING (utid) 145 GROUP BY utid; 146 `; 147 148 engine.query(query); 149 150 return; 151 } 152 153 getColumnDefinitions(): ColumnDef[] { 154 return [ 155 { 156 title: 'Thread Name', 157 kind: 'STRING', 158 columnConstructor: Uint16Array, 159 columnId: 'thread_name', 160 }, 161 { 162 title: 'TID', 163 kind: 'NUMBER', 164 columnConstructor: Uint16Array, 165 columnId: 'tid', 166 }, 167 { 168 title: 'PID', 169 kind: 'NUMBER', 170 columnConstructor: Uint16Array, 171 columnId: 'pid', 172 }, 173 { 174 title: 'Active power (estimated mW)', 175 kind: 'NUMBER', 176 columnConstructor: Float64Array, 177 columnId: 'active_mw', 178 sum: true, 179 }, 180 { 181 title: 'Active energy (estimated mWs)', 182 kind: 'NUMBER', 183 columnConstructor: Float64Array, 184 columnId: 'active_mws', 185 sum: true, 186 }, 187 { 188 title: 'Idle transitions overhead (estimated mWs)', 189 kind: 'NUMBER', 190 columnConstructor: Float64Array, 191 columnId: 'idle_cost_mws', 192 sum: true, 193 }, 194 { 195 title: 'Total energy (estimated mWs)', 196 kind: 'NUMBER', 197 columnConstructor: Float64Array, 198 columnId: 'total_mws', 199 sum: true, 200 }, 201 ]; 202 } 203 204 async getExtra() {} 205 206 getTabName() { 207 return 'Wattson by thread'; 208 } 209 210 getDefaultSorting(): Sorting { 211 return {column: 'active_mws', direction: 'DESC'}; 212 } 213} 214