• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright (C) 2021 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#      http://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
16from perfetto.trace_processor.api import TraceProcessor
17from perfetto.trace_processor.api import TraceProcessorException
18
19
20def compute_breakdown(tp: TraceProcessor,
21                      start_ts=None,
22                      end_ts=None,
23                      process_name=None):
24  """For each userspace slice in the trace processor instance |tp|, computes
25  the self-time of that slice grouping by process name, thread name
26  and thread state.
27
28  Args:
29    tp: the trace processor instance to query.
30    start_ts: optional bound to only consider slices after this ts
31    end_ts: optional bound to only consider slices until this ts
32    process_name: optional process name to filter for slices; specifying
33        this argument can make computing the breakdown a lot faster.
34
35  Returns:
36    A Pandas dataframe containing the total self time taken by a slice stack
37    broken down by process name, thread name and thread state.
38  """
39  bounds = tp.query('SELECT * FROM trace_bounds').as_pandas_dataframe()
40  start_ts = start_ts if start_ts else bounds['start_ts'][0]
41  end_ts = end_ts if end_ts else bounds['end_ts'][0]
42
43  tp.query("""
44    DROP VIEW IF EXISTS modded_names
45  """)
46
47  tp.query("""
48    CREATE VIEW modded_names AS
49    SELECT
50      slice.id,
51      slice.depth,
52      slice.stack_id,
53      CASE
54        WHEN slice.name LIKE 'Choreographer#doFrame%'
55          THEN 'Choreographer#doFrame'
56        WHEN slice.name LIKE 'DrawFrames%'
57          THEN 'DrawFrames'
58        WHEN slice.name LIKE '/data/app%.apk'
59          THEN 'APK load'
60        WHEN slice.name LIKE 'OpenDexFilesFromOat%'
61          THEN 'OpenDexFilesFromOat'
62        WHEN slice.name LIKE 'Open oat file%'
63          THEN 'Open oat file'
64        ELSE slice.name
65      END AS modded_name
66    FROM slice
67  """)
68
69  tp.query("""
70    DROP VIEW IF EXISTS thread_slice_stack
71  """)
72
73  tp.query("""
74    CREATE VIEW thread_slice_stack AS
75    SELECT
76      efs.ts,
77      efs.dur,
78      IFNULL(n.stack_id, -1) AS stack_id,
79      t.utid,
80      IIF(efs.source_id IS NULL, '[No slice]', IFNULL(
81        (
82          SELECT GROUP_CONCAT(modded_name, ' > ')
83          FROM (
84            SELECT p.modded_name
85            FROM ancestor_slice(efs.source_id) a
86            JOIN modded_names p ON a.id = p.id
87            ORDER BY p.depth
88          )
89        ) || ' > ' || n.modded_name,
90        n.modded_name
91      )) AS stack_name
92    FROM experimental_flat_slice({}, {}) efs
93    LEFT JOIN modded_names n ON efs.source_id = n.id
94    JOIN thread_track t ON t.id = efs.track_id
95  """.format(start_ts, end_ts))
96
97  tp.query("""
98    DROP TABLE IF EXISTS thread_slice_stack_with_state
99  """)
100
101  tp.query("""
102    CREATE VIRTUAL TABLE thread_slice_stack_with_state
103    USING SPAN_JOIN(
104      thread_slice_stack PARTITIONED utid,
105      thread_state PARTITIONED utid
106    )
107  """)
108
109  if process_name:
110    where_process = "AND process.name = '{}'".format(process_name)
111  else:
112    where_process = ''
113
114  breakdown = tp.query("""
115    SELECT
116      process.name AS process_name,
117      thread.name AS thread_name,
118      CASE
119        WHEN slice.state = 'D' and slice.io_wait
120          THEN 'Uninterruptible sleep (IO)'
121        WHEN slice.state = 'DK' and slice.io_wait
122          THEN 'Uninterruptible sleep + Wake-kill (IO)'
123        WHEN slice.state = 'D' and not slice.io_wait
124          THEN 'Uninterruptible sleep (non-IO)'
125        WHEN slice.state = 'DK' and not slice.io_wait
126          THEN 'Uninterruptible sleep + Wake-kill (non-IO)'
127        WHEN slice.state = 'D'
128          THEN 'Uninterruptible sleep'
129        WHEN slice.state = 'DK'
130          THEN 'Uninterruptible sleep + Wake-kill'
131        WHEN slice.state = 'S' THEN 'Interruptible sleep'
132        WHEN slice.state = 'R' THEN 'Runnable'
133        WHEN slice.state = 'R+' THEN 'Runnable (Preempted)'
134        ELSE slice.state
135      END AS state,
136      slice.stack_name,
137      SUM(slice.dur)/1e6 AS dur_sum,
138      MIN(slice.dur/1e6) AS dur_min,
139      MAX(slice.dur/1e6) AS dur_max,
140      AVG(slice.dur/1e6) AS dur_mean,
141      PERCENTILE(slice.dur/1e6, 50) AS dur_median,
142      PERCENTILE(slice.dur/1e6, 25) AS dur_25_percentile,
143      PERCENTILE(slice.dur/1e6, 75) AS dur_75_percentile,
144      PERCENTILE(slice.dur/1e6, 95) AS dur_95_percentile,
145      PERCENTILE(slice.dur/1e6, 99) AS dur_99_percentile,
146      COUNT(1) as count
147    FROM process
148    JOIN thread USING (upid)
149    JOIN thread_slice_stack_with_state slice USING (utid)
150    WHERE dur != -1 {}
151    GROUP BY thread.name, stack_id, state
152    ORDER BY dur_sum DESC
153  """.format(where_process)).as_pandas_dataframe()
154
155  return breakdown
156
157
158def compute_breakdown_for_startup(tp: TraceProcessor,
159                                  package_name=None,
160                                  process_name=None):
161  """Computes the slice breakdown (like |compute_breakdown|) but only
162  considering slices which happened during an app startup
163
164  Args:
165    tp: the trace processor instance to query.
166    package_name: optional package name to filter for startups. Only a single
167        startup matching this package name should be present. If not specified,
168        only a single startup of any app should be in the trace.
169    process_name: optional process name to filter for slices; specifying
170        this argument can make computing the breakdown a lot faster.
171
172  Returns:
173    The same as |compute_breakdown| but only containing slices which happened
174    during app startup.
175  """
176  tp.metric(['android_startup'])
177
178  # Verify there was only one startup in the trace matching the package
179  # name.
180  filter = "WHERE package = '{}'".format(package_name) if package_name else ''
181  launches = tp.query('''
182    SELECT ts, ts_end, dur
183    FROM launches
184    {}
185  '''.format(filter)).as_pandas_dataframe()
186  if len(launches) == 0:
187    raise TraceProcessorException("Didn't find startup in trace")
188  if len(launches) > 1:
189    raise TraceProcessorException("Found multiple startups in trace")
190
191  start = launches['ts'][0]
192  end = launches['ts_end'][0]
193
194  return compute_breakdown(tp, start, end, process_name)
195