| # Copyright 2022 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| from __future__ import annotations |
| |
| import logging |
| from typing import TYPE_CHECKING |
| |
| from crossbench.probes import metric |
| from crossbench.probes.json import JsonResultProbe |
| from crossbench.probes.probe import ProbeIncompatibleBrowser |
| |
| if TYPE_CHECKING: |
| from crossbench.browsers.browser import Browser |
| from crossbench.env import HostEnvironment |
| from crossbench.probes.results import ProbeResult |
| from crossbench.runner.actions import Actions |
| from crossbench.runner.groups import BrowsersRunGroup, StoriesRunGroup |
| from crossbench.types import Json |
| |
| |
| class PerformanceEntriesProbe(JsonResultProbe): |
| """ |
| Extract all JavaScript PerformanceEntry [1] from a website. |
| Website owners can define more entries via `performance.mark()`. |
| |
| [1] https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry |
| """ |
| NAME = "performance.entries" |
| |
| def validate_browser(self, env: HostEnvironment, browser: Browser) -> None: |
| super().validate_browser(env, browser) |
| if not hasattr(browser, "js"): |
| raise ProbeIncompatibleBrowser(self, browser, |
| "Needs browser with JS-execution support") |
| |
| def to_json(self, actions: Actions) -> Json: |
| return actions.js(""" |
| let data = { __proto__: null, paint: {}, mark: {} }; |
| for (let entryType of Object.keys(data)) { |
| for (let entry of performance.getEntriesByType(entryType)) { |
| const typeData = data[entryType]; |
| let values = typeData[entry.name]; |
| if (values === undefined) { |
| values = typeData[entry.name] = { startTime: [], duration: [] }; |
| } |
| for (let metricName of Object.keys(values)) { |
| values[metricName].push(entry[metricName]); |
| } |
| } |
| } |
| data.navigation = {}; |
| const navigationTiming = performance.getEntriesByType("navigation")[0]; |
| for (let name in navigationTiming) { |
| const value = navigationTiming[name]; |
| if (typeof value !== "number") continue; |
| data.navigation[name] = value; |
| } |
| return data; |
| """) |
| |
| def merge_stories(self, group: StoriesRunGroup) -> ProbeResult: |
| stories = list(group.stories) |
| if len(stories) > 1: |
| logging.warning( |
| "%s: Merging performance.entries from %d possibly unrelated pages %s", |
| group.browser.unique_name, len(stories), |
| ", ".join(story.name for story in stories)) |
| merged = metric.MetricsMerger.merge_json_list( |
| (story_group.results[self].json |
| for story_group in group.repetitions_groups), |
| merge_duplicate_paths=True) |
| return self.write_group_result(group, merged, write_csv=True) |
| |
| def merge_browsers(self, group: BrowsersRunGroup) -> ProbeResult: |
| # TODO: recreate the CSV from the merged JSON files since we might not |
| # get the same values in all browsers. |
| # TODO: Add merged browser data with separate stories. |
| return self.merge_browsers_json_list(group).merge( |
| self.merge_browsers_csv_list(group)) |