11cb0ef41Sopenharmony_ci#!/usr/bin/python3
21cb0ef41Sopenharmony_ci# Copyright 2019 the V8 project authors. All rights reserved.
31cb0ef41Sopenharmony_ci# Use of this source code is governed by a BSD-style license that can be
41cb0ef41Sopenharmony_ci# found in the LICENSE file.
51cb0ef41Sopenharmony_ci
61cb0ef41Sopenharmony_ci# Runs chromium/src/run_benchmark for a given story and extracts the generated
71cb0ef41Sopenharmony_ci# runtime call stats.
81cb0ef41Sopenharmony_ci
91cb0ef41Sopenharmony_ciimport argparse
101cb0ef41Sopenharmony_ciimport csv
111cb0ef41Sopenharmony_ciimport json
121cb0ef41Sopenharmony_ciimport glob
131cb0ef41Sopenharmony_ciimport os
141cb0ef41Sopenharmony_ciimport pathlib
151cb0ef41Sopenharmony_ciimport re
161cb0ef41Sopenharmony_ciimport tabulate
171cb0ef41Sopenharmony_ciimport shutil
181cb0ef41Sopenharmony_ciimport statistics
191cb0ef41Sopenharmony_ciimport subprocess
201cb0ef41Sopenharmony_ciimport sys
211cb0ef41Sopenharmony_ciimport tempfile
221cb0ef41Sopenharmony_ci
231cb0ef41Sopenharmony_cifrom callstats_groups import RUNTIME_CALL_STATS_GROUPS
241cb0ef41Sopenharmony_ci
251cb0ef41Sopenharmony_ci
261cb0ef41Sopenharmony_ciJSON_FILE_EXTENSION=".pb_converted.json"
271cb0ef41Sopenharmony_ci
281cb0ef41Sopenharmony_cidef parse_args():
291cb0ef41Sopenharmony_ci  parser = argparse.ArgumentParser(
301cb0ef41Sopenharmony_ci      description="Run story and collect runtime call stats.")
311cb0ef41Sopenharmony_ci  parser.add_argument("story", metavar="story", nargs=1, help="story to run")
321cb0ef41Sopenharmony_ci  parser.add_argument(
331cb0ef41Sopenharmony_ci      "--group",
341cb0ef41Sopenharmony_ci      dest="group",
351cb0ef41Sopenharmony_ci      action="store_true",
361cb0ef41Sopenharmony_ci      help="group common stats together into buckets")
371cb0ef41Sopenharmony_ci  parser.add_argument(
381cb0ef41Sopenharmony_ci      "-r",
391cb0ef41Sopenharmony_ci      "--repeats",
401cb0ef41Sopenharmony_ci      dest="repeats",
411cb0ef41Sopenharmony_ci      metavar="N",
421cb0ef41Sopenharmony_ci      action="store",
431cb0ef41Sopenharmony_ci      type=int,
441cb0ef41Sopenharmony_ci      default=1,
451cb0ef41Sopenharmony_ci      help="number of times to run the story")
461cb0ef41Sopenharmony_ci  parser.add_argument(
471cb0ef41Sopenharmony_ci      "-v",
481cb0ef41Sopenharmony_ci      "--verbose",
491cb0ef41Sopenharmony_ci      dest="verbose",
501cb0ef41Sopenharmony_ci      action="store_true",
511cb0ef41Sopenharmony_ci      help="output benchmark runs to stdout")
521cb0ef41Sopenharmony_ci  parser.add_argument(
531cb0ef41Sopenharmony_ci      "--device",
541cb0ef41Sopenharmony_ci      dest="device",
551cb0ef41Sopenharmony_ci      action="store",
561cb0ef41Sopenharmony_ci      help="device to run the test on. Passed directly to run_benchmark")
571cb0ef41Sopenharmony_ci  parser.add_argument(
581cb0ef41Sopenharmony_ci      "-d",
591cb0ef41Sopenharmony_ci      "--dir",
601cb0ef41Sopenharmony_ci      dest="dir",
611cb0ef41Sopenharmony_ci      action="store",
621cb0ef41Sopenharmony_ci      help=("directory to look for already generated output in. This must "
631cb0ef41Sopenharmony_ci            "already exists and it won't re-run the benchmark"))
641cb0ef41Sopenharmony_ci  parser.add_argument(
651cb0ef41Sopenharmony_ci      "-f",
661cb0ef41Sopenharmony_ci      "--format",
671cb0ef41Sopenharmony_ci      dest="format",
681cb0ef41Sopenharmony_ci      action="store",
691cb0ef41Sopenharmony_ci      choices=["csv", "table"],
701cb0ef41Sopenharmony_ci      help="output as CSV")
711cb0ef41Sopenharmony_ci  parser.add_argument(
721cb0ef41Sopenharmony_ci      "-o",
731cb0ef41Sopenharmony_ci      "--output",
741cb0ef41Sopenharmony_ci      metavar="FILE",
751cb0ef41Sopenharmony_ci      dest="out_file",
761cb0ef41Sopenharmony_ci      action="store",
771cb0ef41Sopenharmony_ci      help="write table to FILE rather stdout")
781cb0ef41Sopenharmony_ci  parser.add_argument(
791cb0ef41Sopenharmony_ci      "--browser",
801cb0ef41Sopenharmony_ci      dest="browser",
811cb0ef41Sopenharmony_ci      metavar="BROWSER_TYPE",
821cb0ef41Sopenharmony_ci      action="store",
831cb0ef41Sopenharmony_ci      default="release",
841cb0ef41Sopenharmony_ci      help=("Passed directly to --browser option of run_benchmark. Ignored if "
851cb0ef41Sopenharmony_ci            "-executable is used"))
861cb0ef41Sopenharmony_ci  parser.add_argument(
871cb0ef41Sopenharmony_ci      "-e",
881cb0ef41Sopenharmony_ci      "--executable",
891cb0ef41Sopenharmony_ci      dest="executable",
901cb0ef41Sopenharmony_ci      metavar="EXECUTABLE",
911cb0ef41Sopenharmony_ci      action="store",
921cb0ef41Sopenharmony_ci      help=("path to executable to run. If not given it will pass '--browser "
931cb0ef41Sopenharmony_ci            "release' to run_benchmark"))
941cb0ef41Sopenharmony_ci  parser.add_argument(
951cb0ef41Sopenharmony_ci      "--chromium-dir",
961cb0ef41Sopenharmony_ci      dest="chromium_dir",
971cb0ef41Sopenharmony_ci      metavar="DIR",
981cb0ef41Sopenharmony_ci      action="store",
991cb0ef41Sopenharmony_ci      default=".",
1001cb0ef41Sopenharmony_ci      help=("path to chromium directory. If not given, the script must be run "
1011cb0ef41Sopenharmony_ci            "inside the chromium/src directory"))
1021cb0ef41Sopenharmony_ci  parser.add_argument(
1031cb0ef41Sopenharmony_ci      "--js-flags", dest="js_flags", action="store", help="flags to pass to v8")
1041cb0ef41Sopenharmony_ci  parser.add_argument(
1051cb0ef41Sopenharmony_ci      "--extra-browser-args",
1061cb0ef41Sopenharmony_ci      dest="browser_args",
1071cb0ef41Sopenharmony_ci      action="store",
1081cb0ef41Sopenharmony_ci      help="flags to pass to chrome")
1091cb0ef41Sopenharmony_ci  parser.add_argument(
1101cb0ef41Sopenharmony_ci      "--benchmark",
1111cb0ef41Sopenharmony_ci      dest="benchmark",
1121cb0ef41Sopenharmony_ci      action="store",
1131cb0ef41Sopenharmony_ci      default="v8.browsing_desktop",
1141cb0ef41Sopenharmony_ci      help="benchmark to run")
1151cb0ef41Sopenharmony_ci  parser.add_argument(
1161cb0ef41Sopenharmony_ci      "--stdev",
1171cb0ef41Sopenharmony_ci      dest="stdev",
1181cb0ef41Sopenharmony_ci      action="store_true",
1191cb0ef41Sopenharmony_ci      help="adds columns for the standard deviation")
1201cb0ef41Sopenharmony_ci  parser.add_argument(
1211cb0ef41Sopenharmony_ci      "--filter",
1221cb0ef41Sopenharmony_ci      dest="filter",
1231cb0ef41Sopenharmony_ci      action="append",
1241cb0ef41Sopenharmony_ci      help="useable with --group to only show buckets specified by filter")
1251cb0ef41Sopenharmony_ci  parser.add_argument(
1261cb0ef41Sopenharmony_ci      "--retain",
1271cb0ef41Sopenharmony_ci      dest="retain",
1281cb0ef41Sopenharmony_ci      action="store",
1291cb0ef41Sopenharmony_ci      default="json",
1301cb0ef41Sopenharmony_ci      choices=["none", "json", "all"],
1311cb0ef41Sopenharmony_ci      help=("controls artifacts to be retained after run. With none, all files "
1321cb0ef41Sopenharmony_ci            "are deleted; only the json.gz file is retained for each run; and "
1331cb0ef41Sopenharmony_ci            "all keep all files"))
1341cb0ef41Sopenharmony_ci
1351cb0ef41Sopenharmony_ci  return parser.parse_args()
1361cb0ef41Sopenharmony_ci
1371cb0ef41Sopenharmony_ci
1381cb0ef41Sopenharmony_cidef process_trace(trace_file):
1391cb0ef41Sopenharmony_ci  text_string = pathlib.Path(trace_file).read_text()
1401cb0ef41Sopenharmony_ci  result = json.loads(text_string)
1411cb0ef41Sopenharmony_ci
1421cb0ef41Sopenharmony_ci  output = {}
1431cb0ef41Sopenharmony_ci  result = result["traceEvents"]
1441cb0ef41Sopenharmony_ci  for o in result:
1451cb0ef41Sopenharmony_ci    o = o["args"]
1461cb0ef41Sopenharmony_ci    if "runtime-call-stats" in o:
1471cb0ef41Sopenharmony_ci      r = o["runtime-call-stats"]
1481cb0ef41Sopenharmony_ci      for name in r:
1491cb0ef41Sopenharmony_ci        count = r[name][0]
1501cb0ef41Sopenharmony_ci        duration = r[name][1]
1511cb0ef41Sopenharmony_ci        if name in output:
1521cb0ef41Sopenharmony_ci          output[name]["count"] += count
1531cb0ef41Sopenharmony_ci          output[name]["duration"] += duration
1541cb0ef41Sopenharmony_ci        else:
1551cb0ef41Sopenharmony_ci          output[name] = {"count": count, "duration": duration}
1561cb0ef41Sopenharmony_ci
1571cb0ef41Sopenharmony_ci  return output
1581cb0ef41Sopenharmony_ci
1591cb0ef41Sopenharmony_ci
1601cb0ef41Sopenharmony_cidef run_benchmark(story,
1611cb0ef41Sopenharmony_ci                  repeats=1,
1621cb0ef41Sopenharmony_ci                  output_dir=".",
1631cb0ef41Sopenharmony_ci                  verbose=False,
1641cb0ef41Sopenharmony_ci                  js_flags=None,
1651cb0ef41Sopenharmony_ci                  browser_args=None,
1661cb0ef41Sopenharmony_ci                  chromium_dir=".",
1671cb0ef41Sopenharmony_ci                  executable=None,
1681cb0ef41Sopenharmony_ci                  benchmark="v8.browsing_desktop",
1691cb0ef41Sopenharmony_ci                  device=None,
1701cb0ef41Sopenharmony_ci                  browser="release"):
1711cb0ef41Sopenharmony_ci
1721cb0ef41Sopenharmony_ci  orig_chromium_dir = chromium_dir
1731cb0ef41Sopenharmony_ci  xvfb = os.path.join(chromium_dir, "testing", "xvfb.py")
1741cb0ef41Sopenharmony_ci  if not os.path.isfile(xvfb):
1751cb0ef41Sopenharmony_ci    chromium_dir = os.path(chromium_dir, "src")
1761cb0ef41Sopenharmony_ci    xvfb = os.path.join(chromium_dir, "testing", "xvfb.py")
1771cb0ef41Sopenharmony_ci    if not os.path.isfile(xvfb):
1781cb0ef41Sopenharmony_ci      print(("chromium_dir does not point to a valid chromium checkout: " +
1791cb0ef41Sopenharmony_ci             orig_chromium_dir))
1801cb0ef41Sopenharmony_ci      sys.exit(1)
1811cb0ef41Sopenharmony_ci
1821cb0ef41Sopenharmony_ci  command = [
1831cb0ef41Sopenharmony_ci      xvfb,
1841cb0ef41Sopenharmony_ci      os.path.join(chromium_dir, "tools", "perf", "run_benchmark"),
1851cb0ef41Sopenharmony_ci      "run",
1861cb0ef41Sopenharmony_ci      "--story",
1871cb0ef41Sopenharmony_ci      story,
1881cb0ef41Sopenharmony_ci      "--pageset-repeat",
1891cb0ef41Sopenharmony_ci      str(repeats),
1901cb0ef41Sopenharmony_ci      "--output-dir",
1911cb0ef41Sopenharmony_ci      output_dir,
1921cb0ef41Sopenharmony_ci      "--intermediate-dir",
1931cb0ef41Sopenharmony_ci      os.path.join(output_dir, "artifacts"),
1941cb0ef41Sopenharmony_ci      benchmark,
1951cb0ef41Sopenharmony_ci  ]
1961cb0ef41Sopenharmony_ci
1971cb0ef41Sopenharmony_ci  if executable:
1981cb0ef41Sopenharmony_ci    command += ["--browser-executable", executable]
1991cb0ef41Sopenharmony_ci  else:
2001cb0ef41Sopenharmony_ci    command += ["--browser", browser]
2011cb0ef41Sopenharmony_ci
2021cb0ef41Sopenharmony_ci  if device:
2031cb0ef41Sopenharmony_ci    command += ["--device", device]
2041cb0ef41Sopenharmony_ci  if browser_args:
2051cb0ef41Sopenharmony_ci    command += ["--extra-browser-args", browser_args]
2061cb0ef41Sopenharmony_ci  if js_flags:
2071cb0ef41Sopenharmony_ci    command += ["--js-flags", js_flags]
2081cb0ef41Sopenharmony_ci
2091cb0ef41Sopenharmony_ci  if not benchmark.startswith("v8."):
2101cb0ef41Sopenharmony_ci    # Most benchmarks by default don't collect runtime call stats so enable them
2111cb0ef41Sopenharmony_ci    # manually.
2121cb0ef41Sopenharmony_ci    categories = [
2131cb0ef41Sopenharmony_ci        "v8",
2141cb0ef41Sopenharmony_ci        "disabled-by-default-v8.runtime_stats",
2151cb0ef41Sopenharmony_ci    ]
2161cb0ef41Sopenharmony_ci
2171cb0ef41Sopenharmony_ci    command += ["--extra-chrome-categories", ",".join(categories)]
2181cb0ef41Sopenharmony_ci
2191cb0ef41Sopenharmony_ci  print("Output directory: %s" % output_dir)
2201cb0ef41Sopenharmony_ci  stdout = ""
2211cb0ef41Sopenharmony_ci  print(f"Running: {' '.join(command)}\n")
2221cb0ef41Sopenharmony_ci  proc = subprocess.Popen(
2231cb0ef41Sopenharmony_ci      command,
2241cb0ef41Sopenharmony_ci      stdout=subprocess.PIPE,
2251cb0ef41Sopenharmony_ci      stderr=subprocess.PIPE,
2261cb0ef41Sopenharmony_ci      universal_newlines=True)
2271cb0ef41Sopenharmony_ci  proc.stderr.close()
2281cb0ef41Sopenharmony_ci  status_matcher = re.compile(r"\[ +(\w+) +\]")
2291cb0ef41Sopenharmony_ci  for line in iter(proc.stdout.readline, ""):
2301cb0ef41Sopenharmony_ci    stdout += line
2311cb0ef41Sopenharmony_ci    match = status_matcher.match(line)
2321cb0ef41Sopenharmony_ci    if verbose or match:
2331cb0ef41Sopenharmony_ci      print(line, end="")
2341cb0ef41Sopenharmony_ci
2351cb0ef41Sopenharmony_ci  proc.stdout.close()
2361cb0ef41Sopenharmony_ci
2371cb0ef41Sopenharmony_ci  if proc.wait() != 0:
2381cb0ef41Sopenharmony_ci    print("\nrun_benchmark failed:")
2391cb0ef41Sopenharmony_ci    # If verbose then everything has already been printed.
2401cb0ef41Sopenharmony_ci    if not verbose:
2411cb0ef41Sopenharmony_ci      print(stdout)
2421cb0ef41Sopenharmony_ci    sys.exit(1)
2431cb0ef41Sopenharmony_ci
2441cb0ef41Sopenharmony_ci  print("\nrun_benchmark completed")
2451cb0ef41Sopenharmony_ci
2461cb0ef41Sopenharmony_ci
2471cb0ef41Sopenharmony_cidef write_output(f, table, headers, run_count, format="table"):
2481cb0ef41Sopenharmony_ci  if format == "csv":
2491cb0ef41Sopenharmony_ci    # strip new lines from CSV output
2501cb0ef41Sopenharmony_ci    headers = [h.replace("\n", " ") for h in headers]
2511cb0ef41Sopenharmony_ci    writer = csv.writer(f)
2521cb0ef41Sopenharmony_ci    writer.writerow(headers)
2531cb0ef41Sopenharmony_ci    writer.writerows(table)
2541cb0ef41Sopenharmony_ci  else:
2551cb0ef41Sopenharmony_ci    # First column is name, and then they alternate between counts and durations
2561cb0ef41Sopenharmony_ci    summary_count = len(headers) - 2 * run_count - 1
2571cb0ef41Sopenharmony_ci    floatfmt = ("",) + (".0f", ".2f") * run_count + (".2f",) * summary_count
2581cb0ef41Sopenharmony_ci    f.write(tabulate.tabulate(table, headers=headers, floatfmt=floatfmt))
2591cb0ef41Sopenharmony_ci    f.write("\n")
2601cb0ef41Sopenharmony_ci
2611cb0ef41Sopenharmony_ci
2621cb0ef41Sopenharmony_ciclass Row:
2631cb0ef41Sopenharmony_ci
2641cb0ef41Sopenharmony_ci  def __init__(self, name, run_count):
2651cb0ef41Sopenharmony_ci    self.name = name
2661cb0ef41Sopenharmony_ci    self.durations = [0] * run_count
2671cb0ef41Sopenharmony_ci    self.counts = [0] * run_count
2681cb0ef41Sopenharmony_ci    self.mean_duration = None
2691cb0ef41Sopenharmony_ci    self.mean_count = None
2701cb0ef41Sopenharmony_ci    self.stdev_duration = None
2711cb0ef41Sopenharmony_ci    self.stdev_count = None
2721cb0ef41Sopenharmony_ci
2731cb0ef41Sopenharmony_ci  def __repr__(self):
2741cb0ef41Sopenharmony_ci    data_str = ", ".join(
2751cb0ef41Sopenharmony_ci        str((c, d)) for (c, d) in zip(self.counts, self.durations))
2761cb0ef41Sopenharmony_ci    return (f"{self.name}: {data_str}, mean_count: {self.mean_count}, " +
2771cb0ef41Sopenharmony_ci            f"mean_duration: {self.mean_duration}")
2781cb0ef41Sopenharmony_ci
2791cb0ef41Sopenharmony_ci  def add_data(self, counts, durations):
2801cb0ef41Sopenharmony_ci    self.counts = counts
2811cb0ef41Sopenharmony_ci    self.durations = durations
2821cb0ef41Sopenharmony_ci
2831cb0ef41Sopenharmony_ci  def add_data_point(self, run, count, duration):
2841cb0ef41Sopenharmony_ci    self.counts[run] = count
2851cb0ef41Sopenharmony_ci    self.durations[run] = duration
2861cb0ef41Sopenharmony_ci
2871cb0ef41Sopenharmony_ci  def prepare(self, stdev=False):
2881cb0ef41Sopenharmony_ci    if len(self.durations) > 1:
2891cb0ef41Sopenharmony_ci      self.mean_duration = statistics.mean(self.durations)
2901cb0ef41Sopenharmony_ci      self.mean_count = statistics.mean(self.counts)
2911cb0ef41Sopenharmony_ci      if stdev:
2921cb0ef41Sopenharmony_ci        self.stdev_duration = statistics.stdev(self.durations)
2931cb0ef41Sopenharmony_ci        self.stdev_count = statistics.stdev(self.counts)
2941cb0ef41Sopenharmony_ci
2951cb0ef41Sopenharmony_ci  def as_list(self):
2961cb0ef41Sopenharmony_ci    l = [self.name]
2971cb0ef41Sopenharmony_ci    for (c, d) in zip(self.counts, self.durations):
2981cb0ef41Sopenharmony_ci      l += [c, d]
2991cb0ef41Sopenharmony_ci    if self.mean_duration is not None:
3001cb0ef41Sopenharmony_ci      l += [self.mean_count]
3011cb0ef41Sopenharmony_ci      if self.stdev_count is not None:
3021cb0ef41Sopenharmony_ci        l += [self.stdev_count]
3031cb0ef41Sopenharmony_ci      l += [self.mean_duration]
3041cb0ef41Sopenharmony_ci      if self.stdev_duration is not None:
3051cb0ef41Sopenharmony_ci        l += [self.stdev_duration]
3061cb0ef41Sopenharmony_ci    return l
3071cb0ef41Sopenharmony_ci
3081cb0ef41Sopenharmony_ci  def key(self):
3091cb0ef41Sopenharmony_ci    if self.mean_duration is not None:
3101cb0ef41Sopenharmony_ci      return self.mean_duration
3111cb0ef41Sopenharmony_ci    else:
3121cb0ef41Sopenharmony_ci      return self.durations[0]
3131cb0ef41Sopenharmony_ci
3141cb0ef41Sopenharmony_ci
3151cb0ef41Sopenharmony_ciclass Bucket:
3161cb0ef41Sopenharmony_ci
3171cb0ef41Sopenharmony_ci  def __init__(self, name, run_count):
3181cb0ef41Sopenharmony_ci    self.name = name
3191cb0ef41Sopenharmony_ci    self.run_count = run_count
3201cb0ef41Sopenharmony_ci    self.data = {}
3211cb0ef41Sopenharmony_ci    self.table = None
3221cb0ef41Sopenharmony_ci    self.total_row = None
3231cb0ef41Sopenharmony_ci
3241cb0ef41Sopenharmony_ci  def __repr__(self):
3251cb0ef41Sopenharmony_ci    s = "Bucket: " + self.name + " {\n"
3261cb0ef41Sopenharmony_ci    if self.table:
3271cb0ef41Sopenharmony_ci      s += "\n  ".join(str(row) for row in self.table) + "\n"
3281cb0ef41Sopenharmony_ci    elif self.data:
3291cb0ef41Sopenharmony_ci      s += "\n  ".join(str(row) for row in self.data.values()) + "\n"
3301cb0ef41Sopenharmony_ci    if self.total_row:
3311cb0ef41Sopenharmony_ci      s += "  " + str(self.total_row) + "\n"
3321cb0ef41Sopenharmony_ci    return s + "}"
3331cb0ef41Sopenharmony_ci
3341cb0ef41Sopenharmony_ci  def add_data_point(self, name, run, count, duration):
3351cb0ef41Sopenharmony_ci    if name not in self.data:
3361cb0ef41Sopenharmony_ci      self.data[name] = Row(name, self.run_count)
3371cb0ef41Sopenharmony_ci
3381cb0ef41Sopenharmony_ci    self.data[name].add_data_point(run, count, duration)
3391cb0ef41Sopenharmony_ci
3401cb0ef41Sopenharmony_ci  def prepare(self, stdev=False):
3411cb0ef41Sopenharmony_ci    if self.data:
3421cb0ef41Sopenharmony_ci      for row in self.data.values():
3431cb0ef41Sopenharmony_ci        row.prepare(stdev)
3441cb0ef41Sopenharmony_ci
3451cb0ef41Sopenharmony_ci      self.table = sorted(self.data.values(), key=Row.key)
3461cb0ef41Sopenharmony_ci      self.total_row = Row("Total", self.run_count)
3471cb0ef41Sopenharmony_ci      self.total_row.add_data([
3481cb0ef41Sopenharmony_ci          sum(r.counts[i]
3491cb0ef41Sopenharmony_ci              for r in self.data.values())
3501cb0ef41Sopenharmony_ci          for i in range(0, self.run_count)
3511cb0ef41Sopenharmony_ci      ], [
3521cb0ef41Sopenharmony_ci          sum(r.durations[i]
3531cb0ef41Sopenharmony_ci              for r in self.data.values())
3541cb0ef41Sopenharmony_ci          for i in range(0, self.run_count)
3551cb0ef41Sopenharmony_ci      ])
3561cb0ef41Sopenharmony_ci      self.total_row.prepare(stdev)
3571cb0ef41Sopenharmony_ci
3581cb0ef41Sopenharmony_ci  def as_list(self, add_bucket_titles=True, filter=None):
3591cb0ef41Sopenharmony_ci    t = []
3601cb0ef41Sopenharmony_ci    if filter is None or self.name in filter:
3611cb0ef41Sopenharmony_ci      if add_bucket_titles:
3621cb0ef41Sopenharmony_ci        t += [["\n"], [self.name]]
3631cb0ef41Sopenharmony_ci      t += [r.as_list() for r in self.table]
3641cb0ef41Sopenharmony_ci      t += [self.total_row.as_list()]
3651cb0ef41Sopenharmony_ci    return t
3661cb0ef41Sopenharmony_ci
3671cb0ef41Sopenharmony_ci
3681cb0ef41Sopenharmony_cidef collect_buckets(story, group=True, repeats=1, output_dir="."):
3691cb0ef41Sopenharmony_ci  if group:
3701cb0ef41Sopenharmony_ci    groups = RUNTIME_CALL_STATS_GROUPS
3711cb0ef41Sopenharmony_ci  else:
3721cb0ef41Sopenharmony_ci    groups = []
3731cb0ef41Sopenharmony_ci
3741cb0ef41Sopenharmony_ci  buckets = {}
3751cb0ef41Sopenharmony_ci
3761cb0ef41Sopenharmony_ci  for i in range(0, repeats):
3771cb0ef41Sopenharmony_ci    story_dir = f"{story.replace(':', '_')}_{i + 1}"
3781cb0ef41Sopenharmony_ci    trace_dir = os.path.join(output_dir, "artifacts", story_dir, "trace",
3791cb0ef41Sopenharmony_ci                             "traceEvents")
3801cb0ef41Sopenharmony_ci
3811cb0ef41Sopenharmony_ci    # run_benchmark now dumps two files: a .pb.gz file and a .pb_converted.json
3821cb0ef41Sopenharmony_ci    # file. We only need the latter.
3831cb0ef41Sopenharmony_ci    trace_file_glob = os.path.join(trace_dir, "*" + JSON_FILE_EXTENSION)
3841cb0ef41Sopenharmony_ci    trace_files = glob.glob(trace_file_glob)
3851cb0ef41Sopenharmony_ci    if not trace_files:
3861cb0ef41Sopenharmony_ci      print("Could not find *%s file in %s" % (JSON_FILE_EXTENSION, trace_dir))
3871cb0ef41Sopenharmony_ci      sys.exit(1)
3881cb0ef41Sopenharmony_ci    if len(trace_files) > 1:
3891cb0ef41Sopenharmony_ci      print("Expecting one file but got: %s" % trace_files)
3901cb0ef41Sopenharmony_ci      sys.exit(1)
3911cb0ef41Sopenharmony_ci
3921cb0ef41Sopenharmony_ci    trace_file = trace_files[0]
3931cb0ef41Sopenharmony_ci
3941cb0ef41Sopenharmony_ci    output = process_trace(trace_file)
3951cb0ef41Sopenharmony_ci    for name in output:
3961cb0ef41Sopenharmony_ci      bucket_name = "Other"
3971cb0ef41Sopenharmony_ci      for group in groups:
3981cb0ef41Sopenharmony_ci        if group[1].match(name):
3991cb0ef41Sopenharmony_ci          bucket_name = group[0]
4001cb0ef41Sopenharmony_ci          break
4011cb0ef41Sopenharmony_ci
4021cb0ef41Sopenharmony_ci      value = output[name]
4031cb0ef41Sopenharmony_ci      if bucket_name not in buckets:
4041cb0ef41Sopenharmony_ci        bucket = Bucket(bucket_name, repeats)
4051cb0ef41Sopenharmony_ci        buckets[bucket_name] = bucket
4061cb0ef41Sopenharmony_ci      else:
4071cb0ef41Sopenharmony_ci        bucket = buckets[bucket_name]
4081cb0ef41Sopenharmony_ci
4091cb0ef41Sopenharmony_ci      bucket.add_data_point(name, i, value["count"], value["duration"] / 1000.0)
4101cb0ef41Sopenharmony_ci  return buckets
4111cb0ef41Sopenharmony_ci
4121cb0ef41Sopenharmony_ci
4131cb0ef41Sopenharmony_cidef create_table(buckets, record_bucket_names=True, filter=None):
4141cb0ef41Sopenharmony_ci  table = []
4151cb0ef41Sopenharmony_ci  for bucket in buckets.values():
4161cb0ef41Sopenharmony_ci    table += bucket.as_list(
4171cb0ef41Sopenharmony_ci        add_bucket_titles=record_bucket_names, filter=filter)
4181cb0ef41Sopenharmony_ci  return table
4191cb0ef41Sopenharmony_ci
4201cb0ef41Sopenharmony_ci
4211cb0ef41Sopenharmony_cidef main():
4221cb0ef41Sopenharmony_ci  args = parse_args()
4231cb0ef41Sopenharmony_ci  story = args.story[0]
4241cb0ef41Sopenharmony_ci
4251cb0ef41Sopenharmony_ci  retain = args.retain
4261cb0ef41Sopenharmony_ci  if args.dir is not None:
4271cb0ef41Sopenharmony_ci    output_dir = args.dir
4281cb0ef41Sopenharmony_ci    if not os.path.isdir(output_dir):
4291cb0ef41Sopenharmony_ci      print("Specified output directory does not exist: " % output_dir)
4301cb0ef41Sopenharmony_ci      sys.exit(1)
4311cb0ef41Sopenharmony_ci  else:
4321cb0ef41Sopenharmony_ci    output_dir = tempfile.mkdtemp(prefix="runtime_call_stats_")
4331cb0ef41Sopenharmony_ci    run_benchmark(
4341cb0ef41Sopenharmony_ci        story,
4351cb0ef41Sopenharmony_ci        repeats=args.repeats,
4361cb0ef41Sopenharmony_ci        output_dir=output_dir,
4371cb0ef41Sopenharmony_ci        verbose=args.verbose,
4381cb0ef41Sopenharmony_ci        js_flags=args.js_flags,
4391cb0ef41Sopenharmony_ci        browser_args=args.browser_args,
4401cb0ef41Sopenharmony_ci        chromium_dir=args.chromium_dir,
4411cb0ef41Sopenharmony_ci        benchmark=args.benchmark,
4421cb0ef41Sopenharmony_ci        executable=args.executable,
4431cb0ef41Sopenharmony_ci        browser=args.browser,
4441cb0ef41Sopenharmony_ci        device=args.device)
4451cb0ef41Sopenharmony_ci
4461cb0ef41Sopenharmony_ci  try:
4471cb0ef41Sopenharmony_ci    buckets = collect_buckets(
4481cb0ef41Sopenharmony_ci        story, group=args.group, repeats=args.repeats, output_dir=output_dir)
4491cb0ef41Sopenharmony_ci
4501cb0ef41Sopenharmony_ci    for b in buckets.values():
4511cb0ef41Sopenharmony_ci      b.prepare(args.stdev)
4521cb0ef41Sopenharmony_ci
4531cb0ef41Sopenharmony_ci    table = create_table(
4541cb0ef41Sopenharmony_ci        buckets, record_bucket_names=args.group, filter=args.filter)
4551cb0ef41Sopenharmony_ci
4561cb0ef41Sopenharmony_ci    headers = [""] + ["Count", "Duration\n(ms)"] * args.repeats
4571cb0ef41Sopenharmony_ci    if args.repeats > 1:
4581cb0ef41Sopenharmony_ci      if args.stdev:
4591cb0ef41Sopenharmony_ci        headers += [
4601cb0ef41Sopenharmony_ci            "Count\nMean", "Count\nStdev", "Duration\nMean (ms)",
4611cb0ef41Sopenharmony_ci            "Duration\nStdev (ms)"
4621cb0ef41Sopenharmony_ci        ]
4631cb0ef41Sopenharmony_ci      else:
4641cb0ef41Sopenharmony_ci        headers += ["Count\nMean", "Duration\nMean (ms)"]
4651cb0ef41Sopenharmony_ci
4661cb0ef41Sopenharmony_ci    if args.out_file:
4671cb0ef41Sopenharmony_ci      with open(args.out_file, "w", newline="") as f:
4681cb0ef41Sopenharmony_ci        write_output(f, table, headers, args.repeats, args.format)
4691cb0ef41Sopenharmony_ci    else:
4701cb0ef41Sopenharmony_ci      write_output(sys.stdout, table, headers, args.repeats, args.format)
4711cb0ef41Sopenharmony_ci  finally:
4721cb0ef41Sopenharmony_ci    if retain == "none":
4731cb0ef41Sopenharmony_ci      shutil.rmtree(output_dir)
4741cb0ef41Sopenharmony_ci    elif retain == "json":
4751cb0ef41Sopenharmony_ci      # Delete all files bottom up except ones ending in JSON_FILE_EXTENSION and
4761cb0ef41Sopenharmony_ci      # attempt to delete subdirectories (ignoring errors).
4771cb0ef41Sopenharmony_ci      for dir_name, subdir_list, file_list in os.walk(
4781cb0ef41Sopenharmony_ci          output_dir, topdown=False):
4791cb0ef41Sopenharmony_ci        for file_name in file_list:
4801cb0ef41Sopenharmony_ci          if not file_name.endswith(JSON_FILE_EXTENSION):
4811cb0ef41Sopenharmony_ci            os.remove(os.path.join(dir_name, file_name))
4821cb0ef41Sopenharmony_ci        for subdir in subdir_list:
4831cb0ef41Sopenharmony_ci          try:
4841cb0ef41Sopenharmony_ci            os.rmdir(os.path.join(dir_name, subdir))
4851cb0ef41Sopenharmony_ci          except OSError:
4861cb0ef41Sopenharmony_ci            pass
4871cb0ef41Sopenharmony_ci
4881cb0ef41Sopenharmony_ci
4891cb0ef41Sopenharmony_ciif __name__ == "__main__":
4901cb0ef41Sopenharmony_ci  sys.exit(main())
491