1-- 2-- Copyright 2024 The Android Open Source Project 3-- 4-- Licensed under the Apache License, Version 2.0 (the "License"); 5-- you may not use this file except in compliance with the License. 6-- You may obtain a copy of the License at 7-- 8-- https://www.apache.org/licenses/LICENSE-2.0 9-- 10-- Unless required by applicable law or agreed to in writing, software 11-- distributed under the License is distributed on an "AS IS" BASIS, 12-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13-- See the License for the specific language governing permissions and 14-- limitations under the License. 15 16INCLUDE PERFETTO MODULE intervals.intersect; 17 18INCLUDE PERFETTO MODULE wattson.curves.estimates; 19 20-- Get slice info of threads/processes 21CREATE PERFETTO TABLE _thread_process_slices AS 22SELECT 23 sched.ts, 24 sched.dur, 25 sched.cpu, 26 thread.utid, 27 thread.upid 28FROM thread 29JOIN sched 30 USING (utid) 31WHERE 32 dur > 0; 33 34-- Helper macro so Perfetto tables can be used with interval intersect 35CREATE PERFETTO MACRO _ii_table( 36 tab TableOrSubquery 37) 38RETURNS TableOrSubquery AS 39( 40 SELECT 41 _auto_id AS id, 42 * 43 FROM $tab 44); 45 46-- Get slices only where there is transition from deep idle to active 47CREATE PERFETTO TABLE _idle_exits AS 48SELECT 49 ts, 50 dur, 51 cpu, 52 idle 53FROM _adjusted_deep_idle 54WHERE 55 idle = -1 AND dur > 0; 56 57-- Gets the slices where the CPU transitions from deep idle to active, and the 58-- associated thread that causes the idle exit 59CREATE PERFETTO TABLE _idle_w_threads AS 60WITH 61 _ii_idle_threads AS ( 62 SELECT 63 ii.ts, 64 ii.dur, 65 ii.cpu, 66 threads.utid, 67 threads.upid, 68 id_1 AS idle_group 69 FROM _interval_intersect!( 70 ( 71 _ii_table!(_thread_process_slices), 72 _ii_table!(_idle_exits) 73 ), 74 (cpu) 75 ) AS ii 76 JOIN _thread_process_slices AS threads 77 ON threads._auto_id = id_0 78 ), 79 -- Since sorted by time, MIN() is fast aggregate function that will return the 80 -- first time slice, which will be the utid = 0 slice immediately succeeding the 81 -- idle to active transition, and immediately preceding the active thread 82 first_swapper_slice AS ( 83 SELECT 84 ts, 85 dur, 86 cpu, 87 idle_group, 88 min(ts) AS min 89 FROM _ii_idle_threads 90 GROUP BY 91 idle_group 92 ), 93 -- MIN() here will give the first active thread immediately succeeding the idle 94 -- to active transition slice, which means this the the thread that causes the 95 -- idle exit 96 first_non_swapper_slice AS ( 97 SELECT 98 idle_group, 99 utid, 100 upid, 101 min(ts) AS min, 102 min(ts) + dur AS next_ts 103 FROM _ii_idle_threads 104 WHERE 105 NOT utid IN ( 106 SELECT 107 utid 108 FROM thread 109 WHERE 110 is_idle 111 ) 112 GROUP BY 113 idle_group 114 ), 115 -- MAX() here will give the last time slice in the group. This will be the 116 -- utid = 0 slice immediately preceding the active to idle transition. 117 last_swapper_slice AS ( 118 SELECT 119 ts, 120 dur, 121 cpu, 122 idle_group, 123 max(ts) AS min 124 FROM _ii_idle_threads 125 GROUP BY 126 idle_group 127 ) 128SELECT 129 swapper_info.ts, 130 swapper_info.dur, 131 swapper_info.cpu, 132 thread_info.utid, 133 thread_info.upid 134FROM first_non_swapper_slice AS thread_info 135JOIN first_swapper_slice AS swapper_info 136 USING (idle_group) 137UNION ALL 138-- Adds the last slice to idle transition attribution IF this is a singleton 139-- thread wakeup. This is true if there is only one thread between swapper idle 140-- exits/wakeups. For example, groups with order of swapper, thread X, swapper 141-- will be included. Entries that have multiple thread between swappers, such as 142-- swapper, thread X, thread Y, swapper will not be included. 143SELECT 144 swapper_info.ts, 145 swapper_info.dur, 146 swapper_info.cpu, 147 thread_info.utid, 148 thread_info.upid 149FROM first_non_swapper_slice AS thread_info 150JOIN last_swapper_slice AS swapper_info 151 USING (idle_group) 152WHERE 153 ts = next_ts; 154 155-- Interval intersect with the estimate power track, so that each slice can be 156-- attributed to the power of the CPU in that time duration 157CREATE PERFETTO TABLE _idle_transition_cost AS 158SELECT 159 ii.ts, 160 ii.dur, 161 threads.cpu, 162 threads.utid, 163 threads.upid, 164 CASE threads.cpu 165 WHEN 0 166 THEN power.cpu0_mw 167 WHEN 1 168 THEN power.cpu1_mw 169 WHEN 2 170 THEN power.cpu2_mw 171 WHEN 3 172 THEN power.cpu3_mw 173 WHEN 4 174 THEN power.cpu4_mw 175 WHEN 5 176 THEN power.cpu5_mw 177 WHEN 6 178 THEN power.cpu6_mw 179 WHEN 7 180 THEN power.cpu7_mw 181 ELSE 0 182 END AS estimated_mw 183FROM _interval_intersect!( 184 ( 185 _ii_table!(_idle_w_threads), 186 _ii_table!(_system_state_mw) 187 ), 188 () 189) AS ii 190JOIN _idle_w_threads AS threads 191 ON threads._auto_id = id_0 192JOIN _system_state_mw AS power 193 ON power._auto_id = id_1; 194 195-- Macro for easily filtering idle attribution to a specified time window. This 196-- information can then further be filtered by specific CPU and GROUP BY on 197-- either utid or upid 198CREATE PERFETTO FUNCTION _filter_idle_attribution( 199 ts TIMESTAMP, 200 dur LONG 201) 202RETURNS TABLE ( 203 idle_cost_mws LONG, 204 utid JOINID(thread.id), 205 upid JOINID(process.id), 206 cpu JOINID(cpu.id) 207) AS 208-- Give the negative sum of idle costs to the swapper thread, which by 209-- definition has a utid = 0, upid = 0, and by definition will not be defined, 210-- so need to UNION to manually add swapper thread 211WITH 212 base AS ( 213 SELECT 214 cost.estimated_mw * cost.dur / 1e9 AS idle_cost_mws, 215 cost.utid, 216 cost.upid, 217 cost.cpu 218 FROM _interval_intersect_single!( 219 $ts, $dur, _ii_table!(_idle_transition_cost) 220 ) AS ii 221 JOIN _idle_transition_cost AS cost 222 ON cost._auto_id = id 223 ) 224SELECT 225 idle_cost_mws, 226 utid, 227 upid, 228 cpu 229FROM base 230UNION ALL 231SELECT 232 -1 * sum(idle_cost_mws) AS idle_cost_mws, 233 0 AS utid, 234 0 AS upid, 235 cpu 236FROM base 237GROUP BY 238 cpu; 239