# 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.browsers import BrowsersRunGroup from crossbench.runner.groups.stories import 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) 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))