summaryrefslogtreecommitdiff
path: root/startop/scripts/app_startup/run_app_with_prefetch.py
blob: 2f1eff2c41f67a96420919e419bc58cfd97fce30 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
#!/usr/bin/env python3
#
# Copyright 2019, The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Runner of one test given a setting.

Run app and gather the measurement in a certain configuration.
Print the result to stdout.
See --help for more details.

Sample usage:
  $> ./python run_app_with_prefetch.py  -p com.android.settings -a
     com.android.settings.Settings -r fadvise -i input

"""

import argparse
import os
import sys
import time
from typing import List, Tuple, Optional

# local imports
import lib.adb_utils as adb_utils
from lib.app_runner import AppRunner, AppRunnerListener

# global variables
DIR = os.path.abspath(os.path.dirname(__file__))

sys.path.append(os.path.dirname(DIR))
import lib.print_utils as print_utils
import lib.cmd_utils as cmd_utils
import iorap.lib.iorapd_utils as iorapd_utils

class PrefetchAppRunner(AppRunnerListener):
  def __init__(self,
               package: str,
               activity: Optional[str],
               readahead: str,
               compiler_filter: Optional[str],
               timeout: Optional[int],
               simulate: bool,
               debug: bool,
               input:Optional[str],
               **kwargs):
    self.app_runner = AppRunner(package,
                                activity,
                                compiler_filter,
                                timeout,
                                simulate)
    self.app_runner.add_callbacks(self)

    self.simulate = simulate
    self.readahead = readahead
    self.debug = debug
    self.input = input
    print_utils.DEBUG = self.debug
    cmd_utils.SIMULATE = self.simulate


  def run(self) -> Optional[List[Tuple[str]]]:
    """Runs an app.

    Returns:
      A list of (metric, value) tuples.
    """
    return self.app_runner.run()

  def preprocess(self):
    passed = self.validate_options()
    if not passed:
      return

    # Sets up adb environment.
    adb_utils.root()
    adb_utils.disable_selinux()
    time.sleep(1)

    # Kill any existing process of this app
    adb_utils.pkill(self.app_runner.package)

    if self.readahead != 'warm':
      print_utils.debug_print('Drop caches for non-warm start.')
      # Drop all caches to get cold starts.
      adb_utils.vm_drop_cache()

    if self.readahead != 'warm' and self.readahead != 'cold':
      iorapd_utils.enable_iorapd_readahead()

  def postprocess(self, pre_launch_timestamp: str):
    passed = self._perform_post_launch_cleanup(pre_launch_timestamp)
    if not passed and not self.app_runner.simulate:
      print_utils.error_print('Cannot perform post launch cleanup!')
      return None

    # Kill any existing process of this app
    adb_utils.pkill(self.app_runner.package)

  def _perform_post_launch_cleanup(self, logcat_timestamp: str) -> bool:
    """Performs cleanup at the end of each loop iteration.

    Returns:
      A bool indicates whether the cleanup succeeds or not.
    """
    if self.readahead != 'warm' and self.readahead != 'cold':
      passed = iorapd_utils.wait_for_iorapd_finish(self.app_runner.package,
                                                   self.app_runner.activity,
                                                   self.app_runner.timeout,
                                                   self.debug,
                                                   logcat_timestamp)

      if not passed:
        return passed

      return iorapd_utils.disable_iorapd_readahead()

    # Don't need to do anything for warm or cold.
    return True

  def metrics_selector(self, am_start_output: str,
                       pre_launch_timestamp: str) -> str:
    """Parses the metric after app startup by reading from logcat in a blocking
    manner until all metrics have been found".

    Returns:
      the total time and displayed time of app startup.
      For example: "TotalTime=123\nDisplayedTime=121
    """
    total_time = AppRunner.parse_total_time(am_start_output)
    displayed_time = adb_utils.blocking_wait_for_logcat_displayed_time(
        pre_launch_timestamp, self.app_runner.package, self.app_runner.timeout)

    return 'TotalTime={}\nDisplayedTime={}'.format(total_time, displayed_time)

  def validate_options(self) -> bool:
    """Validates the activity and trace file if needed.

    Returns:
      A bool indicates whether the activity is valid.
    """
    needs_trace_file = self.readahead != 'cold' and self.readahead != 'warm'
    if needs_trace_file and (self.input is None or
                             not os.path.exists(self.input)):
      print_utils.error_print('--input not specified!')
      return False

    # Install necessary trace file. This must be after the activity checking.
    if needs_trace_file:
      passed = iorapd_utils.iorapd_compiler_install_trace_file(
          self.app_runner.package, self.app_runner.activity, self.input)
      if not cmd_utils.SIMULATE and not passed:
        print_utils.error_print('Failed to install compiled TraceFile.pb for '
                                '"{}/{}"'.
                                    format(self.app_runner.package,
                                           self.app_runner.activity))
        return False

    return True



def parse_options(argv: List[str] = None):
  """Parses command line arguments and return an argparse Namespace object."""
  parser = argparse.ArgumentParser(
      description='Run an Android application once and measure startup time.'
  )

  required_named = parser.add_argument_group('required named arguments')
  required_named.add_argument('-p', '--package', action='store', dest='package',
                              help='package of the application', required=True)

  # optional arguments
  # use a group here to get the required arguments to appear 'above' the
  # optional arguments in help.
  optional_named = parser.add_argument_group('optional named arguments')
  optional_named.add_argument('-a', '--activity', action='store',
                              dest='activity',
                              help='launch activity of the application')
  optional_named.add_argument('-s', '--simulate', dest='simulate',
                              action='store_true',
                              help='simulate the process without executing '
                                   'any shell commands')
  optional_named.add_argument('-d', '--debug', dest='debug',
                              action='store_true',
                              help='Add extra debugging output')
  optional_named.add_argument('-i', '--input', action='store', dest='input',
                              help='perfetto trace file protobuf',
                              default='TraceFile.pb')
  optional_named.add_argument('-r', '--readahead', action='store',
                              dest='readahead',
                              help='which readahead mode to use',
                              default='cold',
                              choices=('warm', 'cold', 'mlock', 'fadvise'))
  optional_named.add_argument('-t', '--timeout', dest='timeout', action='store',
                              type=int,
                              help='Timeout after this many seconds when '
                                   'executing a single run.',
                              default=10)
  optional_named.add_argument('--compiler-filter', dest='compiler_filter',
                              action='store',
                              help='Which compiler filter to use.',
                              default=None)

  return parser.parse_args(argv)

def main():
  opts = parse_options()
  runner = PrefetchAppRunner(**vars(opts))
  result = runner.run()

  if result is None:
    return 1

  print(result)
  return 0

if __name__ == '__main__':
  sys.exit(main())