• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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