Skip to content

Commit f090838

Browse files
Jake ChampionJakeChampion
authored andcommitted
feat: Add high-resolution timing function "fastly.now()" behind feature flag "--enable-experimental-high-resolution-time-methods"
This function accepts no arguments and returns the number of Microseconds since the epoch, midnight, January 1, 1970 UTC. This function is very useful to have for running performance tests within JavaScript applications.
1 parent b1c4848 commit f090838

File tree

13 files changed

+174
-33
lines changed

13 files changed

+174
-33
lines changed

c-dependencies/js-compute-runtime/builtins/fastly.cpp

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,11 @@ bool Fastly::includeBytes(JSContext *cx, unsigned argc, JS::Value *vp) {
130130
return true;
131131
}
132132

133-
const JSFunctionSpec Fastly::methods[] = {
134-
JS_FN("dump", dump, 1, 0),
135-
JS_FN("enableDebugLogging", enableDebugLogging, 1, JSPROP_ENUMERATE),
136-
JS_FN("getGeolocationForIpAddress", getGeolocationForIpAddress, 1, JSPROP_ENUMERATE),
137-
JS_FN("getLogger", getLogger, 1, JSPROP_ENUMERATE),
138-
JS_FN("includeBytes", includeBytes, 1, JSPROP_ENUMERATE),
139-
JS_FS_END};
133+
bool Fastly::now(JSContext *cx, unsigned argc, JS::Value *vp) {
134+
JS::CallArgs args = CallArgsFromVp(argc, vp);
135+
args.rval().setNumber(JS_Now());
136+
return true;
137+
}
140138

141139
bool Fastly::env_get(JSContext *cx, unsigned argc, JS::Value *vp) {
142140
JS::CallArgs args = CallArgsFromVp(argc, vp);
@@ -204,19 +202,35 @@ const JSPropertySpec Fastly::properties[] = {
204202
JSPROP_ENUMERATE),
205203
JS_PS_END};
206204

207-
bool Fastly::create(JSContext *cx, JS::HandleObject global) {
205+
bool Fastly::create(JSContext *cx, JS::HandleObject global, FastlyOptions options) {
208206
JS::RootedObject fastly(cx, JS_NewPlainObject(cx));
209-
if (!fastly)
207+
if (!fastly) {
210208
return false;
209+
}
211210

212211
env.init(cx, Env::create(cx));
213-
if (!env)
212+
if (!env) {
214213
return false;
214+
}
215215
baseURL.init(cx);
216216
defaultBackend.init(cx);
217217

218-
if (!JS_DefineProperty(cx, global, "fastly", fastly, 0))
218+
if (!JS_DefineProperty(cx, global, "fastly", fastly, 0)) {
219219
return false;
220+
}
221+
222+
JSFunctionSpec nowfn = JS_FN("now", now, 0, JSPROP_ENUMERATE);
223+
JSFunctionSpec end = JS_FS_END;
224+
225+
const JSFunctionSpec methods[] = {
226+
JS_FN("dump", dump, 1, 0),
227+
JS_FN("enableDebugLogging", enableDebugLogging, 1, JSPROP_ENUMERATE),
228+
JS_FN("getGeolocationForIpAddress", getGeolocationForIpAddress, 1, JSPROP_ENUMERATE),
229+
JS_FN("getLogger", getLogger, 1, JSPROP_ENUMERATE),
230+
JS_FN("includeBytes", includeBytes, 1, JSPROP_ENUMERATE),
231+
options.getExperimentalHighResolutionTimeMethodsEnabled() ? nowfn : end,
232+
end};
233+
220234
return JS_DefineFunctions(cx, fastly, methods) && JS_DefineProperties(cx, fastly, properties);
221235
}
222236

c-dependencies/js-compute-runtime/builtins/fastly.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ class Fastly : public BuiltinNoConstructor<Fastly> {
2121
static JS::PersistentRooted<JSString *> defaultBackend;
2222
static bool allowDynamicBackends;
2323

24-
static const JSFunctionSpec methods[];
2524
static const JSPropertySpec properties[];
2625

26+
static bool now(JSContext *cx, unsigned argc, JS::Value *vp);
2727
static bool dump(JSContext *cx, unsigned argc, JS::Value *vp);
2828
static bool enableDebugLogging(JSContext *cx, unsigned argc, JS::Value *vp);
2929
static bool getGeolocationForIpAddress(JSContext *cx, unsigned argc, JS::Value *vp);
@@ -36,7 +36,7 @@ class Fastly : public BuiltinNoConstructor<Fastly> {
3636
static bool defaultBackend_set(JSContext *cx, unsigned argc, JS::Value *vp);
3737
static bool allowDynamicBackends_get(JSContext *cx, unsigned argc, JS::Value *vp);
3838
static bool allowDynamicBackends_set(JSContext *cx, unsigned argc, JS::Value *vp);
39-
static bool create(JSContext *cx, JS::HandleObject global);
39+
static bool create(JSContext *cx, JS::HandleObject global, FastlyOptions options);
4040
};
4141

4242
} // namespace builtins

c-dependencies/js-compute-runtime/js-compute-builtins.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,7 +1280,7 @@ bool math_random(JSContext *cx, unsigned argc, Value *vp) {
12801280
return true;
12811281
}
12821282

1283-
bool define_fastly_sys(JSContext *cx, HandleObject global) {
1283+
bool define_fastly_sys(JSContext *cx, HandleObject global, FastlyOptions options) {
12841284
// Allocating the reusable hostcall buffer here means it's baked into the
12851285
// snapshot, and since it's all zeros, it won't increase the size of the
12861286
// snapshot.
@@ -1292,7 +1292,7 @@ bool define_fastly_sys(JSContext *cx, HandleObject global) {
12921292

12931293
if (!builtins::Backend::init_class(cx, global))
12941294
return false;
1295-
if (!builtins::Fastly::create(cx, global))
1295+
if (!builtins::Fastly::create(cx, global, options))
12961296
return false;
12971297
if (!builtins::Console::create(cx, global))
12981298
return false;

c-dependencies/js-compute-runtime/js-compute-builtins.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,28 @@ bool hasWizeningFinished();
8181
bool isWizening();
8282
void markWizeningAsFinished();
8383

84-
bool define_fastly_sys(JSContext *cx, JS::HandleObject global);
84+
class FastlyOptions {
85+
private:
86+
uint8_t mask = 0;
87+
88+
public:
89+
static constexpr const uint8_t experimental_high_resolution_time_methods_enabled_flag = 1 << 0;
90+
91+
FastlyOptions() = default;
92+
93+
bool getExperimentalHighResolutionTimeMethodsEnabled() {
94+
return this->mask & experimental_high_resolution_time_methods_enabled_flag;
95+
};
96+
void setExperimentalHighResolutionTimeMethodsEnabled(bool set) {
97+
if (set) {
98+
this->mask |= experimental_high_resolution_time_methods_enabled_flag;
99+
} else {
100+
this->mask &= ~experimental_high_resolution_time_methods_enabled_flag;
101+
}
102+
};
103+
};
104+
105+
bool define_fastly_sys(JSContext *cx, JS::HandleObject global, FastlyOptions options);
85106

86107
bool RejectPromiseWithPendingError(JSContext *cx, JS::HandleObject promise);
87108

c-dependencies/js-compute-runtime/js-compute-runtime.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,13 @@ void init() {
358358
JSAutoRealm ar(cx, global);
359359
FETCH_HANDLERS = new JS::PersistentRootedObjectVector(cx);
360360

361-
define_fastly_sys(cx, global);
361+
bool ENABLE_EXPERIMENTAL_HIGH_RESOLUTION_TIME_METHODS =
362+
std::string(std::getenv("ENABLE_EXPERIMENTAL_HIGH_RESOLUTION_TIME_METHODS")) == "1";
363+
FastlyOptions options;
364+
options.setExperimentalHighResolutionTimeMethodsEnabled(
365+
ENABLE_EXPERIMENTAL_HIGH_RESOLUTION_TIME_METHODS);
366+
367+
define_fastly_sys(cx, global, options);
362368
if (!JS_DefineFunction(cx, global, "addEventListener", addEventListener, 2, 0))
363369
exit(1);
364370

integration-tests/cli/help.test.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,20 @@ test('--help should return help on stdout and zero exit code', async function (t
1919
await cleanup();
2020
});
2121
const { code, stdout, stderr } = await execute(process.execPath, `${cli} --help`);
22-
22+
2323
t.is(code, 0);
2424
t.alike(stdout, [
2525
`js-compute-runtime ${version}`,
2626
'USAGE:',
2727
'js-compute-runtime [FLAGS] [OPTIONS] [ARGS]',
2828
'FLAGS:',
29-
'-h, --help Prints help information',
30-
'-V, --version Prints version information',
29+
'-h, --help Prints help information',
30+
'-V, --version Prints version information',
3131
'OPTIONS:',
32-
'--engine-wasm <engine-wasm> The JS engine Wasm file path',
32+
'--engine-wasm <engine-wasm> The JS engine Wasm file path',
33+
'--enable-experimental-high-resolution-time-methods Enable experimental high-resolution fastly.now() method',
3334
'ARGS:',
34-
'<input> The input JS script\'s file path [default: bin/index.js]',
35+
"<input> The input JS script's file path [default: bin/index.js]",
3536
'<output> The file path to write the output Wasm module to [default: bin/main.wasm]'
3637
])
3738
t.alike(stderr, [])
@@ -50,12 +51,13 @@ test('-h should return help on stdout and zero exit code', async function (t) {
5051
'USAGE:',
5152
'js-compute-runtime [FLAGS] [OPTIONS] [ARGS]',
5253
'FLAGS:',
53-
'-h, --help Prints help information',
54-
'-V, --version Prints version information',
54+
'-h, --help Prints help information',
55+
'-V, --version Prints version information',
5556
'OPTIONS:',
56-
'--engine-wasm <engine-wasm> The JS engine Wasm file path',
57+
'--engine-wasm <engine-wasm> The JS engine Wasm file path',
58+
'--enable-experimental-high-resolution-time-methods Enable experimental high-resolution fastly.now() method',
5759
'ARGS:',
58-
'<input> The input JS script\'s file path [default: bin/index.js]',
60+
"<input> The input JS script's file path [default: bin/index.js]",
5961
'<output> The file path to write the output Wasm module to [default: bin/main.wasm]'
6062
])
6163
t.alike(stderr, [])
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* eslint-env serviceworker */
2+
3+
import { env } from 'fastly:env';
4+
import { pass, fail, assert } from "../../../assertions.js";
5+
6+
addEventListener("fetch", event => {
7+
event.respondWith(app(event))
8+
})
9+
/**
10+
* @param {FetchEvent} event
11+
* @returns {Response}
12+
*/
13+
async function app(event) {
14+
try {
15+
const path = (new URL(event.request.url)).pathname;
16+
console.log(`path: ${path}`)
17+
console.log(`FASTLY_SERVICE_VERSION: ${env('FASTLY_SERVICE_VERSION')}`)
18+
if (routes.has(path)) {
19+
const routeHandler = routes.get(path);
20+
return await routeHandler()
21+
}
22+
return fail(`${path} endpoint does not exist`)
23+
} catch (error) {
24+
return fail(`The routeHandler threw an error: ${error.message}` + '\n' + error.stack)
25+
}
26+
}
27+
28+
const routes = new Map();
29+
routes.set('/', () => {
30+
routes.delete('/');
31+
let test_routes = Array.from(routes.keys())
32+
return new Response(JSON.stringify(test_routes), { 'headers': { 'content-type': 'application/json' } });
33+
});
34+
// fastly.now
35+
{
36+
routes.set("/fastly/now", function () {
37+
let error = assert(typeof fastly.now, 'function', 'typeof fastly.now')
38+
if (error) { return error }
39+
40+
error = assert(fastly.now.name, 'now', 'fastly.now.name')
41+
if (error) { return error }
42+
43+
error = assert(fastly.now.length, 0, 'fastly.now.length')
44+
if (error) { return error }
45+
46+
error = assert(typeof fastly.now(), 'number', `typeof fastly.now()`)
47+
if (error) { return error }
48+
49+
error = assert(fastly.now() > Date.now(), true, `fastly.now() > Date.now()`)
50+
if (error) { return error }
51+
52+
console.log(fastly.now())
53+
54+
return pass()
55+
})
56+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# This file describes a Fastly Compute@Edge package. To learn more visit:
2+
# https://developer.fastly.com/reference/fastly-toml/
3+
4+
authors = ["[email protected]"]
5+
description = ""
6+
language = "other"
7+
manifest_version = 2
8+
name = "fastly"
9+
service_id = ""
10+
11+
[scripts]
12+
build = "node ../../../../js-compute-runtime-cli.js --enable-experimental-high-resolution-time-methods"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"GET /fastly/now": {
3+
"environments": ["c@e", "viceroy"],
4+
"downstream_request": {
5+
"method": "GET",
6+
"pathname": "/fastly/now"
7+
},
8+
"downstream_response": {
9+
"status": 200
10+
}
11+
}
12+
}

js-compute-runtime-cli.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@ import { printVersion } from "./src/printVersion.js";
55
import { printHelp } from "./src/printHelp.js";
66
import { addSdkMetadataField } from "./src/addSdkMetadataField.js";
77

8-
const {wasmEngine, input, component, output, version, help} = await parseInputs(process.argv.slice(2))
8+
const {
9+
enableExperimentalHighResolutionTimeMethods,
10+
wasmEngine,
11+
input,
12+
component,
13+
output,
14+
version,
15+
help
16+
} = await parseInputs(process.argv.slice(2))
917

1018
if (version) {
1119
await printVersion();
@@ -19,7 +27,7 @@ if (version) {
1927
// it could be that the user is using an older version of js-compute-runtime
2028
// and a newer version does support the platform they are using.
2129
const {compileApplicationToWasm} = await import('./src/compileApplicationToWasm.js')
22-
await compileApplicationToWasm(input, output, wasmEngine);
30+
await compileApplicationToWasm(input, output, wasmEngine, enableExperimentalHighResolutionTimeMethods);
2331
if (component) {
2432
const {compileComponent} = await import('./src/component.js');
2533
await compileComponent(output);

0 commit comments

Comments
 (0)