Skip to content

Commit f7c3a70

Browse files
Remote Runner / PostMessge api (#456)
1 parent 0dc0b1c commit f7c3a70

23 files changed

+408
-35
lines changed

resources/benchmark-runner.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Metric } from "./metric.mjs";
2-
import { params } from "./params.mjs";
2+
import { params } from "./shared/params.mjs";
33
import { SUITE_RUNNER_LOOKUP } from "./suite-runner.mjs";
44

55
const performance = globalThis.performance;

resources/developer-mode.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Suites, Tags } from "./tests.mjs";
2-
import { params, defaultParams } from "./params.mjs";
2+
import { params, defaultParams } from "./shared/params.mjs";
33

44
export function createDeveloperModeContainer() {
55
const container = document.createElement("div");

resources/main.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { BenchmarkRunner } from "./benchmark-runner.mjs";
22
import * as Statistics from "./statistics.mjs";
33
import { Suites } from "./tests.mjs";
44
import { renderMetricView } from "./metric-ui.mjs";
5-
import { params } from "./params.mjs";
5+
import { params } from "./shared/params.mjs";
66
import { createDeveloperModeContainer } from "./developer-mode.mjs";
77

88
// FIXME(camillobruni): Add base class
+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="./_next/static/css/a0dca1379a01e5cf.css" as="style"/><link rel="stylesheet" href="./_next/static/css/a0dca1379a01e5cf.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="./_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="./_next/static/chunks/webpack-e50e9853db18b759.js" defer=""></script><script src="./_next/static/chunks/framework-2c79e2a64abdb08b.js" defer=""></script><script src="./_next/static/chunks/main-2ba37e62325cc71b.js" defer=""></script><script src="./_next/static/chunks/pages/_app-77983e68be50f72a.js" defer=""></script><script src="./_next/static/chunks/pages/_error-54de1933a164a1ff.js" defer=""></script><script src="./_next/static/ofW8d8vHz4HS9u6cDJv4X/_buildManifest.js" defer=""></script><script src="./_next/static/ofW8d8vHz4HS9u6cDJv4X/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><div id="settings-container"></div><div id="notifications-container"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"ofW8d8vHz4HS9u6cDJv4X","assetPrefix":".","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
1+
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="./_next/static/css/a0dca1379a01e5cf.css" as="style"/><link rel="stylesheet" href="./_next/static/css/a0dca1379a01e5cf.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="./_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="./_next/static/chunks/webpack-e50e9853db18b759.js" defer=""></script><script src="./_next/static/chunks/framework-2c79e2a64abdb08b.js" defer=""></script><script src="./_next/static/chunks/main-2ba37e62325cc71b.js" defer=""></script><script src="./_next/static/chunks/pages/_app-77983e68be50f72a.js" defer=""></script><script src="./_next/static/chunks/pages/_error-54de1933a164a1ff.js" defer=""></script><script src="./_next/static/tdun_naEsLSWD7CGin1GQ/_buildManifest.js" defer=""></script><script src="./_next/static/tdun_naEsLSWD7CGin1GQ/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><div id="settings-container"></div><div id="notifications-container"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"tdun_naEsLSWD7CGin1GQ","assetPrefix":".","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>

resources/newssite/news-next/dist/_next/static/chunks/pages/index-7fd4a7c8e35958df.js

-1
This file was deleted.

resources/newssite/news-next/dist/_next/static/chunks/pages/index-ca407dccff56c060.js

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

resources/newssite/news-next/dist/_next/static/ofW8d8vHz4HS9u6cDJv4X/_buildManifest.js resources/newssite/news-next/dist/_next/static/tdun_naEsLSWD7CGin1GQ/_buildManifest.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="./_next/static/css/a0dca1379a01e5cf.css" as="style"/><link rel="stylesheet" href="./_next/static/css/a0dca1379a01e5cf.css" data-n-g=""/><link rel="preload" href="./_next/static/css/2cf5163b53bb0adb.css" as="style"/><link rel="stylesheet" href="./_next/static/css/2cf5163b53bb0adb.css" data-n-p=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="./_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="./_next/static/chunks/webpack-e50e9853db18b759.js" defer=""></script><script src="./_next/static/chunks/framework-2c79e2a64abdb08b.js" defer=""></script><script src="./_next/static/chunks/main-2ba37e62325cc71b.js" defer=""></script><script src="./_next/static/chunks/pages/_app-77983e68be50f72a.js" defer=""></script><script src="./_next/static/chunks/743-fd706aeabb7828e3.js" defer=""></script><script src="./_next/static/chunks/pages/index-7fd4a7c8e35958df.js" defer=""></script><script src="./_next/static/ofW8d8vHz4HS9u6cDJv4X/_buildManifest.js" defer=""></script><script src="./_next/static/ofW8d8vHz4HS9u6cDJv4X/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><div id="settings-container"></div><div id="notifications-container"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/","query":{},"buildId":"ofW8d8vHz4HS9u6cDJv4X","assetPrefix":".","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
1+
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="./_next/static/css/a0dca1379a01e5cf.css" as="style"/><link rel="stylesheet" href="./_next/static/css/a0dca1379a01e5cf.css" data-n-g=""/><link rel="preload" href="./_next/static/css/2cf5163b53bb0adb.css" as="style"/><link rel="stylesheet" href="./_next/static/css/2cf5163b53bb0adb.css" data-n-p=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="./_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="./_next/static/chunks/webpack-e50e9853db18b759.js" defer=""></script><script src="./_next/static/chunks/framework-2c79e2a64abdb08b.js" defer=""></script><script src="./_next/static/chunks/main-2ba37e62325cc71b.js" defer=""></script><script src="./_next/static/chunks/pages/_app-77983e68be50f72a.js" defer=""></script><script src="./_next/static/chunks/743-fd706aeabb7828e3.js" defer=""></script><script src="./_next/static/chunks/pages/index-ca407dccff56c060.js" defer=""></script><script src="./_next/static/tdun_naEsLSWD7CGin1GQ/_buildManifest.js" defer=""></script><script src="./_next/static/tdun_naEsLSWD7CGin1GQ/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><div id="settings-container"></div><div id="notifications-container"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/","query":{},"buildId":"tdun_naEsLSWD7CGin1GQ","assetPrefix":".","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>

resources/newssite/news-next/package-lock.json

+18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

resources/newssite/news-next/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"react-dom": "18.2.0",
2121
"react-router-dom": "^6.11.1",
2222
"react-router-hash-link": "^2.4.3",
23+
"speedometer-utils": "../../shared",
2324
"uuid": "^9.0.0"
2425
}
2526
}

resources/newssite/news-next/src/pages/index.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1-
import React from "react";
1+
import { useEffect } from "react";
22
import { HashRouter as Router, Routes, Route } from "react-router-dom";
33
import Page from "@/partials/page/page";
44
import Head from "next/head";
55
import { DataContextProvider } from "@/context/data-context";
6+
import { BenchmarkConnector } from "speedometer-utils/workload-testing-utils.mjs";
7+
import suites from "@/workload-test.mjs";
68

79
export default function App() {
10+
// Using 'useLayoutEffect' here, since this will connect the workload after all DOM mutations happened.
11+
// This ensures that all elemetns are in the DOM, prior to signaling to the Benchmark that the workload is ready to run a test suite.
12+
useEffect(() => {
13+
const benchmarkConnector = new BenchmarkConnector(suites, "news-next", 1);
14+
benchmarkConnector.connect();
15+
16+
return () => benchmarkConnector.disconnect();
17+
}, []);
18+
819
return (
920
<>
1021
<Head>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { BenchmarkStep, BenchmarkSuite } from "speedometer-utils/benchmark.mjs";
2+
import { forceLayout, getElement } from "speedometer-utils/helpers.mjs";
3+
4+
const suites = {
5+
default: new BenchmarkSuite("default", [
6+
new BenchmarkStep("Navigate-to-US-page", () => {
7+
for (let i = 0; i < 25; i++) {
8+
getElement("#navbar-dropdown-toggle").click();
9+
forceLayout();
10+
getElement("#navbar-dropdown-toggle").click();
11+
forceLayout();
12+
}
13+
14+
getElement("#navbar-navlist-us-link").click();
15+
forceLayout();
16+
}),
17+
new BenchmarkStep("Navigate-to-World-page", () => {
18+
for (let i = 0; i < 25; i++) {
19+
getElement("#navbar-dropdown-toggle").click();
20+
forceLayout();
21+
getElement("#navbar-dropdown-toggle").click();
22+
forceLayout();
23+
}
24+
25+
getElement("#navbar-navlist-world-link").click();
26+
forceLayout();
27+
}),
28+
new BenchmarkStep("Navigate-to-Politics-page", () => {
29+
for (let i = 0; i < 25; i++) {
30+
getElement("#navbar-dropdown-toggle").click();
31+
forceLayout();
32+
getElement("#navbar-dropdown-toggle").click();
33+
forceLayout();
34+
}
35+
36+
getElement("#navbar-navlist-politics-link").click();
37+
forceLayout();
38+
}),
39+
]),
40+
};
41+
42+
export default suites;

resources/shared/benchmark.mjs

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/* eslint-disable no-case-declarations */
2+
import { TestRunner } from "./test-runner.mjs";
3+
import { Params } from "./params.mjs";
4+
5+
/**
6+
* BenchmarkStep
7+
*
8+
* A single test step, with a common interface to interact with.
9+
*/
10+
export class BenchmarkStep {
11+
constructor(name, run) {
12+
this.name = name;
13+
this.run = run;
14+
}
15+
16+
async runAndRecord(params, suite, test, callback) {
17+
const testRunner = new TestRunner(null, null, params, suite, test, callback);
18+
const result = await testRunner.runTest();
19+
return result;
20+
}
21+
}
22+
23+
/**
24+
* BenchmarkSuite
25+
*
26+
* A single test suite that contains one or more test steps.
27+
*/
28+
export class BenchmarkSuite {
29+
constructor(name, tests) {
30+
this.name = name;
31+
this.tests = tests;
32+
}
33+
34+
record(_test, syncTime, asyncTime) {
35+
const total = syncTime + asyncTime;
36+
const results = {
37+
tests: { Sync: syncTime, Async: asyncTime },
38+
total: total,
39+
};
40+
41+
return results;
42+
}
43+
44+
async runAndRecord(params, onProgress) {
45+
const measuredValues = {
46+
tests: {},
47+
total: 0,
48+
};
49+
const suiteStartLabel = `suite-${this.name}-start`;
50+
const suiteEndLabel = `suite-${this.name}-end`;
51+
52+
performance.mark(suiteStartLabel);
53+
54+
for (const test of this.tests) {
55+
const result = await test.runAndRecord(params, this, test, this.record);
56+
measuredValues.tests[test.name] = result;
57+
measuredValues.total += result.total;
58+
onProgress?.(test.name);
59+
}
60+
61+
performance.mark(suiteEndLabel);
62+
performance.measure(`suite-${this.name}`, suiteStartLabel, suiteEndLabel);
63+
64+
return {
65+
type: "suite-tests-complete",
66+
status: "success",
67+
result: measuredValues,
68+
suitename: this.name,
69+
};
70+
}
71+
}
72+
73+
/** **********************************************************************
74+
* BenchmarkConnector
75+
*
76+
* postMessage is used to communicate between app and benchmark.
77+
* When the app is ready, an 'app-ready' message is sent to signal that the app can receive instructions.
78+
*
79+
* A prepare script within the apps appends window.name and window.version from the package.json file.
80+
* The appId is build by appending name-version
81+
* It's used as an additional safe-guard to ensure the correct app responds to a message.
82+
*************************************************************************/
83+
export class BenchmarkConnector {
84+
constructor(suites, name, version) {
85+
this.suites = suites;
86+
this.name = name;
87+
this.version = version;
88+
89+
if (!name || !version)
90+
console.warn("No name or version supplied, to create a unique appId");
91+
92+
this.appId = name && version ? `${name}-${version}` : -1;
93+
this.onMessage = this.onMessage.bind(this);
94+
}
95+
96+
async onMessage(event) {
97+
if (event.data.id !== this.appId || event.data.key !== "benchmark-connector")
98+
return;
99+
100+
switch (event.data.type) {
101+
case "benchmark-suite":
102+
const params = new Params(new URLSearchParams(window.location.search));
103+
const suite = this.suites[event.data.name];
104+
if (!suite)
105+
console.error(`Suite with the name of "${event.data.name}" not found!`);
106+
const { result } = await suite.runAndRecord(params, (test) => this.sendMessage({ type: "step-complete", status: "success", appId: this.appId, name: this.name, test }));
107+
this.sendMessage({ type: "suite-complete", status: "success", appId: this.appId, result });
108+
this.disconnect();
109+
break;
110+
default:
111+
console.error(`Message data type not supported: ${event.data.type}`);
112+
}
113+
}
114+
115+
sendMessage(message) {
116+
window.top.postMessage(message, "*");
117+
}
118+
119+
connect() {
120+
window.addEventListener("message", this.onMessage);
121+
this.sendMessage({ type: "app-ready", status: "success", appId: this.appId });
122+
}
123+
124+
disconnect() {
125+
window.removeEventListener("message", this.onMessage);
126+
}
127+
}

resources/shared/helpers.mjs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Helper Methods
3+
*
4+
* Various methods that are extracted from the Page class.
5+
*/
6+
export function getParent(lookupStartNode, path) {
7+
lookupStartNode = lookupStartNode.shadowRoot ?? lookupStartNode;
8+
const parent = path.reduce((root, selector) => {
9+
const node = root.querySelector(selector);
10+
return node.shadowRoot ?? node;
11+
}, lookupStartNode);
12+
13+
return parent;
14+
}
15+
16+
export function getElement(selector, path = [], lookupStartNode = document) {
17+
const element = getParent(lookupStartNode, path).querySelector(selector);
18+
return element;
19+
}
20+
21+
export function getAllElements(selector, path = [], lookupStartNode = document) {
22+
const elements = Array.from(getParent(lookupStartNode, path).querySelectorAll(selector));
23+
return elements;
24+
}
25+
26+
export function forceLayout() {
27+
const rect = document.body.getBoundingClientRect();
28+
const e = document.elementFromPoint((rect.width / 2) | 0, (rect.height / 2) | 0);
29+
return e;
30+
}

resources/shared/package.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "speedometer-utils",
3+
"version": "1.0.0",
4+
"description": "Utility files for Speedometer & Workloads"
5+
}

resources/params.mjs resources/shared/params.mjs

+20-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
class Params {
1+
export class Params {
22
viewport = {
33
width: 800,
44
height: 600,
@@ -108,7 +108,7 @@ class Params {
108108
return defaultParams.suites;
109109
}
110110

111-
_parseTags() {
111+
_parseTags(searchParams) {
112112
if (!searchParams.has("tags"))
113113
return defaultParams.tags;
114114
if (this.suites.length)
@@ -146,21 +146,34 @@ class Params {
146146
return shuffleSeed;
147147
}
148148

149+
toSearchParamsObject() {
150+
const rawParams = { __proto__: null };
151+
for (const [key, value] of Object.entries(this)) {
152+
if (value === defaultParams[key])
153+
continue;
154+
rawParams[key] = value;
155+
}
156+
157+
// Either suites or params can be used at the same time.
158+
if (rawParams.suites?.length && rawParams.tags?.length)
159+
delete rawParams.suites;
160+
rawParams.viewport = `${this.viewport.width}x${this.viewport.height}`;
161+
162+
return new URLSearchParams(rawParams);
163+
}
164+
149165
toSearchParams() {
150-
const rawParams = { ...this };
151-
rawParams["viewport"] = `${this.viewport.width}x${this.viewport.height}`;
152-
return new URLSearchParams(rawParams).toString();
166+
return this.toSearchParamsObject().toString();
153167
}
154168
}
155169

156170
export const defaultParams = new Params();
157171

158-
const searchParams = new URLSearchParams(window.location.search);
172+
const searchParams = new URLSearchParams(typeof window !== "undefined" ? window.location.search : undefined);
159173
let maybeCustomParams = new Params();
160174
try {
161175
maybeCustomParams = new Params(searchParams);
162176
} catch (e) {
163177
console.error("Invalid URL Param", e, "\nUsing defaults as fallback:", maybeCustomParams);
164-
alert(`Invalid URL Param: ${e}`);
165178
}
166179
export const params = maybeCustomParams;

resources/test-invoker.mjs resources/shared/test-invoker.mjs

+4-4
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ export class TimerTestInvoker extends TestInvoker {
1515
setTimeout(() => {
1616
this._asyncCallback();
1717
requestAnimationFrame(async () => {
18-
await this._reportCallback();
19-
resolve();
18+
const result = await this._reportCallback();
19+
resolve(result);
2020
});
2121
}, 0);
2222
}, this._params.waitBeforeSync);
@@ -40,8 +40,8 @@ export class RAFTestInvoker extends TestInvoker {
4040
setTimeout(() => {
4141
this._asyncCallback();
4242
setTimeout(async () => {
43-
await this._reportCallback();
44-
resolve();
43+
const result = await this._reportCallback();
44+
resolve(result);
4545
}, 0);
4646
}, 0);
4747
});
File renamed without changes.

0 commit comments

Comments
 (0)