-
Notifications
You must be signed in to change notification settings - Fork 320
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
EW-8447 Add regression test for CPU profiling
- Loading branch information
1 parent
3374dd6
commit b972f43
Showing
4 changed files
with
150 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
load("@aspect_rules_js//js:defs.bzl", "js_test") | ||
|
||
js_test( | ||
name = "inspector-test", | ||
entry_point = "driver.mjs", | ||
env = { | ||
"WORKERD_BINARY": "$(rootpath //src/workerd/server:workerd)", | ||
"WORKERD_CONFIG": "$(rootpath :config.capnp)", | ||
}, | ||
data = [ | ||
"//:node_modules/chrome-remote-interface", | ||
"//:node_modules/@workerd/test", | ||
"//src/workerd/server:workerd", | ||
":config.capnp", | ||
":index.mjs", | ||
], | ||
tags = ["js-test"], | ||
) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# config.capnp | ||
using Workerd = import "/workerd/workerd.capnp"; | ||
|
||
const config :Workerd.Config = ( | ||
services = [ | ||
( name = "main", worker = .worker ), | ||
], | ||
sockets = [ | ||
( name = "http", address = "*:0", http = (), service = "main" ), | ||
] | ||
); | ||
|
||
const worker :Workerd.Worker = ( | ||
modules = [ | ||
( name = "./index.mjs", esModule = embed "index.mjs" ) | ||
], | ||
compatibilityDate = "2024-01-01", | ||
compatibilityFlags = ["nodejs_compat"], | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { env } from "node:process"; | ||
import { beforeEach, afterEach, test } from "node:test"; | ||
import assert from "node:assert"; | ||
import CDP from "chrome-remote-interface"; | ||
import { WorkerdServerHarness } from "@workerd/test/server-harness.mjs"; | ||
|
||
// Globals that are reset for each test. | ||
let workerd; | ||
let inspectorClient; | ||
|
||
assert(env.WORKERD_BINARY !== undefined, "You must set the WORKERD_BINARY environment variable."); | ||
assert(env.WORKERD_CONFIG !== undefined, "You must set the WORKERD_CONFIG environment variable."); | ||
|
||
// Start workerd and connect to its inspector port with our CDP library. | ||
beforeEach(async () => { | ||
workerd = new WorkerdServerHarness({ | ||
workerdBinary: env.WORKERD_BINARY, | ||
workerdConfig: env.WORKERD_CONFIG, | ||
|
||
// Hard-coded to match a socket name expected in the `workerdConfig` file. | ||
listenPortNames: [ "http" ], | ||
}); | ||
|
||
await workerd.start(); | ||
|
||
inspectorClient = await CDP({ | ||
port: await workerd.getListenInspectorPort(), | ||
|
||
// Hard-coded to match a service name expected in the `workerdConfig` file. | ||
target: "/main", | ||
|
||
// Required to avoid trying to load the Protocol (schema, I guess?) from workerd, which doesn't | ||
// implement the inspector protocol message in question. | ||
local: true, | ||
}); | ||
}); | ||
|
||
// Stop both our CDP client and workerd. | ||
afterEach(async () => { | ||
await inspectorClient.close(); | ||
inspectorClient = null; | ||
|
||
const [code, signal] = await workerd.stop(); | ||
assert.equal(code, 0); | ||
workerd = null; | ||
}); | ||
|
||
test("Profiler mostly sees deriveBits() frames", async () => { | ||
// Enable and start profiling. | ||
await inspectorClient.Profiler.enable(); | ||
await inspectorClient.Profiler.start(); | ||
|
||
// Drive the worker with a test request. A single one is sufficient. | ||
let httpPort = await workerd.getListenPort("http"); | ||
const response = await fetch(`http://localhost:${httpPort}`); | ||
await response.arrayBuffer(); | ||
|
||
// Stop and disable profiling. | ||
const profile = await inspectorClient.Profiler.stop(); | ||
await inspectorClient.Profiler.disable(); | ||
|
||
// Figure out which function name was most frequently sampled. | ||
let hitCountMap = new Map(); | ||
|
||
for (let node of profile.profile.nodes) { | ||
if (hitCountMap.get(node.callFrame.functionName) === undefined) { | ||
hitCountMap.set(node.callFrame.functionName, 0); | ||
} | ||
hitCountMap.set(node.callFrame.functionName, | ||
hitCountMap.get(node.callFrame.functionName) + node.hitCount); | ||
} | ||
|
||
let max = { | ||
name: null, | ||
count: 0, | ||
}; | ||
|
||
for (let [name, count] of hitCountMap) { | ||
if (count > max.count) { | ||
max.name = name; | ||
max.count = count; | ||
} | ||
} | ||
|
||
// The most CPU-intensive function our test script runs is `deriveBits()`, so we expect that to be | ||
// the most frequently sampled function. | ||
assert.equal(max.name, "deriveBits"); | ||
assert(max.count > 0); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// index.mjs | ||
import { Buffer } from "node:buffer"; | ||
|
||
const encoder = new TextEncoder(); | ||
|
||
async function pbkdf2Derive(password) { | ||
const passwordArray = encoder.encode(password); | ||
const passwordKey = await crypto.subtle.importKey( | ||
"raw", passwordArray, "PBKDF2", false, ["deriveBits"] | ||
); | ||
const saltArray = crypto.getRandomValues(new Uint8Array(16)); | ||
const keyBuffer = await crypto.subtle.deriveBits( | ||
{ name: "PBKDF2", hash: "SHA-256", salt: saltArray, iterations: 1_000_000 }, | ||
passwordKey, 256 | ||
); | ||
return Buffer.from(keyBuffer).toString("base64"); | ||
} | ||
|
||
export default { | ||
async fetch(request, env, ctx) { | ||
return new Response(await pbkdf2Derive("hello!")); | ||
} | ||
} |