xref: /aosp_15_r20/system/core/init/perfboot.py (revision 00c7fec1bb09f3284aad6a6f96d2f63dfc3650ad)
1*00c7fec1SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*00c7fec1SAndroid Build Coastguard Worker# Copyright (C) 2015 The Android Open Source Project
3*00c7fec1SAndroid Build Coastguard Worker#
4*00c7fec1SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
5*00c7fec1SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
6*00c7fec1SAndroid Build Coastguard Worker# You may obtain a copy of the License at
7*00c7fec1SAndroid Build Coastguard Worker#
8*00c7fec1SAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
9*00c7fec1SAndroid Build Coastguard Worker#
10*00c7fec1SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
11*00c7fec1SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
12*00c7fec1SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*00c7fec1SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
14*00c7fec1SAndroid Build Coastguard Worker# limitations under the License.
15*00c7fec1SAndroid Build Coastguard Worker"""Record the event logs during boot and output them to a file.
16*00c7fec1SAndroid Build Coastguard Worker
17*00c7fec1SAndroid Build Coastguard WorkerThis script repeats the record of each event log during Android boot specified
18*00c7fec1SAndroid Build Coastguard Workertimes. By default, interval between measurements is adjusted in such a way that
19*00c7fec1SAndroid Build Coastguard WorkerCPUs are cooled down sufficiently to avoid boot time slowdown caused by CPU
20*00c7fec1SAndroid Build Coastguard Workerthermal throttling. The result is output in a tab-separated value format.
21*00c7fec1SAndroid Build Coastguard Worker
22*00c7fec1SAndroid Build Coastguard WorkerExamples:
23*00c7fec1SAndroid Build Coastguard Worker
24*00c7fec1SAndroid Build Coastguard WorkerRepeat measurements 10 times. Interval between iterations is adjusted based on
25*00c7fec1SAndroid Build Coastguard WorkerCPU temperature of the device.
26*00c7fec1SAndroid Build Coastguard Worker
27*00c7fec1SAndroid Build Coastguard Worker$ ./perfboot.py --iterations=10
28*00c7fec1SAndroid Build Coastguard Worker
29*00c7fec1SAndroid Build Coastguard WorkerRepeat measurements 20 times. 60 seconds interval is taken between each
30*00c7fec1SAndroid Build Coastguard Workeriteration.
31*00c7fec1SAndroid Build Coastguard Worker
32*00c7fec1SAndroid Build Coastguard Worker$ ./perfboot.py --iterations=20 --interval=60
33*00c7fec1SAndroid Build Coastguard Worker
34*00c7fec1SAndroid Build Coastguard WorkerRepeat measurements 20 times, show verbose output, output the result to
35*00c7fec1SAndroid Build Coastguard Workerdata.tsv, and read event tags from eventtags.txt.
36*00c7fec1SAndroid Build Coastguard Worker
37*00c7fec1SAndroid Build Coastguard Worker$ ./perfboot.py --iterations=30 -v --output=data.tsv --tags=eventtags.txt
38*00c7fec1SAndroid Build Coastguard Worker"""
39*00c7fec1SAndroid Build Coastguard Worker
40*00c7fec1SAndroid Build Coastguard Workerimport argparse
41*00c7fec1SAndroid Build Coastguard Workerimport atexit
42*00c7fec1SAndroid Build Coastguard Workerimport io
43*00c7fec1SAndroid Build Coastguard Workerimport glob
44*00c7fec1SAndroid Build Coastguard Workerimport inspect
45*00c7fec1SAndroid Build Coastguard Workerimport logging
46*00c7fec1SAndroid Build Coastguard Workerimport math
47*00c7fec1SAndroid Build Coastguard Workerimport os
48*00c7fec1SAndroid Build Coastguard Workerimport re
49*00c7fec1SAndroid Build Coastguard Workerimport subprocess
50*00c7fec1SAndroid Build Coastguard Workerimport sys
51*00c7fec1SAndroid Build Coastguard Workerimport threading
52*00c7fec1SAndroid Build Coastguard Workerimport time
53*00c7fec1SAndroid Build Coastguard Worker
54*00c7fec1SAndroid Build Coastguard Workersys.path.append(os.path.dirname(os.path.dirname(__file__)))
55*00c7fec1SAndroid Build Coastguard Workerimport adb
56*00c7fec1SAndroid Build Coastguard Worker
57*00c7fec1SAndroid Build Coastguard Worker# The default event tags to record.
58*00c7fec1SAndroid Build Coastguard Worker_DEFAULT_EVENT_TAGS = [
59*00c7fec1SAndroid Build Coastguard Worker    'boot_progress_start',
60*00c7fec1SAndroid Build Coastguard Worker    'boot_progress_preload_start',
61*00c7fec1SAndroid Build Coastguard Worker    'boot_progress_preload_end',
62*00c7fec1SAndroid Build Coastguard Worker    'boot_progress_system_run',
63*00c7fec1SAndroid Build Coastguard Worker    'boot_progress_pms_start',
64*00c7fec1SAndroid Build Coastguard Worker    'boot_progress_pms_system_scan_start',
65*00c7fec1SAndroid Build Coastguard Worker    'boot_progress_pms_data_scan_start',
66*00c7fec1SAndroid Build Coastguard Worker    'boot_progress_pms_scan_end',
67*00c7fec1SAndroid Build Coastguard Worker    'boot_progress_pms_ready',
68*00c7fec1SAndroid Build Coastguard Worker    'boot_progress_ams_ready',
69*00c7fec1SAndroid Build Coastguard Worker    'boot_progress_enable_screen',
70*00c7fec1SAndroid Build Coastguard Worker    'sf_stop_bootanim',
71*00c7fec1SAndroid Build Coastguard Worker    'wm_boot_animation_done',
72*00c7fec1SAndroid Build Coastguard Worker]
73*00c7fec1SAndroid Build Coastguard Worker
74*00c7fec1SAndroid Build Coastguard Worker
75*00c7fec1SAndroid Build Coastguard Workerclass IntervalAdjuster(object):
76*00c7fec1SAndroid Build Coastguard Worker    """A helper class to take suffficient interval between iterations."""
77*00c7fec1SAndroid Build Coastguard Worker
78*00c7fec1SAndroid Build Coastguard Worker    # CPU temperature values per product used to decide interval
79*00c7fec1SAndroid Build Coastguard Worker    _CPU_COOL_DOWN_THRESHOLDS = {
80*00c7fec1SAndroid Build Coastguard Worker        'flo': 40,
81*00c7fec1SAndroid Build Coastguard Worker        'flounder': 40000,
82*00c7fec1SAndroid Build Coastguard Worker        'razor': 40,
83*00c7fec1SAndroid Build Coastguard Worker        'volantis': 40000,
84*00c7fec1SAndroid Build Coastguard Worker    }
85*00c7fec1SAndroid Build Coastguard Worker    # The interval between CPU temperature checks
86*00c7fec1SAndroid Build Coastguard Worker    _CPU_COOL_DOWN_WAIT_INTERVAL = 10
87*00c7fec1SAndroid Build Coastguard Worker    # The wait time used when the value of _CPU_COOL_DOWN_THRESHOLDS for
88*00c7fec1SAndroid Build Coastguard Worker    # the product is not defined.
89*00c7fec1SAndroid Build Coastguard Worker    _CPU_COOL_DOWN_WAIT_TIME_DEFAULT = 120
90*00c7fec1SAndroid Build Coastguard Worker
91*00c7fec1SAndroid Build Coastguard Worker    def __init__(self, interval, device):
92*00c7fec1SAndroid Build Coastguard Worker        self._interval = interval
93*00c7fec1SAndroid Build Coastguard Worker        self._device = device
94*00c7fec1SAndroid Build Coastguard Worker        self._temp_paths = device.shell(
95*00c7fec1SAndroid Build Coastguard Worker            ['ls', '/sys/class/thermal/thermal_zone*/temp'])[0].splitlines()
96*00c7fec1SAndroid Build Coastguard Worker        self._product = device.get_prop('ro.build.product')
97*00c7fec1SAndroid Build Coastguard Worker        self._waited = False
98*00c7fec1SAndroid Build Coastguard Worker
99*00c7fec1SAndroid Build Coastguard Worker    def wait(self):
100*00c7fec1SAndroid Build Coastguard Worker        """Waits certain amount of time for CPUs cool-down."""
101*00c7fec1SAndroid Build Coastguard Worker        if self._interval is None:
102*00c7fec1SAndroid Build Coastguard Worker            self._wait_cpu_cool_down(self._product, self._temp_paths)
103*00c7fec1SAndroid Build Coastguard Worker        else:
104*00c7fec1SAndroid Build Coastguard Worker            if self._waited:
105*00c7fec1SAndroid Build Coastguard Worker                print('Waiting for %d seconds' % self._interval)
106*00c7fec1SAndroid Build Coastguard Worker                time.sleep(self._interval)
107*00c7fec1SAndroid Build Coastguard Worker        self._waited = True
108*00c7fec1SAndroid Build Coastguard Worker
109*00c7fec1SAndroid Build Coastguard Worker    def _get_cpu_temp(self, threshold):
110*00c7fec1SAndroid Build Coastguard Worker        max_temp = 0
111*00c7fec1SAndroid Build Coastguard Worker        for temp_path in self._temp_paths:
112*00c7fec1SAndroid Build Coastguard Worker            temp = int(self._device.shell(['cat', temp_path])[0].rstrip())
113*00c7fec1SAndroid Build Coastguard Worker            max_temp = max(max_temp, temp)
114*00c7fec1SAndroid Build Coastguard Worker            if temp >= threshold:
115*00c7fec1SAndroid Build Coastguard Worker                return temp
116*00c7fec1SAndroid Build Coastguard Worker        return max_temp
117*00c7fec1SAndroid Build Coastguard Worker
118*00c7fec1SAndroid Build Coastguard Worker    def _wait_cpu_cool_down(self, product, temp_paths):
119*00c7fec1SAndroid Build Coastguard Worker        threshold = IntervalAdjuster._CPU_COOL_DOWN_THRESHOLDS.get(
120*00c7fec1SAndroid Build Coastguard Worker            self._product)
121*00c7fec1SAndroid Build Coastguard Worker        if threshold is None:
122*00c7fec1SAndroid Build Coastguard Worker            print('No CPU temperature threshold is set for ' + self._product)
123*00c7fec1SAndroid Build Coastguard Worker            print(('Just wait %d seconds' %
124*00c7fec1SAndroid Build Coastguard Worker                   IntervalAdjuster._CPU_COOL_DOWN_WAIT_TIME_DEFAULT))
125*00c7fec1SAndroid Build Coastguard Worker            time.sleep(IntervalAdjuster._CPU_COOL_DOWN_WAIT_TIME_DEFAULT)
126*00c7fec1SAndroid Build Coastguard Worker            return
127*00c7fec1SAndroid Build Coastguard Worker        while True:
128*00c7fec1SAndroid Build Coastguard Worker            temp = self._get_cpu_temp(threshold)
129*00c7fec1SAndroid Build Coastguard Worker            if temp < threshold:
130*00c7fec1SAndroid Build Coastguard Worker                logging.info('Current CPU temperature %s' % temp)
131*00c7fec1SAndroid Build Coastguard Worker                return
132*00c7fec1SAndroid Build Coastguard Worker            print('Waiting until CPU temperature (%d) falls below %d' % (
133*00c7fec1SAndroid Build Coastguard Worker                temp, threshold))
134*00c7fec1SAndroid Build Coastguard Worker            time.sleep(IntervalAdjuster._CPU_COOL_DOWN_WAIT_INTERVAL)
135*00c7fec1SAndroid Build Coastguard Worker
136*00c7fec1SAndroid Build Coastguard Worker
137*00c7fec1SAndroid Build Coastguard Workerclass WatchdogTimer(object):
138*00c7fec1SAndroid Build Coastguard Worker    """A timer that makes is_timedout() return true in |timeout| seconds."""
139*00c7fec1SAndroid Build Coastguard Worker    def __init__(self, timeout):
140*00c7fec1SAndroid Build Coastguard Worker        self._timedout = False
141*00c7fec1SAndroid Build Coastguard Worker
142*00c7fec1SAndroid Build Coastguard Worker        def notify_timeout():
143*00c7fec1SAndroid Build Coastguard Worker            self._timedout = True
144*00c7fec1SAndroid Build Coastguard Worker        self._timer = threading.Timer(timeout, notify_timeout)
145*00c7fec1SAndroid Build Coastguard Worker        self._timer.daemon = True
146*00c7fec1SAndroid Build Coastguard Worker        self._timer.start()
147*00c7fec1SAndroid Build Coastguard Worker
148*00c7fec1SAndroid Build Coastguard Worker    def is_timedout(self):
149*00c7fec1SAndroid Build Coastguard Worker        return self._timedout
150*00c7fec1SAndroid Build Coastguard Worker
151*00c7fec1SAndroid Build Coastguard Worker    def cancel(self):
152*00c7fec1SAndroid Build Coastguard Worker        self._timer.cancel()
153*00c7fec1SAndroid Build Coastguard Worker
154*00c7fec1SAndroid Build Coastguard Worker
155*00c7fec1SAndroid Build Coastguard Workerdef readlines_unbuffered(proc):
156*00c7fec1SAndroid Build Coastguard Worker    """Read lines from |proc|'s standard out without buffering."""
157*00c7fec1SAndroid Build Coastguard Worker    while True:
158*00c7fec1SAndroid Build Coastguard Worker        buf = []
159*00c7fec1SAndroid Build Coastguard Worker        c = proc.stdout.read(1)
160*00c7fec1SAndroid Build Coastguard Worker        if c == '' and proc.poll() is not None:
161*00c7fec1SAndroid Build Coastguard Worker            break
162*00c7fec1SAndroid Build Coastguard Worker        while c != '\n':
163*00c7fec1SAndroid Build Coastguard Worker            if c == '' and proc.poll() is not None:
164*00c7fec1SAndroid Build Coastguard Worker                break
165*00c7fec1SAndroid Build Coastguard Worker            buf.append(c)
166*00c7fec1SAndroid Build Coastguard Worker            c = proc.stdout.read(1)
167*00c7fec1SAndroid Build Coastguard Worker        yield ''.join(buf)
168*00c7fec1SAndroid Build Coastguard Worker
169*00c7fec1SAndroid Build Coastguard Worker
170*00c7fec1SAndroid Build Coastguard Workerdef disable_dropbox(device):
171*00c7fec1SAndroid Build Coastguard Worker    """Removes the files created by Dropbox and avoids creating the files."""
172*00c7fec1SAndroid Build Coastguard Worker    device.root()
173*00c7fec1SAndroid Build Coastguard Worker    device.wait()
174*00c7fec1SAndroid Build Coastguard Worker    device.shell(['rm', '-rf', '/system/data/dropbox'])
175*00c7fec1SAndroid Build Coastguard Worker    original_dropbox_max_files = device.shell(
176*00c7fec1SAndroid Build Coastguard Worker        ['settings', 'get', 'global', 'dropbox_max_files'])[0].rstrip()
177*00c7fec1SAndroid Build Coastguard Worker    device.shell(['settings', 'put', 'global', 'dropbox_max_files', '0'])
178*00c7fec1SAndroid Build Coastguard Worker    return original_dropbox_max_files
179*00c7fec1SAndroid Build Coastguard Worker
180*00c7fec1SAndroid Build Coastguard Worker
181*00c7fec1SAndroid Build Coastguard Workerdef restore_dropbox(device, original_dropbox_max_files):
182*00c7fec1SAndroid Build Coastguard Worker    """Restores the dropbox_max_files setting."""
183*00c7fec1SAndroid Build Coastguard Worker    device.root()
184*00c7fec1SAndroid Build Coastguard Worker    device.wait()
185*00c7fec1SAndroid Build Coastguard Worker    if original_dropbox_max_files == 'null':
186*00c7fec1SAndroid Build Coastguard Worker        device.shell(['settings', 'delete', 'global', 'dropbox_max_files'])
187*00c7fec1SAndroid Build Coastguard Worker    else:
188*00c7fec1SAndroid Build Coastguard Worker        device.shell(['settings', 'put', 'global', 'dropbox_max_files',
189*00c7fec1SAndroid Build Coastguard Worker                      original_dropbox_max_files])
190*00c7fec1SAndroid Build Coastguard Worker
191*00c7fec1SAndroid Build Coastguard Worker
192*00c7fec1SAndroid Build Coastguard Workerdef init_perf(device, output, record_list, tags):
193*00c7fec1SAndroid Build Coastguard Worker    device.wait()
194*00c7fec1SAndroid Build Coastguard Worker    debuggable = device.get_prop('ro.debuggable')
195*00c7fec1SAndroid Build Coastguard Worker    original_dropbox_max_files = None
196*00c7fec1SAndroid Build Coastguard Worker    if debuggable == '1':
197*00c7fec1SAndroid Build Coastguard Worker        # Workaround for Dropbox issue (http://b/20890386).
198*00c7fec1SAndroid Build Coastguard Worker        original_dropbox_max_files = disable_dropbox(device)
199*00c7fec1SAndroid Build Coastguard Worker
200*00c7fec1SAndroid Build Coastguard Worker    def cleanup():
201*00c7fec1SAndroid Build Coastguard Worker        try:
202*00c7fec1SAndroid Build Coastguard Worker            if record_list:
203*00c7fec1SAndroid Build Coastguard Worker                print_summary(record_list, tags[-1])
204*00c7fec1SAndroid Build Coastguard Worker                output_results(output, record_list, tags)
205*00c7fec1SAndroid Build Coastguard Worker            if original_dropbox_max_files is not None:
206*00c7fec1SAndroid Build Coastguard Worker                restore_dropbox(device, original_dropbox_max_files)
207*00c7fec1SAndroid Build Coastguard Worker        except (subprocess.CalledProcessError, RuntimeError):
208*00c7fec1SAndroid Build Coastguard Worker            pass
209*00c7fec1SAndroid Build Coastguard Worker    atexit.register(cleanup)
210*00c7fec1SAndroid Build Coastguard Worker
211*00c7fec1SAndroid Build Coastguard Worker
212*00c7fec1SAndroid Build Coastguard Workerdef check_dm_verity_settings(device):
213*00c7fec1SAndroid Build Coastguard Worker    device.wait()
214*00c7fec1SAndroid Build Coastguard Worker    for partition in ['system', 'vendor']:
215*00c7fec1SAndroid Build Coastguard Worker        verity_mode = device.get_prop('partition.%s.verified' % partition)
216*00c7fec1SAndroid Build Coastguard Worker        if verity_mode is None:
217*00c7fec1SAndroid Build Coastguard Worker            logging.warning('dm-verity is not enabled for /%s. Did you run '
218*00c7fec1SAndroid Build Coastguard Worker                            'adb disable-verity? That may skew the result.',
219*00c7fec1SAndroid Build Coastguard Worker                            partition)
220*00c7fec1SAndroid Build Coastguard Worker
221*00c7fec1SAndroid Build Coastguard Worker
222*00c7fec1SAndroid Build Coastguard Workerdef read_event_tags(tags_file):
223*00c7fec1SAndroid Build Coastguard Worker    """Reads event tags from |tags_file|."""
224*00c7fec1SAndroid Build Coastguard Worker    if not tags_file:
225*00c7fec1SAndroid Build Coastguard Worker        return _DEFAULT_EVENT_TAGS
226*00c7fec1SAndroid Build Coastguard Worker    tags = []
227*00c7fec1SAndroid Build Coastguard Worker    with open(tags_file) as f:
228*00c7fec1SAndroid Build Coastguard Worker        for line in f:
229*00c7fec1SAndroid Build Coastguard Worker            if '#' in line:
230*00c7fec1SAndroid Build Coastguard Worker                line = line[:line.find('#')]
231*00c7fec1SAndroid Build Coastguard Worker            line = line.strip()
232*00c7fec1SAndroid Build Coastguard Worker            if line:
233*00c7fec1SAndroid Build Coastguard Worker                tags.append(line)
234*00c7fec1SAndroid Build Coastguard Worker    return tags
235*00c7fec1SAndroid Build Coastguard Worker
236*00c7fec1SAndroid Build Coastguard Worker
237*00c7fec1SAndroid Build Coastguard Workerdef make_event_tags_re(tags):
238*00c7fec1SAndroid Build Coastguard Worker    """Makes a regular expression object that matches event logs of |tags|."""
239*00c7fec1SAndroid Build Coastguard Worker    return re.compile(r'(?P<pid>[0-9]+) +[0-9]+ I (?P<tag>%s): (?P<time>\d+)' %
240*00c7fec1SAndroid Build Coastguard Worker                      '|'.join(tags))
241*00c7fec1SAndroid Build Coastguard Worker
242*00c7fec1SAndroid Build Coastguard Worker
243*00c7fec1SAndroid Build Coastguard Workerdef filter_event_tags(tags, device):
244*00c7fec1SAndroid Build Coastguard Worker    """Drop unknown tags not listed in device's event-log-tags file."""
245*00c7fec1SAndroid Build Coastguard Worker    device.wait()
246*00c7fec1SAndroid Build Coastguard Worker    supported_tags = set()
247*00c7fec1SAndroid Build Coastguard Worker    for l in device.shell(
248*00c7fec1SAndroid Build Coastguard Worker        ['cat', '/system/etc/event-log-tags'])[0].splitlines():
249*00c7fec1SAndroid Build Coastguard Worker        tokens = l.split(' ')
250*00c7fec1SAndroid Build Coastguard Worker        if len(tokens) >= 2:
251*00c7fec1SAndroid Build Coastguard Worker            supported_tags.add(tokens[1])
252*00c7fec1SAndroid Build Coastguard Worker    filtered = []
253*00c7fec1SAndroid Build Coastguard Worker    for tag in tags:
254*00c7fec1SAndroid Build Coastguard Worker        if tag in supported_tags:
255*00c7fec1SAndroid Build Coastguard Worker            filtered.append(tag)
256*00c7fec1SAndroid Build Coastguard Worker        else:
257*00c7fec1SAndroid Build Coastguard Worker            logging.warning('Unknown tag \'%s\'. Ignoring...', tag)
258*00c7fec1SAndroid Build Coastguard Worker    return filtered
259*00c7fec1SAndroid Build Coastguard Worker
260*00c7fec1SAndroid Build Coastguard Worker
261*00c7fec1SAndroid Build Coastguard Workerdef get_values(record, tag):
262*00c7fec1SAndroid Build Coastguard Worker    """Gets values that matches |tag| from |record|."""
263*00c7fec1SAndroid Build Coastguard Worker    keys = [key for key in list(record.keys()) if key[0] == tag]
264*00c7fec1SAndroid Build Coastguard Worker    return [record[k] for k in sorted(keys)]
265*00c7fec1SAndroid Build Coastguard Worker
266*00c7fec1SAndroid Build Coastguard Worker
267*00c7fec1SAndroid Build Coastguard Workerdef get_last_value(record, tag):
268*00c7fec1SAndroid Build Coastguard Worker    """Gets the last value that matches |tag| from |record|."""
269*00c7fec1SAndroid Build Coastguard Worker    values = get_values(record, tag)
270*00c7fec1SAndroid Build Coastguard Worker    if not values:
271*00c7fec1SAndroid Build Coastguard Worker        return 0
272*00c7fec1SAndroid Build Coastguard Worker    return values[-1]
273*00c7fec1SAndroid Build Coastguard Worker
274*00c7fec1SAndroid Build Coastguard Worker
275*00c7fec1SAndroid Build Coastguard Workerdef output_results(filename, record_list, tags):
276*00c7fec1SAndroid Build Coastguard Worker    """Outputs |record_list| into |filename| in a TSV format."""
277*00c7fec1SAndroid Build Coastguard Worker    # First, count the number of the values of each tag.
278*00c7fec1SAndroid Build Coastguard Worker    # This is for dealing with events that occur multiple times.
279*00c7fec1SAndroid Build Coastguard Worker    # For instance, boot_progress_preload_start and boot_progress_preload_end
280*00c7fec1SAndroid Build Coastguard Worker    # are recorded twice on 64-bit system. One is for 64-bit zygote process
281*00c7fec1SAndroid Build Coastguard Worker    # and the other is for 32-bit zygote process.
282*00c7fec1SAndroid Build Coastguard Worker    values_counter = {}
283*00c7fec1SAndroid Build Coastguard Worker    for record in record_list:
284*00c7fec1SAndroid Build Coastguard Worker        for tag in tags:
285*00c7fec1SAndroid Build Coastguard Worker            # Some record might lack values for some tags due to unanticipated
286*00c7fec1SAndroid Build Coastguard Worker            # problems (e.g. timeout), so take the maximum count among all the
287*00c7fec1SAndroid Build Coastguard Worker            # record.
288*00c7fec1SAndroid Build Coastguard Worker            values_counter[tag] = max(values_counter.get(tag, 1),
289*00c7fec1SAndroid Build Coastguard Worker                                      len(get_values(record, tag)))
290*00c7fec1SAndroid Build Coastguard Worker
291*00c7fec1SAndroid Build Coastguard Worker    # Then creates labels for the data. If there are multiple values for one
292*00c7fec1SAndroid Build Coastguard Worker    # tag, labels for these values are numbered except the first one as
293*00c7fec1SAndroid Build Coastguard Worker    # follows:
294*00c7fec1SAndroid Build Coastguard Worker    #
295*00c7fec1SAndroid Build Coastguard Worker    # event_tag event_tag2 event_tag3
296*00c7fec1SAndroid Build Coastguard Worker    #
297*00c7fec1SAndroid Build Coastguard Worker    # The corresponding values are sorted in an ascending order of PID.
298*00c7fec1SAndroid Build Coastguard Worker    labels = []
299*00c7fec1SAndroid Build Coastguard Worker    for tag in tags:
300*00c7fec1SAndroid Build Coastguard Worker        for i in range(1, values_counter[tag] + 1):
301*00c7fec1SAndroid Build Coastguard Worker            labels.append('%s%s' % (tag, '' if i == 1 else str(i)))
302*00c7fec1SAndroid Build Coastguard Worker
303*00c7fec1SAndroid Build Coastguard Worker    # Finally write the data into the file.
304*00c7fec1SAndroid Build Coastguard Worker    with open(filename, 'w') as f:
305*00c7fec1SAndroid Build Coastguard Worker        f.write('\t'.join(labels) + '\n')
306*00c7fec1SAndroid Build Coastguard Worker        for record in record_list:
307*00c7fec1SAndroid Build Coastguard Worker            line = io.StringIO()
308*00c7fec1SAndroid Build Coastguard Worker            invalid_line = False
309*00c7fec1SAndroid Build Coastguard Worker            for i, tag in enumerate(tags):
310*00c7fec1SAndroid Build Coastguard Worker                if i != 0:
311*00c7fec1SAndroid Build Coastguard Worker                    line.write('\t')
312*00c7fec1SAndroid Build Coastguard Worker                values = get_values(record, tag)
313*00c7fec1SAndroid Build Coastguard Worker                if len(values) < values_counter[tag]:
314*00c7fec1SAndroid Build Coastguard Worker                    invalid_line = True
315*00c7fec1SAndroid Build Coastguard Worker                    # Fill invalid record with 0
316*00c7fec1SAndroid Build Coastguard Worker                    values += [0] * (values_counter[tag] - len(values))
317*00c7fec1SAndroid Build Coastguard Worker                line.write('\t'.join(str(t) for t in values))
318*00c7fec1SAndroid Build Coastguard Worker            if invalid_line:
319*00c7fec1SAndroid Build Coastguard Worker                logging.error('Invalid record found: ' + line.getvalue())
320*00c7fec1SAndroid Build Coastguard Worker            line.write('\n')
321*00c7fec1SAndroid Build Coastguard Worker            f.write(line.getvalue())
322*00c7fec1SAndroid Build Coastguard Worker    print(('Wrote: ' + filename))
323*00c7fec1SAndroid Build Coastguard Worker
324*00c7fec1SAndroid Build Coastguard Worker
325*00c7fec1SAndroid Build Coastguard Workerdef median(data):
326*00c7fec1SAndroid Build Coastguard Worker    """Calculates the median value from |data|."""
327*00c7fec1SAndroid Build Coastguard Worker    data = sorted(data)
328*00c7fec1SAndroid Build Coastguard Worker    n = len(data)
329*00c7fec1SAndroid Build Coastguard Worker    if n % 2 == 1:
330*00c7fec1SAndroid Build Coastguard Worker        return data[n / 2]
331*00c7fec1SAndroid Build Coastguard Worker    else:
332*00c7fec1SAndroid Build Coastguard Worker        n2 = n / 2
333*00c7fec1SAndroid Build Coastguard Worker        return (data[n2 - 1] + data[n2]) / 2.0
334*00c7fec1SAndroid Build Coastguard Worker
335*00c7fec1SAndroid Build Coastguard Worker
336*00c7fec1SAndroid Build Coastguard Workerdef mean(data):
337*00c7fec1SAndroid Build Coastguard Worker    """Calculates the mean value from |data|."""
338*00c7fec1SAndroid Build Coastguard Worker    return float(sum(data)) / len(data)
339*00c7fec1SAndroid Build Coastguard Worker
340*00c7fec1SAndroid Build Coastguard Worker
341*00c7fec1SAndroid Build Coastguard Workerdef stddev(data):
342*00c7fec1SAndroid Build Coastguard Worker    """Calculates the standard deviation value from |value|."""
343*00c7fec1SAndroid Build Coastguard Worker    m = mean(data)
344*00c7fec1SAndroid Build Coastguard Worker    return math.sqrt(sum((x - m) ** 2 for x in data) / len(data))
345*00c7fec1SAndroid Build Coastguard Worker
346*00c7fec1SAndroid Build Coastguard Worker
347*00c7fec1SAndroid Build Coastguard Workerdef print_summary(record_list, end_tag):
348*00c7fec1SAndroid Build Coastguard Worker    """Prints the summary of |record_list|."""
349*00c7fec1SAndroid Build Coastguard Worker    # Filter out invalid data.
350*00c7fec1SAndroid Build Coastguard Worker    end_times = [get_last_value(record, end_tag) for record in record_list
351*00c7fec1SAndroid Build Coastguard Worker                 if get_last_value(record, end_tag) != 0]
352*00c7fec1SAndroid Build Coastguard Worker    print(('mean:', int(round(mean(end_times))), 'ms'))
353*00c7fec1SAndroid Build Coastguard Worker    print(('median:', int(round(median(end_times))), 'ms'))
354*00c7fec1SAndroid Build Coastguard Worker    print(('standard deviation:', int(round(stddev(end_times))), 'ms'))
355*00c7fec1SAndroid Build Coastguard Worker
356*00c7fec1SAndroid Build Coastguard Worker
357*00c7fec1SAndroid Build Coastguard Workerdef do_iteration(device, interval_adjuster, event_tags_re, end_tag):
358*00c7fec1SAndroid Build Coastguard Worker    """Measures the boot time once."""
359*00c7fec1SAndroid Build Coastguard Worker    device.wait()
360*00c7fec1SAndroid Build Coastguard Worker    interval_adjuster.wait()
361*00c7fec1SAndroid Build Coastguard Worker    device.reboot()
362*00c7fec1SAndroid Build Coastguard Worker    print('Rebooted the device, waiting for tag', end_tag)
363*00c7fec1SAndroid Build Coastguard Worker    record = {}
364*00c7fec1SAndroid Build Coastguard Worker    booted = False
365*00c7fec1SAndroid Build Coastguard Worker    while not booted:
366*00c7fec1SAndroid Build Coastguard Worker        device.wait()
367*00c7fec1SAndroid Build Coastguard Worker        # Stop the iteration if it does not finish within 120 seconds.
368*00c7fec1SAndroid Build Coastguard Worker        timeout = 120
369*00c7fec1SAndroid Build Coastguard Worker        t = WatchdogTimer(timeout)
370*00c7fec1SAndroid Build Coastguard Worker        p = subprocess.Popen(
371*00c7fec1SAndroid Build Coastguard Worker                ['adb', 'logcat', '-b', 'events', '-v', 'threadtime'],
372*00c7fec1SAndroid Build Coastguard Worker                stdout=subprocess.PIPE)
373*00c7fec1SAndroid Build Coastguard Worker        for line in readlines_unbuffered(p):
374*00c7fec1SAndroid Build Coastguard Worker            if t.is_timedout():
375*00c7fec1SAndroid Build Coastguard Worker                print('*** Timed out ***')
376*00c7fec1SAndroid Build Coastguard Worker                return record
377*00c7fec1SAndroid Build Coastguard Worker            m = event_tags_re.search(line)
378*00c7fec1SAndroid Build Coastguard Worker            if not m:
379*00c7fec1SAndroid Build Coastguard Worker                continue
380*00c7fec1SAndroid Build Coastguard Worker            tag = m.group('tag')
381*00c7fec1SAndroid Build Coastguard Worker            event_time = int(m.group('time'))
382*00c7fec1SAndroid Build Coastguard Worker            pid = m.group('pid')
383*00c7fec1SAndroid Build Coastguard Worker            record[(tag, pid)] = event_time
384*00c7fec1SAndroid Build Coastguard Worker            print(('Event log recorded: %s (%s) - %d ms' % (
385*00c7fec1SAndroid Build Coastguard Worker                tag, pid, event_time)))
386*00c7fec1SAndroid Build Coastguard Worker            if tag == end_tag:
387*00c7fec1SAndroid Build Coastguard Worker                booted = True
388*00c7fec1SAndroid Build Coastguard Worker                t.cancel()
389*00c7fec1SAndroid Build Coastguard Worker                break
390*00c7fec1SAndroid Build Coastguard Worker    return record
391*00c7fec1SAndroid Build Coastguard Worker
392*00c7fec1SAndroid Build Coastguard Worker
393*00c7fec1SAndroid Build Coastguard Workerdef parse_args():
394*00c7fec1SAndroid Build Coastguard Worker    """Parses the command line arguments."""
395*00c7fec1SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser(
396*00c7fec1SAndroid Build Coastguard Worker        description=inspect.getdoc(sys.modules[__name__]),
397*00c7fec1SAndroid Build Coastguard Worker        formatter_class=argparse.RawDescriptionHelpFormatter)
398*00c7fec1SAndroid Build Coastguard Worker    parser.add_argument('--iterations', type=int, default=5,
399*00c7fec1SAndroid Build Coastguard Worker                        help='Number of times to repeat boot measurements.')
400*00c7fec1SAndroid Build Coastguard Worker    parser.add_argument('--interval', type=int,
401*00c7fec1SAndroid Build Coastguard Worker                        help=('Duration between iterations. If this is not '
402*00c7fec1SAndroid Build Coastguard Worker                              'set explicitly, durations are determined '
403*00c7fec1SAndroid Build Coastguard Worker                              'adaptively based on CPUs temperature.'))
404*00c7fec1SAndroid Build Coastguard Worker    parser.add_argument('-o', '--output', help='File name of output data.')
405*00c7fec1SAndroid Build Coastguard Worker    parser.add_argument('-v', '--verbose', action='store_true',
406*00c7fec1SAndroid Build Coastguard Worker                        help='Show verbose output.')
407*00c7fec1SAndroid Build Coastguard Worker    parser.add_argument('-s', '--serial', default=os.getenv('ANDROID_SERIAL'),
408*00c7fec1SAndroid Build Coastguard Worker                        help='Adb device serial number.')
409*00c7fec1SAndroid Build Coastguard Worker    parser.add_argument('-t', '--tags', help='Specify the filename from which '
410*00c7fec1SAndroid Build Coastguard Worker                        'event tags are read. Every line contains one event '
411*00c7fec1SAndroid Build Coastguard Worker                        'tag and the last event tag is used to detect that '
412*00c7fec1SAndroid Build Coastguard Worker                        'the device has finished booting unless --end-tag is '
413*00c7fec1SAndroid Build Coastguard Worker                        'specified.')
414*00c7fec1SAndroid Build Coastguard Worker    parser.add_argument('--end-tag', help='An event tag on which the script '
415*00c7fec1SAndroid Build Coastguard Worker                        'stops measuring the boot time.')
416*00c7fec1SAndroid Build Coastguard Worker    parser.add_argument('--apk-dir', help='Specify the directory which contains '
417*00c7fec1SAndroid Build Coastguard Worker                        'APK files to be installed before measuring boot time.')
418*00c7fec1SAndroid Build Coastguard Worker    return parser.parse_args()
419*00c7fec1SAndroid Build Coastguard Worker
420*00c7fec1SAndroid Build Coastguard Worker
421*00c7fec1SAndroid Build Coastguard Workerdef install_apks(device, apk_dir):
422*00c7fec1SAndroid Build Coastguard Worker    for apk in glob.glob(os.path.join(apk_dir, '*.apk')):
423*00c7fec1SAndroid Build Coastguard Worker        print('Installing: ' + apk)
424*00c7fec1SAndroid Build Coastguard Worker        device.install(apk, replace=True)
425*00c7fec1SAndroid Build Coastguard Worker
426*00c7fec1SAndroid Build Coastguard Worker
427*00c7fec1SAndroid Build Coastguard Workerdef main():
428*00c7fec1SAndroid Build Coastguard Worker    args = parse_args()
429*00c7fec1SAndroid Build Coastguard Worker    if args.verbose:
430*00c7fec1SAndroid Build Coastguard Worker        logging.getLogger().setLevel(logging.INFO)
431*00c7fec1SAndroid Build Coastguard Worker
432*00c7fec1SAndroid Build Coastguard Worker    device = adb.get_device(args.serial)
433*00c7fec1SAndroid Build Coastguard Worker
434*00c7fec1SAndroid Build Coastguard Worker    if not args.output:
435*00c7fec1SAndroid Build Coastguard Worker        device.wait()
436*00c7fec1SAndroid Build Coastguard Worker        args.output = 'perf-%s-%s.tsv' % (
437*00c7fec1SAndroid Build Coastguard Worker            device.get_prop('ro.build.flavor'),
438*00c7fec1SAndroid Build Coastguard Worker            device.get_prop('ro.build.version.incremental'))
439*00c7fec1SAndroid Build Coastguard Worker    check_dm_verity_settings(device)
440*00c7fec1SAndroid Build Coastguard Worker
441*00c7fec1SAndroid Build Coastguard Worker    if args.apk_dir:
442*00c7fec1SAndroid Build Coastguard Worker        install_apks(device, args.apk_dir)
443*00c7fec1SAndroid Build Coastguard Worker
444*00c7fec1SAndroid Build Coastguard Worker    record_list = []
445*00c7fec1SAndroid Build Coastguard Worker    event_tags = filter_event_tags(read_event_tags(args.tags), device)
446*00c7fec1SAndroid Build Coastguard Worker    end_tag = args.end_tag or event_tags[-1]
447*00c7fec1SAndroid Build Coastguard Worker    if end_tag not in event_tags:
448*00c7fec1SAndroid Build Coastguard Worker        sys.exit('%s is not a valid tag.' % end_tag)
449*00c7fec1SAndroid Build Coastguard Worker    event_tags = event_tags[0 : event_tags.index(end_tag) + 1]
450*00c7fec1SAndroid Build Coastguard Worker    init_perf(device, args.output, record_list, event_tags)
451*00c7fec1SAndroid Build Coastguard Worker    interval_adjuster = IntervalAdjuster(args.interval, device)
452*00c7fec1SAndroid Build Coastguard Worker    event_tags_re = make_event_tags_re(event_tags)
453*00c7fec1SAndroid Build Coastguard Worker
454*00c7fec1SAndroid Build Coastguard Worker    for i in range(args.iterations):
455*00c7fec1SAndroid Build Coastguard Worker        print('Run #%d ' % i)
456*00c7fec1SAndroid Build Coastguard Worker        record = do_iteration(
457*00c7fec1SAndroid Build Coastguard Worker            device, interval_adjuster, event_tags_re, end_tag)
458*00c7fec1SAndroid Build Coastguard Worker        record_list.append(record)
459*00c7fec1SAndroid Build Coastguard Worker
460*00c7fec1SAndroid Build Coastguard Worker
461*00c7fec1SAndroid Build Coastguard Workerif __name__ == '__main__':
462*00c7fec1SAndroid Build Coastguard Worker    main()
463