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_plugin_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 wattson_plugin_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 wattson_plugin_windowed_summary; 52 CREATE VIRTUAL TABLE wattson_plugin_windowed_summary 53 USING SPAN_JOIN( 54 wattson_plugin_ui_selection_window, 55 _sched_w_thread_process_package_summary 56 ); 57 58 -- Only get idle attribution in user defined window and filter by selected 59 -- CPUs and GROUP BY thread 60 CREATE OR REPLACE PERFETTO TABLE wattson_plugin_per_thread_idle_cost AS 61 SELECT 62 SUM(idle_cost_mws) as idle_cost_mws, 63 utid 64 FROM _filter_idle_attribution(${area.start}, ${duration}) 65 WHERE cpu in ${cpusCsv} 66 GROUP BY utid 67 ; 68 `); 69 this.runEstimateThreadsQuery(engine, selectedCpus, duration); 70 71 return true; 72 } 73 74 // This function returns a query that gets the average and estimate from 75 // Wattson for the selection in the UI window based on thread. The grouping by 76 // thread needs to 'remove' 2 dimensions; the threads need to be grouped over 77 // time and the threads need to be grouped over CPUs. 78 // 1. Window and associate thread with proper Wattson estimate slice 79 // 2. Group all threads over time on a per CPU basis 80 // 3. Group all threads over all CPUs 81 runEstimateThreadsQuery( 82 engine: Engine, 83 selectedCpu: number[], 84 duration: bigint, 85 ) { 86 // Estimate and total per UTID per CPU 87 selectedCpu.forEach((cpu) => { 88 engine.query(` 89 -- Packages filtered by CPU 90 CREATE OR REPLACE PERFETTO VIEW 91 wattson_plugin_windowed_summary_per_cpu${cpu} AS 92 SELECT * 93 FROM wattson_plugin_windowed_summary WHERE cpu = ${cpu}; 94 95 -- CPU specific track with slices for curves 96 CREATE OR REPLACE PERFETTO VIEW wattson_plugin_per_cpu${cpu}_curve AS 97 SELECT ts, dur, cpu${cpu}_curve 98 FROM _system_state_curves; 99 100 -- Filter out track when threads are available 101 DROP TABLE IF EXISTS wattson_plugin_windowed_thread_curve${cpu}; 102 CREATE VIRTUAL TABLE wattson_plugin_windowed_thread_curve${cpu} 103 USING SPAN_JOIN( 104 wattson_plugin_per_cpu${cpu}_curve, 105 wattson_plugin_windowed_summary_per_cpu${cpu} 106 ); 107 108 -- Total estimate per UTID per CPU 109 CREATE OR REPLACE PERFETTO VIEW wattson_plugin_total_per_cpu${cpu} AS 110 SELECT 111 SUM(cpu${cpu}_curve * dur) as total_pws, 112 SUM(dur) as dur, 113 tid, 114 pid, 115 uid, 116 utid, 117 upid, 118 thread_name, 119 process_name, 120 package_name 121 FROM wattson_plugin_windowed_thread_curve${cpu} 122 GROUP BY utid; 123 `); 124 }); 125 126 // Estimate and total per UTID, removing CPU dimension 127 let query = ` 128 CREATE OR REPLACE PERFETTO TABLE wattson_plugin_unioned_per_cpu_total AS 129 `; 130 selectedCpu.forEach((cpu, i) => { 131 query += i != 0 ? `UNION ALL\n` : ``; 132 query += `SELECT * from wattson_plugin_total_per_cpu${cpu}\n`; 133 }); 134 query += ` 135 ; 136 137 -- Grouped again by UTID, but this time to make it CPU agnostic 138 CREATE PERFETTO VIEW ${this.id} AS 139 WITH base AS ( 140 SELECT 141 ROUND(SUM(total_pws) / ${duration}, 3) as active_mw, 142 ROUND(SUM(total_pws) / 1000000000, 3) as active_mws, 143 ROUND(COALESCE(idle_cost_mws, 0), 3) as idle_cost_mws, 144 ROUND( 145 COALESCE(idle_cost_mws, 0) + SUM(total_pws) / 1000000000, 146 3 147 ) as total_mws, 148 thread_name, 149 utid, 150 tid, 151 pid 152 FROM wattson_plugin_unioned_per_cpu_total 153 LEFT JOIN wattson_plugin_per_thread_idle_cost USING (utid) 154 GROUP BY utid 155 ), 156 secondary AS ( 157 SELECT utid, 158 ROUND(100 * (total_mws) / (SUM(total_mws) OVER()), 3) 159 AS percent_of_total_energy 160 FROM base 161 GROUP BY utid 162 ) 163 select * 164 from base INNER JOIN secondary 165 USING (utid); 166 `; 167 168 engine.query(query); 169 170 return; 171 } 172 173 getColumnDefinitions(): ColumnDef[] { 174 return [ 175 { 176 title: 'Thread Name', 177 kind: 'STRING', 178 columnConstructor: Uint16Array, 179 columnId: 'thread_name', 180 }, 181 { 182 title: 'TID', 183 kind: 'NUMBER', 184 columnConstructor: Uint16Array, 185 columnId: 'tid', 186 }, 187 { 188 title: 'PID', 189 kind: 'NUMBER', 190 columnConstructor: Uint16Array, 191 columnId: 'pid', 192 }, 193 { 194 title: 'Active power (estimated mW)', 195 kind: 'NUMBER', 196 columnConstructor: Float64Array, 197 columnId: 'active_mw', 198 sum: true, 199 }, 200 { 201 title: 'Active energy (estimated mWs)', 202 kind: 'NUMBER', 203 columnConstructor: Float64Array, 204 columnId: 'active_mws', 205 sum: true, 206 }, 207 { 208 title: 'Idle transitions overhead (estimated mWs)', 209 kind: 'NUMBER', 210 columnConstructor: Float64Array, 211 columnId: 'idle_cost_mws', 212 sum: false, 213 }, 214 { 215 title: 'Total energy (estimated mWs)', 216 kind: 'NUMBER', 217 columnConstructor: Float64Array, 218 columnId: 'total_mws', 219 sum: true, 220 }, 221 { 222 title: '% of total energy', 223 kind: 'PERCENT', 224 columnConstructor: Float64Array, 225 columnId: 'percent_of_total_energy', 226 sum: false, 227 }, 228 ]; 229 } 230 231 async getExtra() {} 232 233 getTabName() { 234 return 'Wattson by thread'; 235 } 236 237 getDefaultSorting(): Sorting { 238 return {column: 'active_mws', direction: 'DESC'}; 239 } 240} 241