• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2022 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5from __future__ import annotations
6
7import collections
8import logging
9from typing import TYPE_CHECKING, Optional, Union, cast
10
11from crossbench.browsers.chromium.chromium import Chromium
12from crossbench.probes.chromium_probe import ChromiumProbe
13from crossbench.probes.probe import ProbeContext, ProbeMissingDataError
14from crossbench.probes.results import LocalProbeResult, ProbeResult
15
16if TYPE_CHECKING:
17  from crossbench.browsers.browser import Browser
18  from crossbench.path import LocalPath
19  from crossbench.runner.groups.browsers import BrowsersRunGroup
20  from crossbench.runner.groups.repetitions import (
21      CacheTemperatureRepetitionsRunGroup, RepetitionsRunGroup)
22  from crossbench.runner.groups.stories import StoriesRunGroup
23  from crossbench.runner.run import Run
24
25
26class V8RCSProbe(ChromiumProbe):
27  """
28  Chromium-only Probe to extract runtime-call-stats data that can be used
29  to analyze precise counters and time spent in various VM components in V8:
30  https://v8.dev/tools/head/callstats.html
31  """
32  NAME = "v8.rcs"
33
34  def attach(self, browser: Browser) -> None:
35    assert isinstance(browser, Chromium), "Expected Chromium-based browser."
36    super().attach(browser)
37    chromium = cast(Chromium, browser)
38    chromium.js_flags.update(("--runtime-call-stats", "--allow-natives-syntax"))
39
40  def get_context(self, run: Run) -> V8RCSProbeContext:
41    return V8RCSProbeContext(self, run)
42
43  def concat_group_files(self,
44                         group: Union[RepetitionsRunGroup,
45                                      CacheTemperatureRepetitionsRunGroup],
46                         file_name: str) -> LocalPath:
47    result_dir = group.get_local_probe_result_dir(self)
48    result_files = (run.results[self].file for run in group.runs)
49    result_file = self.host_platform.concat_files(
50        inputs=result_files,
51        output=result_dir / file_name,
52        prefix=f"\n== Page: {group.story.name}\n")
53    return result_file
54
55  def merge_repetitions(self, group: RepetitionsRunGroup) -> ProbeResult:
56    all_file = self.concat_group_files(group, "all.rcs.txt")
57    result_files = [all_file]
58    for temperature_group in group.cache_temperature_repetitions_groups:
59      temperature_file_name = f"{temperature_group.cache_temperature}.rcs.txt"
60      group_file = self.concat_group_files(temperature_group,
61                                           temperature_file_name)
62      result_files.append(group_file)
63    result_dir = group.get_local_probe_result_dir(self)
64    self.host_platform.symlink_or_copy(all_file,
65                                       result_dir.with_suffix(".rcs.txt"))
66    return LocalProbeResult(file=tuple(result_files))
67
68  def merge_stories(self, group: StoriesRunGroup) -> ProbeResult:
69    name_groups = collections.defaultdict(list)
70    for repetition_group in group.repetitions_groups:
71      for result_file in repetition_group.results[self].file_list:
72        name_groups[result_file.name].append(result_file)
73
74    result_dir = group.get_local_probe_result_dir(self)
75    result_files = []
76    for name, files in name_groups.items():
77      result_files.append(
78          self.host_platform.concat_files(
79              inputs=files, output=result_dir / name))
80    src_file = result_dir / "all.rcs.txt"
81    self.host_platform.symlink_or_copy(src_file,
82                                       result_dir.with_suffix(".rcs.txt"))
83    return LocalProbeResult(file=(src_file,))
84
85  def merge_browsers(self, group: BrowsersRunGroup) -> ProbeResult:
86    # We put all the fils by in a toplevel v8.rcs folder
87    result_dir = group.get_local_probe_result_dir(self)
88    files = []
89    for story_group in group.story_groups:
90      story_group_file = story_group.results[self].file
91      # Be permissive and skip failed probes
92      if not story_group_file.exists():
93        logging.info("Probe %s: skipping non-existing results file: %s",
94                     self.NAME, story_group_file)
95        continue
96      dest_file = result_dir / f"{story_group.browser.unique_name}.rcs.txt"
97      self.host_platform.symlink_or_copy(story_group_file, dest_file)
98      files.append(dest_file)
99    return LocalProbeResult(file=files)
100
101  def log_browsers_result(self, group: BrowsersRunGroup) -> None:
102    if self not in group.results:
103      return
104    logging.info("-" * 80)
105    logging.critical(
106        "V8 RCS results: open on  http://v8.dev/tools/head/callstats.html")
107    for file in group.results[self].get_all("txt"):
108      logging.critical("    %s", file)
109    logging.info("- " * 40)
110
111
112class V8RCSProbeContext(ProbeContext[V8RCSProbe]):
113  _rcs_table: Optional[str] = None
114
115  def setup(self) -> None:
116    pass
117
118  def start(self) -> None:
119    pass
120
121  def stop(self) -> None:
122    with self.run.actions("Extract RCS") as actions:
123      self._rcs_table = actions.js("return %GetAndResetRuntimeCallStats();")
124
125  def teardown(self) -> ProbeResult:
126    if not self._rcs_table:
127      raise ProbeMissingDataError(
128          "Chrome didn't produce any RCS data. "
129          "Use Chrome Canary or make sure to enable the "
130          "v8_enable_runtime_call_stats compile-time flag.")
131    rcs_file = self.local_result_path.with_suffix(".rcs.txt")
132    with rcs_file.open("a") as f:
133      f.write(self._rcs_table)
134    return LocalProbeResult(file=(rcs_file,))
135