xref: /aosp_15_r20/external/perfetto/ui/src/plugins/org.kernel.Wattson/thread_aggregator.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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