• 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.
4from __future__ import annotations
5
6import logging
7from typing import TYPE_CHECKING
8
9from crossbench.probes import metric
10from crossbench.probes.json import JsonResultProbe
11from crossbench.probes.probe import ProbeIncompatibleBrowser
12
13if TYPE_CHECKING:
14  from crossbench.browsers.browser import Browser
15  from crossbench.env import HostEnvironment
16  from crossbench.probes.results import ProbeResult
17  from crossbench.runner.actions import Actions
18  from crossbench.runner.groups.browsers import BrowsersRunGroup
19  from crossbench.runner.groups.stories import StoriesRunGroup
20  from crossbench.types import Json
21
22
23class PerformanceEntriesProbe(JsonResultProbe):
24  """
25  Extract all JavaScript PerformanceEntry [1] from a website.
26  Website owners can define more entries via `performance.mark()`.
27
28  [1] https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry
29  """
30  NAME = "performance.entries"
31
32  def validate_browser(self, env: HostEnvironment, browser: Browser) -> None:
33    super().validate_browser(env, browser)
34    if not hasattr(browser, "js"):
35      raise ProbeIncompatibleBrowser(self, browser,
36                                     "Needs browser with JS-execution support")
37
38  def to_json(self, actions: Actions) -> Json:
39    return actions.js("""
40      let data = { __proto__: null, paint: {}, mark: {} };
41      for (let entryType of Object.keys(data)) {
42        for (let entry of performance.getEntriesByType(entryType)) {
43           const typeData = data[entryType];
44           let values = typeData[entry.name];
45           if (values === undefined) {
46             values = typeData[entry.name] = { startTime: [], duration: [] };
47           }
48           for (let metricName of Object.keys(values)) {
49            values[metricName].push(entry[metricName]);
50          }
51        }
52      }
53      data.navigation = {};
54      const navigationTiming = performance.getEntriesByType("navigation")[0];
55      for (let name in navigationTiming) {
56        const value = navigationTiming[name];
57        if (typeof value !== "number") continue;
58        data.navigation[name] = value;
59      }
60      return data;
61      """)
62
63  def merge_stories(self, group: StoriesRunGroup) -> ProbeResult:
64    stories = list(group.stories)
65    if len(stories) > 1:
66      logging.warning(
67          "%s: Merging performance.entries from %d possibly unrelated pages %s",
68          group.browser.unique_name, len(stories),
69          ", ".join(story.name for story in stories))
70    merged = metric.MetricsMerger.merge_json_list(
71        (story_group.results[self].json
72         for story_group in group.repetitions_groups),
73        merge_duplicate_paths=True)
74    return self.write_group_result(group, merged)
75
76  def merge_browsers(self, group: BrowsersRunGroup) -> ProbeResult:
77    # TODO: recreate the CSV from the merged JSON files since we might not
78    # get the same values in all browsers.
79    # TODO: Add merged browser data with separate stories.
80    return self.merge_browsers_json_list(group).merge(
81        self.merge_browsers_csv_list(group))
82