Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # Copyright 2018, The Android Open Source Project |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | |
| 17 | # |
| 18 | # |
| 19 | # Measure application start-up time by launching applications under various combinations. |
| 20 | # See --help for more details. |
| 21 | # |
| 22 | # |
| 23 | # Sample usage: |
| 24 | # $> ./app_startup_runner.py -p com.google.android.calculator -r warm -r cold -lc 10 -o out.csv |
| 25 | # $> ./analyze_metrics.py out.csv |
| 26 | # |
| 27 | # |
| 28 | |
| 29 | import argparse |
| 30 | import csv |
| 31 | import itertools |
| 32 | import os |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 33 | import sys |
| 34 | import tempfile |
Yan Wang | 6ddab32 | 2019-07-26 10:53:45 -0700 | [diff] [blame] | 35 | from datetime import timedelta |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 36 | from typing import Any, Callable, Iterable, List, NamedTuple, TextIO, Tuple, \ |
Yan Wang | 06f5488 | 2019-07-23 18:09:41 -0700 | [diff] [blame] | 37 | TypeVar, Union, Optional |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 38 | |
| 39 | # local import |
| 40 | DIR = os.path.abspath(os.path.dirname(__file__)) |
| 41 | sys.path.append(os.path.dirname(DIR)) |
Yan Wang | 6ddab32 | 2019-07-26 10:53:45 -0700 | [diff] [blame] | 42 | import lib.cmd_utils as cmd_utils |
| 43 | import lib.print_utils as print_utils |
| 44 | import iorap.compiler as compiler |
Yan Wang | 06f5488 | 2019-07-23 18:09:41 -0700 | [diff] [blame] | 45 | from app_startup.run_app_with_prefetch import PrefetchAppRunner |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 46 | import app_startup.lib.args_utils as args_utils |
| 47 | from app_startup.lib.data_frame import DataFrame |
Yan Wang | 6ddab32 | 2019-07-26 10:53:45 -0700 | [diff] [blame] | 48 | from app_startup.lib.perfetto_trace_collector import PerfettoTraceCollector |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 49 | |
| 50 | # The following command line options participate in the combinatorial generation. |
| 51 | # All other arguments have a global effect. |
Yan Wang | 6ddab32 | 2019-07-26 10:53:45 -0700 | [diff] [blame] | 52 | _COMBINATORIAL_OPTIONS = ['package', 'readahead', 'compiler_filter', |
| 53 | 'activity', 'trace_duration'] |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 54 | _TRACING_READAHEADS = ['mlock', 'fadvise'] |
| 55 | _FORWARD_OPTIONS = {'loop_count': '--count'} |
| 56 | _RUN_SCRIPT = os.path.join(os.path.dirname(os.path.realpath(__file__)), |
| 57 | 'run_app_with_prefetch.py') |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 58 | |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 59 | CollectorPackageInfo = NamedTuple('CollectorPackageInfo', |
| 60 | [('package', str), ('compiler_filter', str)]) |
Yan Wang | 6ddab32 | 2019-07-26 10:53:45 -0700 | [diff] [blame] | 61 | _COMPILER_SCRIPT = os.path.join(os.path.dirname(os.path.dirname( |
| 62 | os.path.realpath(__file__))), 'iorap/compiler.py') |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 63 | # by 2; systrace starts up slowly. |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 64 | |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 65 | _UNLOCK_SCREEN_SCRIPT = os.path.join( |
| 66 | os.path.dirname(os.path.realpath(__file__)), 'unlock_screen') |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 67 | |
Yan Wang | 06f5488 | 2019-07-23 18:09:41 -0700 | [diff] [blame] | 68 | RunCommandArgs = NamedTuple('RunCommandArgs', |
| 69 | [('package', str), |
| 70 | ('readahead', str), |
| 71 | ('activity', Optional[str]), |
| 72 | ('compiler_filter', Optional[str]), |
| 73 | ('timeout', Optional[int]), |
| 74 | ('debug', bool), |
| 75 | ('simulate', bool), |
Yan Wang | 6ddab32 | 2019-07-26 10:53:45 -0700 | [diff] [blame] | 76 | ('input', Optional[str]), |
| 77 | ('trace_duration', Optional[timedelta])]) |
Yan Wang | 06f5488 | 2019-07-23 18:09:41 -0700 | [diff] [blame] | 78 | |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 79 | # This must be the only mutable global variable. All other global variables are constants to avoid magic literals. |
| 80 | _debug = False # See -d/--debug flag. |
| 81 | _DEBUG_FORCE = None # Ignore -d/--debug if this is not none. |
Yan Wang | 6ddab32 | 2019-07-26 10:53:45 -0700 | [diff] [blame] | 82 | _PERFETTO_TRACE_DURATION_MS = 5000 # milliseconds |
| 83 | _PERFETTO_TRACE_DURATION = timedelta(milliseconds=_PERFETTO_TRACE_DURATION_MS) |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 84 | |
| 85 | # Type hinting names. |
| 86 | T = TypeVar('T') |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 87 | NamedTupleMeta = Callable[ |
| 88 | ..., T] # approximation of a (S : NamedTuple<T> where S() == T) metatype. |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 89 | |
| 90 | def parse_options(argv: List[str] = None): |
| 91 | """Parse command line arguments and return an argparse Namespace object.""" |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 92 | parser = argparse.ArgumentParser(description="Run one or more Android " |
| 93 | "applications under various " |
| 94 | "settings in order to measure " |
| 95 | "startup time.") |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 96 | # argparse considers args starting with - and -- optional in --help, even though required=True. |
| 97 | # by using a named argument group --help will clearly say that it's required instead of optional. |
| 98 | required_named = parser.add_argument_group('required named arguments') |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 99 | required_named.add_argument('-p', '--package', action='append', |
| 100 | dest='packages', |
| 101 | help='package of the application', required=True) |
| 102 | required_named.add_argument('-r', '--readahead', action='append', |
| 103 | dest='readaheads', |
| 104 | help='which readahead mode to use', |
| 105 | choices=('warm', 'cold', 'mlock', 'fadvise'), |
| 106 | required=True) |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 107 | |
| 108 | # optional arguments |
| 109 | # use a group here to get the required arguments to appear 'above' the optional arguments in help. |
| 110 | optional_named = parser.add_argument_group('optional named arguments') |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 111 | optional_named.add_argument('-c', '--compiler-filter', action='append', |
| 112 | dest='compiler_filters', |
| 113 | help='which compiler filter to use. if omitted it does not enforce the app\'s compiler filter', |
| 114 | choices=('speed', 'speed-profile', 'quicken')) |
| 115 | optional_named.add_argument('-s', '--simulate', dest='simulate', |
| 116 | action='store_true', |
| 117 | help='Print which commands will run, but don\'t run the apps') |
| 118 | optional_named.add_argument('-d', '--debug', dest='debug', |
| 119 | action='store_true', |
| 120 | help='Add extra debugging output') |
| 121 | optional_named.add_argument('-o', '--output', dest='output', action='store', |
| 122 | help='Write CSV output to file.') |
| 123 | optional_named.add_argument('-t', '--timeout', dest='timeout', action='store', |
| 124 | type=int, default=10, |
| 125 | help='Timeout after this many seconds when executing a single run.') |
| 126 | optional_named.add_argument('-lc', '--loop-count', dest='loop_count', |
| 127 | default=1, type=int, action='store', |
| 128 | help='How many times to loop a single run.') |
| 129 | optional_named.add_argument('-in', '--inodes', dest='inodes', type=str, |
| 130 | action='store', |
| 131 | help='Path to inodes file (system/extras/pagecache/pagecache.py -d inodes)') |
Yan Wang | 6ddab32 | 2019-07-26 10:53:45 -0700 | [diff] [blame] | 132 | optional_named.add_argument('--compiler-trace-duration-ms', |
| 133 | dest='trace_duration', |
| 134 | type=lambda ms_str: timedelta(milliseconds=int(ms_str)), |
| 135 | action='append', |
| 136 | help='The trace duration (milliseconds) in ' |
| 137 | 'compilation') |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 138 | |
| 139 | return parser.parse_args(argv) |
| 140 | |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 141 | def key_to_cmdline_flag(key: str) -> str: |
| 142 | """Convert key into a command line flag, e.g. 'foo-bars' -> '--foo-bar' """ |
| 143 | if key.endswith("s"): |
| 144 | key = key[:-1] |
| 145 | return "--" + key.replace("_", "-") |
| 146 | |
| 147 | def as_run_command(tpl: NamedTuple) -> List[Union[str, Any]]: |
| 148 | """ |
| 149 | Convert a named tuple into a command-line compatible arguments list. |
| 150 | |
| 151 | Example: ABC(1, 2, 3) -> ['--a', 1, '--b', 2, '--c', 3] |
| 152 | """ |
| 153 | args = [] |
| 154 | for key, value in tpl._asdict().items(): |
| 155 | if value is None: |
| 156 | continue |
| 157 | args.append(key_to_cmdline_flag(key)) |
| 158 | args.append(value) |
| 159 | return args |
| 160 | |
Yan Wang | 6ddab32 | 2019-07-26 10:53:45 -0700 | [diff] [blame] | 161 | def run_perfetto_collector(collector_info: CollectorPackageInfo, |
| 162 | timeout: int, |
| 163 | simulate: bool) -> Tuple[bool, TextIO]: |
| 164 | """Run collector to collect prefetching trace. |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 165 | |
Yan Wang | 6ddab32 | 2019-07-26 10:53:45 -0700 | [diff] [blame] | 166 | Returns: |
| 167 | A tuple of whether the collection succeeds and the generated trace file. |
| 168 | """ |
| 169 | tmp_output_file = tempfile.NamedTemporaryFile() |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 170 | |
Yan Wang | 6ddab32 | 2019-07-26 10:53:45 -0700 | [diff] [blame] | 171 | collector = PerfettoTraceCollector(package=collector_info.package, |
| 172 | activity=None, |
| 173 | compiler_filter=collector_info.compiler_filter, |
| 174 | timeout=timeout, |
| 175 | simulate=simulate, |
| 176 | trace_duration=_PERFETTO_TRACE_DURATION, |
| 177 | save_destination_file_path=tmp_output_file.name) |
| 178 | result = collector.run() |
| 179 | |
| 180 | return result is not None, tmp_output_file |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 181 | |
Igor Murashkin | ab37e6e | 2019-05-13 16:31:25 -0700 | [diff] [blame] | 182 | def parse_run_script_csv_file(csv_file: TextIO) -> DataFrame: |
| 183 | """Parse a CSV file full of integers into a DataFrame.""" |
| 184 | csv_reader = csv.reader(csv_file) |
| 185 | |
| 186 | try: |
| 187 | header_list = next(csv_reader) |
| 188 | except StopIteration: |
| 189 | header_list = [] |
| 190 | |
| 191 | if not header_list: |
| 192 | return None |
| 193 | |
| 194 | headers = [i for i in header_list] |
| 195 | |
| 196 | d = {} |
| 197 | for row in csv_reader: |
| 198 | header_idx = 0 |
| 199 | |
| 200 | for i in row: |
| 201 | v = i |
| 202 | if i: |
| 203 | v = int(i) |
| 204 | |
| 205 | header_key = headers[header_idx] |
| 206 | l = d.get(header_key, []) |
| 207 | l.append(v) |
| 208 | d[header_key] = l |
| 209 | |
| 210 | header_idx = header_idx + 1 |
| 211 | |
| 212 | return DataFrame(d) |
| 213 | |
Yan Wang | 6ddab32 | 2019-07-26 10:53:45 -0700 | [diff] [blame] | 214 | def compile_perfetto_trace(inodes_path: str, |
| 215 | perfetto_trace_file: str, |
| 216 | trace_duration: Optional[timedelta]) -> TextIO: |
| 217 | compiler_trace_file = tempfile.NamedTemporaryFile() |
| 218 | argv = [_COMPILER_SCRIPT, '-i', inodes_path, '--perfetto-trace', |
| 219 | perfetto_trace_file, '-o', compiler_trace_file.name] |
| 220 | |
| 221 | if trace_duration is not None: |
| 222 | argv += ['--duration', str(int(trace_duration.total_seconds() |
| 223 | * PerfettoTraceCollector.MS_PER_SEC))] |
| 224 | |
| 225 | print_utils.debug_print(argv) |
| 226 | compiler.main(argv) |
| 227 | return compiler_trace_file |
| 228 | |
| 229 | def execute_run_using_perfetto_trace(collector_info, |
| 230 | run_combos: Iterable[RunCommandArgs], |
| 231 | simulate: bool, |
| 232 | inodes_path: str, |
| 233 | timeout: int) -> DataFrame: |
| 234 | """ Executes run based on perfetto trace. """ |
| 235 | passed, perfetto_trace_file = run_perfetto_collector(collector_info, |
| 236 | timeout, |
| 237 | simulate) |
| 238 | if not passed: |
| 239 | raise RuntimeError('Cannot run perfetto collector!') |
| 240 | |
| 241 | with perfetto_trace_file: |
| 242 | for combos in run_combos: |
| 243 | if combos.readahead in _TRACING_READAHEADS: |
| 244 | if simulate: |
| 245 | compiler_trace_file = tempfile.NamedTemporaryFile() |
| 246 | else: |
| 247 | compiler_trace_file = compile_perfetto_trace(inodes_path, |
| 248 | perfetto_trace_file.name, |
| 249 | combos.trace_duration) |
| 250 | with compiler_trace_file: |
| 251 | combos = combos._replace(input=compiler_trace_file.name) |
| 252 | print_utils.debug_print(combos) |
| 253 | output = PrefetchAppRunner(**combos._asdict()).run() |
| 254 | else: |
| 255 | print_utils.debug_print(combos) |
| 256 | output = PrefetchAppRunner(**combos._asdict()).run() |
| 257 | |
| 258 | yield DataFrame(dict((x, [y]) for x, y in output)) if output else None |
| 259 | |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 260 | def execute_run_combos( |
Yan Wang | 06f5488 | 2019-07-23 18:09:41 -0700 | [diff] [blame] | 261 | grouped_run_combos: Iterable[Tuple[CollectorPackageInfo, Iterable[RunCommandArgs]]], |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 262 | simulate: bool, |
| 263 | inodes_path: str, |
| 264 | timeout: int): |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 265 | # nothing will work if the screen isn't unlocked first. |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 266 | cmd_utils.execute_arbitrary_command([_UNLOCK_SCREEN_SCRIPT], |
| 267 | timeout, |
| 268 | simulate=simulate, |
| 269 | shell=False) |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 270 | |
| 271 | for collector_info, run_combos in grouped_run_combos: |
Yan Wang | 6ddab32 | 2019-07-26 10:53:45 -0700 | [diff] [blame] | 272 | yield from execute_run_using_perfetto_trace(collector_info, |
| 273 | run_combos, |
| 274 | simulate, |
| 275 | inodes_path, |
| 276 | timeout) |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 277 | |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 278 | def gather_results(commands: Iterable[Tuple[DataFrame]], |
| 279 | key_list: List[str], value_list: List[Tuple[str, ...]]): |
| 280 | print_utils.debug_print("gather_results: key_list = ", key_list) |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 281 | stringify_none = lambda s: s is None and "<none>" or s |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 282 | # yield key_list + ["time(ms)"] |
| 283 | for (run_result_list, values) in itertools.zip_longest(commands, value_list): |
| 284 | print_utils.debug_print("run_result_list = ", run_result_list) |
| 285 | print_utils.debug_print("values = ", values) |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 286 | |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 287 | if not run_result_list: |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 288 | continue |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 289 | |
Igor Murashkin | ab37e6e | 2019-05-13 16:31:25 -0700 | [diff] [blame] | 290 | # RunCommandArgs(package='com.whatever', readahead='warm', compiler_filter=None) |
| 291 | # -> {'package':['com.whatever'], 'readahead':['warm'], 'compiler_filter':[None]} |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 292 | values_dict = {} |
| 293 | for k, v in values._asdict().items(): |
| 294 | if not k in key_list: |
| 295 | continue |
| 296 | values_dict[k] = [stringify_none(v)] |
Igor Murashkin | ab37e6e | 2019-05-13 16:31:25 -0700 | [diff] [blame] | 297 | |
| 298 | values_df = DataFrame(values_dict) |
| 299 | # project 'values_df' to be same number of rows as run_result_list. |
| 300 | values_df = values_df.repeat(run_result_list.data_row_len) |
| 301 | |
| 302 | # the results are added as right-hand-side columns onto the existing labels for the table. |
| 303 | values_df.merge_data_columns(run_result_list) |
| 304 | |
| 305 | yield values_df |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 306 | |
| 307 | def eval_and_save_to_csv(output, annotated_result_values): |
Igor Murashkin | ab37e6e | 2019-05-13 16:31:25 -0700 | [diff] [blame] | 308 | printed_header = False |
| 309 | |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 310 | csv_writer = csv.writer(output) |
| 311 | for row in annotated_result_values: |
Igor Murashkin | ab37e6e | 2019-05-13 16:31:25 -0700 | [diff] [blame] | 312 | if not printed_header: |
| 313 | headers = row.headers |
| 314 | csv_writer.writerow(headers) |
| 315 | printed_header = True |
| 316 | # TODO: what about when headers change? |
| 317 | |
| 318 | for data_row in row.data_table: |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 319 | data_row = [d for d in data_row] |
Igor Murashkin | ab37e6e | 2019-05-13 16:31:25 -0700 | [diff] [blame] | 320 | csv_writer.writerow(data_row) |
| 321 | |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 322 | output.flush() # see the output live. |
| 323 | |
| 324 | def coerce_to_list(opts: dict): |
| 325 | """Tranform values of the dictionary to list. |
| 326 | For example: |
| 327 | 1 -> [1], None -> [None], [1,2,3] -> [1,2,3] |
| 328 | [[1],[2]] -> [[1],[2]], {1:1, 2:2} -> [{1:1, 2:2}] |
| 329 | """ |
| 330 | result = {} |
| 331 | for key in opts: |
| 332 | val = opts[key] |
| 333 | result[key] = val if issubclass(type(val), list) else [val] |
| 334 | return result |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 335 | |
| 336 | def main(): |
| 337 | global _debug |
| 338 | |
| 339 | opts = parse_options() |
| 340 | _debug = opts.debug |
| 341 | if _DEBUG_FORCE is not None: |
| 342 | _debug = _DEBUG_FORCE |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 343 | |
| 344 | print_utils.DEBUG = _debug |
| 345 | cmd_utils.SIMULATE = opts.simulate |
| 346 | |
| 347 | print_utils.debug_print("parsed options: ", opts) |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 348 | |
| 349 | output_file = opts.output and open(opts.output, 'w') or sys.stdout |
| 350 | |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 351 | combos = lambda: args_utils.generate_run_combinations( |
Yan Wang | 06f5488 | 2019-07-23 18:09:41 -0700 | [diff] [blame] | 352 | RunCommandArgs, |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 353 | coerce_to_list(vars(opts)), |
| 354 | opts.loop_count) |
| 355 | print_utils.debug_print_gen("run combinations: ", combos()) |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 356 | |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 357 | grouped_combos = lambda: args_utils.generate_group_run_combinations(combos(), |
| 358 | CollectorPackageInfo) |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 359 | |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 360 | print_utils.debug_print_gen("grouped run combinations: ", grouped_combos()) |
| 361 | exec = execute_run_combos(grouped_combos(), |
| 362 | opts.simulate, |
| 363 | opts.inodes, |
| 364 | opts.timeout) |
| 365 | |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 366 | results = gather_results(exec, _COMBINATORIAL_OPTIONS, combos()) |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 367 | |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 368 | eval_and_save_to_csv(output_file, results) |
| 369 | |
Yan Wang | 7af0155 | 2019-07-02 15:46:34 -0700 | [diff] [blame] | 370 | return 1 |
Igor Murashkin | 25f394d | 2018-09-11 16:37:18 -0700 | [diff] [blame] | 371 | |
| 372 | if __name__ == '__main__': |
| 373 | sys.exit(main()) |