1-- Copyright 2025 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-- https://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 15INCLUDE PERFETTO MODULE android.suspend; 16 17-- Table of parsed wakeup / suspend failure events with suspend backoff. 18-- 19-- Certain wakeup events may have multiple causes. When this occurs we 20-- split those causes into multiple rows in this table with the same ts 21-- and raw_wakeup values. 22CREATE PERFETTO TABLE android_wakeups ( 23 -- Timestamp. 24 ts TIMESTAMP, 25 -- Duration for which we blame the wakeup for wakefulness. This is the 26 -- suspend backoff duration if one exists, or the lesser of (5 seconds, 27 -- time to next suspend event). 28 dur DURATION, 29 -- Original wakeup string from the kernel. 30 raw_wakeup STRING, 31 -- Wakeup attribution, as determined on device. May be absent. 32 on_device_attribution STRING, 33 -- One of 'normal' (device woke from sleep), 'abort_pending' (suspend failed 34 -- due to a wakeup that was scheduled by a device during the suspend 35 -- process), 'abort_last_active' (suspend failed, listing the last active 36 -- device) or 'abort_other' (suspend failed for another reason). 37 type STRING, 38 -- Individual wakeup cause. Usually the name of the device that cause the 39 -- wakeup, or the raw message in the 'abort_other' case. 40 item STRING, 41 -- 'good' or 'bad'. 'bad' means failed or short such that suspend backoff 42 -- is triggered. 43 suspend_quality STRING, 44 -- 'new', 'continue' or NULL. Set if suspend backoff is triggered. 45 backoff_state STRING, 46 -- 'short', 'failed' or NULL. Set if suspend backoff is triggered. 47 backoff_reason STRING, 48 -- Number of times suspend backoff has occured, or NULL. Set if suspend 49 -- backoff is triggered. 50 backoff_count LONG, 51 -- Next suspend backoff duration, or NULL. Set if suspend backoff is 52 -- triggered. 53 backoff_millis LONG 54) AS 55WITH 56 wakeup_reason AS ( 57 -- wakeup_reason is recorded ASAP after wakeup; its packet timestamp is reliable. It also contains 58 -- a millisecond timestamp which we can use to associate the wakeup_reason with a later 59 -- wakeup_attribution. 60 SELECT 61 ts, 62 substr(i.name, 0, instr(i.name, ' ')) AS id_timestamp, 63 substr(i.name, instr(i.name, ' ') + 1) AS raw_wakeup 64 FROM track AS t 65 JOIN instant AS i 66 ON t.id = i.track_id 67 WHERE 68 t.name = 'wakeup_reason' 69 ), 70 wakeup_attribution AS ( 71 -- wakeup_attribution is recorded at some later time after wakeup and after batterystat has decided 72 -- how to attribute it. We therefore associate it with the original wakeup via the embedded 73 -- millisecond timestamp. 74 SELECT 75 substr(i.name, 0, instr(i.name, ' ')) AS id_timestamp, 76 substr(i.name, instr(i.name, ' ') + 1) AS on_device_attribution 77 FROM track AS t 78 JOIN instant AS i 79 ON t.id = i.track_id 80 WHERE 81 t.name = 'wakeup_attribution' 82 ), 83 step1 AS ( 84 -- Join reason, attribution, and backoff. reason and attribution contain a timestamp that can be 85 -- used as an ID for joining. backoff does not but we know if it occurs at all it will always 86 -- occur just before the reason. We therefore "join" with a union+lag() to be efficient. 87 SELECT 88 ts, 89 raw_wakeup, 90 on_device_attribution, 91 NULL AS raw_backoff 92 FROM wakeup_reason AS r 93 LEFT OUTER JOIN wakeup_attribution 94 USING (id_timestamp) 95 UNION ALL 96 SELECT 97 ts, 98 NULL AS raw_wakeup, 99 NULL AS on_device_attribution, 100 i.name AS raw_backoff 101 FROM track AS t 102 JOIN instant AS i 103 ON t.id = i.track_id 104 WHERE 105 t.name = 'suspend_backoff' 106 ), 107 step2 AS ( 108 SELECT 109 ts, 110 raw_wakeup, 111 on_device_attribution, 112 lag(raw_backoff) OVER (ORDER BY ts) AS raw_backoff 113 FROM step1 114 ), 115 step3 AS ( 116 SELECT 117 ts, 118 raw_wakeup, 119 on_device_attribution, 120 str_split(raw_backoff, ' ', 0) AS suspend_quality, 121 str_split(raw_backoff, ' ', 1) AS backoff_state, 122 str_split(raw_backoff, ' ', 2) AS backoff_reason, 123 CAST(str_split(raw_backoff, ' ', 3) AS INTEGER) AS backoff_count, 124 CAST(str_split(raw_backoff, ' ', 4) AS INTEGER) AS backoff_millis, 125 FALSE AS suspend_end 126 FROM step2 127 WHERE 128 raw_wakeup IS NOT NULL 129 UNION ALL 130 SELECT 131 ts, 132 NULL AS raw_wakeup, 133 NULL AS on_device_attribution, 134 NULL AS suspend_quality, 135 NULL AS backoff_state, 136 NULL AS backoff_reason, 137 NULL AS backoff_count, 138 NULL AS backoff_millis, 139 TRUE AS suspend_end 140 FROM android_suspend_state 141 WHERE 142 power_state = 'suspended' 143 ), 144 -- If the device is in a failure-to-suspend loop it will back off and take 145 -- up to ~1 minute to suspend. We should allow ourselves to apportion that time 146 -- to the wakeup (unless there was another wakeup or suspend following it). 147 -- NB a failure to suspend loop can also manifest as actually suspending and then 148 -- waking up after a few milliseconds so we don't attempt to filter by aborted 149 -- suspends here. 150 step4 AS ( 151 SELECT 152 ts, 153 CASE suspend_quality 154 WHEN 'good' 155 THEN min(lead(ts, 1, ts + 5e9) OVER (ORDER BY ts) - ts, 5e9) 156 WHEN 'bad' 157 THEN backoff_millis * 1000000 158 ELSE 0 159 END AS dur, 160 raw_wakeup, 161 on_device_attribution, 162 suspend_quality, 163 backoff_state, 164 backoff_reason, 165 backoff_count, 166 backoff_millis, 167 suspend_end 168 FROM step3 169 ), 170 step5 AS ( 171 SELECT 172 ts, 173 dur, 174 raw_wakeup, 175 on_device_attribution, 176 suspend_quality, 177 backoff_state, 178 backoff_reason, 179 backoff_count, 180 backoff_millis 181 FROM step4 182 WHERE 183 NOT suspend_end 184 ), 185 -- Each wakeup can contain multiple reasons. We need to parse the wakeup in order to get them out. 186 -- This is made more annoying by the fact it can be in multiple formats: 187 -- If the wakeup represents an aborted attempt at suspending, it will be prefixed with one of a 188 -- couple of variations on "Abort:" and may (in the pending wakeup case) contain multiple reasons, 189 -- separated by spaces 190 -- 191 -- example: "Abort: Device 0001:01:00.0 failed to suspend: error -16" 192 -- example: "Abort: Pending Wakeup Sources: wlan_oob_irq_wake wlan_txfl_wake wlan_rx_wake" 193 -- example: "Abort: Last active Wakeup Source: wlan_oob_irq_wake" 194 -- 195 -- For a normal wakeup there may be multiple reasons separated by ":" and each preceded by a numeric 196 -- IRQ. Essentially the text is a looked-up explanation of the IRQ number. 197 -- 198 -- example: "170 176a0000.mbox:440 dhdpcie_host_wake" 199 -- example: "374 max_fg_irq:440 dhdpcie_host_wake" 200 -- 201 step6 AS ( 202 SELECT 203 ts, 204 dur, 205 raw_wakeup, 206 on_device_attribution, 207 suspend_quality, 208 backoff_state, 209 backoff_reason, 210 backoff_count, 211 backoff_millis, 212 CASE 213 WHEN raw_wakeup GLOB 'Abort: Pending Wakeup Sources: *' 214 THEN 'abort_pending' 215 WHEN raw_wakeup GLOB 'Abort: Last active Wakeup Source: *' 216 THEN 'abort_last_active' 217 WHEN raw_wakeup GLOB 'Abort: *' 218 THEN 'abort_other' 219 ELSE 'normal' 220 END AS type, 221 CASE 222 WHEN raw_wakeup GLOB 'Abort: Pending Wakeup Sources: *' 223 THEN substr(raw_wakeup, 32) 224 WHEN raw_wakeup GLOB 'Abort: Last active Wakeup Source: *' 225 THEN substr(raw_wakeup, 35) 226 WHEN raw_wakeup GLOB 'Abort: *' 227 THEN substr(raw_wakeup, 8) 228 ELSE raw_wakeup 229 END AS main, 230 CASE 231 WHEN raw_wakeup GLOB 'Abort: Pending Wakeup Sources: *' 232 THEN ' ' 233 WHEN raw_wakeup GLOB 'Abort: *' 234 THEN 'no delimiter needed' 235 ELSE ':' 236 END AS delimiter 237 FROM step5 238 ), 239 step7 AS ( 240 SELECT 241 ts, 242 dur, 243 raw_wakeup, 244 on_device_attribution, 245 suspend_quality, 246 backoff_state, 247 backoff_reason, 248 backoff_count, 249 backoff_millis, 250 type, 251 str_split(main, delimiter, 0) AS item_0, 252 str_split(main, delimiter, 1) AS item_1, 253 str_split(main, delimiter, 2) AS item_2, 254 str_split(main, delimiter, 3) AS item_3 255 FROM step6 256 ), 257 step8 AS ( 258 SELECT 259 ts, 260 dur, 261 raw_wakeup, 262 on_device_attribution, 263 suspend_quality, 264 backoff_state, 265 backoff_reason, 266 backoff_count, 267 backoff_millis, 268 type, 269 item_0 AS item 270 FROM step7 271 UNION ALL 272 SELECT 273 ts, 274 dur, 275 raw_wakeup, 276 on_device_attribution, 277 suspend_quality, 278 backoff_state, 279 backoff_reason, 280 backoff_count, 281 backoff_millis, 282 type, 283 item_1 AS item 284 FROM step7 285 WHERE 286 item_1 IS NOT NULL 287 UNION ALL 288 SELECT 289 ts, 290 dur, 291 raw_wakeup, 292 on_device_attribution, 293 suspend_quality, 294 backoff_state, 295 backoff_reason, 296 backoff_count, 297 backoff_millis, 298 type, 299 item_2 AS item 300 FROM step7 301 WHERE 302 item_2 IS NOT NULL 303 UNION ALL 304 SELECT 305 ts, 306 dur, 307 raw_wakeup, 308 on_device_attribution, 309 suspend_quality, 310 backoff_state, 311 backoff_reason, 312 backoff_count, 313 backoff_millis, 314 type, 315 item_3 AS item 316 FROM step7 317 WHERE 318 item_3 IS NOT NULL 319 ) 320SELECT 321 ts, 322 cast_int!(dur) AS dur, 323 raw_wakeup, 324 on_device_attribution, 325 type, 326 -- Remove the numeric IRQ, it duplicates the text and is less comprehensible. 327 CASE WHEN type = 'normal' THEN coalesce(str_split(item, ' ', 1), item) ELSE item END AS item, 328 suspend_quality, 329 backoff_state, 330 coalesce(backoff_reason, 'none') AS backoff_reason, 331 backoff_count, 332 backoff_millis 333FROM step8; 334