SERVER-126888: Add a high-resolution timer for perf testing in jstests (#53877)

Co-authored-by: Steve McClure <steve.mcclure@mongodb.com>
Co-authored-by: Teo Voinea <teo.voinea@mongodb.com>
GitOrigin-RevId: e91b166957c79f6576efe2c6fbf6d796a9636905
This commit is contained in:
Matt Broadstone 2026-05-20 13:37:55 -04:00 committed by MongoDB Bot
parent 0b730ee3c2
commit 41b16e48d9
17 changed files with 719 additions and 50 deletions

View File

@ -37,9 +37,7 @@ def jsToHeader(target, source):
def lineToChars(s):
return ",".join(str(ord(c)) for c in (s.rstrip() + "\n")) + ","
for s in source:
filename = str(s)
objname = os.path.split(filename)[1].split(".")[0]
for module_name, filename, objname in source:
stringname = "_jscode_raw_" + objname
h.append("constexpr char " + stringname + "[] = {")
@ -53,7 +51,7 @@ def jsToHeader(target, source):
h.append("extern const JSFile %s;" % objname)
h.append(
'const JSFile %s = { "%s", StringData(%s, sizeof(%s) - 1) };'
% (objname, filename.replace("\\", "/"), stringname, stringname)
% (objname, module_name, stringname, stringname)
)
h.append("} // namespace JSFiles")
@ -69,9 +67,36 @@ def jsToHeader(target, source):
out.close()
def parse_args(args):
"""Parse source files and optional --module name overrides.
Accepts both forms, which may be mixed:
path/to/foo.js -- module name = file path, C++ var = basename (foo)
--module std:performance foo.js -- module name = std:performance, C++ var = std_performance
"""
entries = []
i = 0
while i < len(args):
if args[i] == "--module":
if i + 2 >= len(args):
raise ValueError("--module requires two arguments: <name> <file>")
module_name = args[i + 1]
filename = args[i + 2]
objname = "".join(c if c.isalnum() else "_" for c in module_name)
entries.append((module_name, filename, objname))
i += 3
else:
filename = args[i].replace("\\", "/")
objname = os.path.split(filename)[1].split(".")[0]
entries.append((filename, filename, objname))
i += 1
return entries
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Must specify [target] [source] ")
sys.exit(1)
jsToHeader(sys.argv[1], sys.argv[2:])
jsToHeader(sys.argv[1], parse_args(sys.argv[2:]))

View File

@ -25,6 +25,7 @@ export default [
languageOptions: {
globals: {
...globals.mongo,
internalModule: true,
// jstests/global.d.ts
TestData: true,

View File

@ -2,7 +2,13 @@
"compilerOptions": {
"baseUrl": ".",
"disableSizeLimit": true,
"target": "ES2020"
"target": "ES2020",
"moduleResolution": "node",
"paths": {
"std:*": [
"src/mongo/shell/std/*"
]
}
},
"include": [
"jstests/**/*.js",
@ -11,6 +17,8 @@
"src/mongo/scripting/**/*.d.ts",
"src/mongo/shell/*.js",
"src/mongo/shell/*.d.ts",
"src/mongo/shell/std/**/*.js",
"src/mongo/shell/std/**/*.d.ts",
"src/third_party/fast_check/**/*"
],
"exclude": [

View File

@ -111,6 +111,7 @@ const expectedGlobalVars = [
"getJSHeapLimitMB",
"globalThis",
"hex_md5",
"internalModule",
"isFinite",
"highWaterMarkResumeTokenType",
"isNaN",

View File

@ -0,0 +1,23 @@
import {describe, it} from "jstests/libs/mochalite.js";
describe("module loader internal binding restrictions", function () {
it("does not allow scripts to import internal bindings as ES modules", async function () {
let importError = null;
try {
await import("performance");
} catch (error) {
importError = error;
}
assert.neq(importError, null, "scripts should not import internal bindings as ES modules");
});
it("prevents non-std modules from calling internalModule()", function () {
let callError = assert.throws(() => internalModule("performance"));
assert.neq(callError, null, "non-std modules should not call internalModule()");
assert(
callError.message.includes("restricted to std:* modules"),
`unexpected internalModule error: ${callError.message}`,
);
});
});

View File

@ -0,0 +1,22 @@
import {describe, it} from "jstests/libs/mochalite.js";
import {performance} from "std:performance";
describe("std:performance with performance internal binding", function () {
it("uses a monotonic high-resolution clock", function () {
const first = performance.now();
const second = performance.now();
assert.gte(first, 0, "performance.now() should be non-negative");
assert.gte(second, first, "performance.now() should be monotonic");
let sawFractionalValue = false;
for (let i = 0; i < 20000; ++i) {
const sample = performance.now();
if (!Number.isInteger(sample)) {
sawFractionalValue = true;
break;
}
}
assert(sawFractionalValue, "performance.now() should report sub-millisecond precision");
});
});

View File

@ -6,3 +6,19 @@ declare function getJSHeapLimitMB();
declare function print();
declare function sleep();
declare function version();
type InternalModuleName = "performance";
/**
* Loads a shell-internal module binding for std module bootstrapping.
*
* This API is intended only for implementations of `std:*` modules under
* `src/mongo/shell/std`. Calling this from non-`std:*` modules throws at
* runtime.
*
* JSTests must not call this directly. Instead import the std module:
* e.g.`import {performance} from "std:performance";`
*/
declare function internalModule(
moduleName: InternalModuleName,
): Record<string, unknown>;

View File

@ -29,6 +29,26 @@ mongo_js_library(
visibility = ["//visibility:public"],
)
mongo_cc_library(
name = "internal_module_registry",
srcs = ["internal_module_registry.cpp"],
copts = select({
"@platforms//os:windows": [
# The default MSVC preprocessor elides commas in some cases as a
# convenience, but this behavior breaks compilation of jspubtd.h.
# Enabling the newer preprocessor fixes the problem.
"/Zc:preprocessor",
"/wd5104",
"/wd5105",
],
"//conditions:default": [],
}),
deps = [
"//src/mongo:base",
"//src/third_party/mozjs",
],
)
mongo_cc_library(
name = "mozjs_shell",
srcs = glob(
@ -38,6 +58,7 @@ mongo_cc_library(
"asan_handles_test.cpp",
"implscope_test.cpp",
"module_loader_test.cpp",
"internal_module_registry.cpp",
],
) + [
":scripting_util_gen",
@ -55,11 +76,13 @@ mongo_cc_library(
"//conditions:default": [],
}),
deps = [
":internal_module_registry",
"//src/mongo/client:clientdriver_network",
"//src/mongo/db:service_context",
"//src/mongo/db/auth:security_token_auth",
"//src/mongo/scripting:scripting_common",
"//src/mongo/scripting/mozjs/common:mozjs_common",
"//src/mongo/shell:std_internal_modules",
"//src/mongo/util:buildinfo",
"//src/mongo/util/concurrency:spin_lock",
"//src/third_party/mozjs",

View File

@ -0,0 +1,86 @@
/**
* Copyright (C) 2026-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/scripting/mozjs/shell/internal_module_registry.h"
#include "mongo/util/concurrency/with_lock.h"
#include <mutex>
#include <string>
#include <unordered_map>
namespace mongo::mozjs {
namespace {
using InternalModuleMap = std::unordered_map<std::string, InternalModuleRegistration>;
InternalModuleMap& getInternalModuleMap(WithLock) {
static InternalModuleMap moduleMap;
return moduleMap;
}
std::mutex& getInternalModuleMapMutex() {
static std::mutex moduleMapMutex;
return moduleMapMutex;
}
void addInternalModuleRegistration(std::string_view moduleName,
InternalModuleInitializer initialize,
const ::mongo::JSFile* setupFile) {
if (moduleName.empty() || initialize == nullptr) {
return;
}
std::lock_guard<std::mutex> lock(getInternalModuleMapMutex());
getInternalModuleMap(lock)[std::string(moduleName)] =
InternalModuleRegistration{std::string(moduleName), initialize, setupFile};
}
} // namespace
std::vector<InternalModuleRegistration> listRegisteredInternalModules() {
std::vector<InternalModuleRegistration> registrations;
std::lock_guard<std::mutex> lock(getInternalModuleMapMutex());
const auto& moduleMap = getInternalModuleMap(lock);
registrations.reserve(moduleMap.size());
for (const auto& [_, registration] : moduleMap) {
registrations.push_back(registration);
}
return registrations;
}
InternalModuleRegistrar::InternalModuleRegistrar(std::string_view moduleName,
InternalModuleInitializer initialize,
const ::mongo::JSFile* setupFile) {
addInternalModuleRegistration(moduleName, initialize, setupFile);
}
} // namespace mongo::mozjs

View File

@ -0,0 +1,80 @@
/**
* Copyright (C) 2026-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include "mongo/util/modules.h"
#include <string>
#include <string_view>
#include <vector>
#include <js/RootingAPI.h>
struct JSContext;
namespace mongo {
struct JSFile;
}
namespace mongo::mozjs {
using InternalModuleInitializer = bool (*)(JSContext* cx, JS::HandleObject target);
struct InternalModuleRegistration {
std::string moduleName;
InternalModuleInitializer initialize;
const ::mongo::JSFile* setupFile;
};
std::vector<InternalModuleRegistration> listRegisteredInternalModules();
class MONGO_MOD_PUB InternalModuleRegistrar {
public:
InternalModuleRegistrar(std::string_view moduleName,
InternalModuleInitializer initialize,
const ::mongo::JSFile* setupFile = nullptr);
};
} // namespace mongo::mozjs
#define MONGO_INTERNAL_MODULE_CONCAT_IMPL(X, Y) X##Y
#define MONGO_INTERNAL_MODULE_CONCAT(X, Y) MONGO_INTERNAL_MODULE_CONCAT_IMPL(X, Y)
#define MONGO_REGISTER_INTERNAL_MODULE(MODULE_NAME, INITIALIZE_FN) \
namespace { \
const ::mongo::mozjs::InternalModuleRegistrar MONGO_INTERNAL_MODULE_CONCAT( \
kInternalModuleRegistrar_, __LINE__)(MODULE_NAME, INITIALIZE_FN, nullptr); \
} // namespace
#define MONGO_REGISTER_INTERNAL_MODULE_WITH_SETUP(MODULE_NAME, INITIALIZE_FN, SETUP_FILE) \
namespace { \
const ::mongo::mozjs::InternalModuleRegistrar MONGO_INTERNAL_MODULE_CONCAT( \
kInternalModuleRegistrar_, __LINE__)(MODULE_NAME, INITIALIZE_FN, SETUP_FILE); \
} // namespace

View File

@ -41,6 +41,7 @@
#include "mongo/logv2/log.h"
#include "mongo/scripting/mongo_path_util.h"
#include "mongo/scripting/mozjs/shell/implscope.h"
#include "mongo/scripting/mozjs/shell/internal_module_registry.h"
#include "mongo/scripting/mozjs/shell/module_loader.h"
#include "mongo/util/file.h"
@ -53,6 +54,7 @@
#include <boost/none.hpp>
#include <boost/optional/optional.hpp>
#include <js/CallArgs.h>
#include <js/CharacterEncoding.h>
#include <js/CompileOptions.h>
#include <js/Context.h>
@ -63,6 +65,7 @@
#include <js/PropertyAndElement.h>
#include <js/PropertyDescriptor.h>
#include <js/RootingAPI.h>
#include <js/ScriptPrivate.h>
#include <js/String.h>
#include <js/TypeDecls.h>
#include <js/Utility.h>
@ -75,6 +78,143 @@
namespace mongo {
namespace mozjs {
namespace {
constexpr const char* kStdModulePrefix = "std:";
enum GlobalAppSlot {
GlobalAppSlotModuleRegistry,
GlobalAppSlotInternalBindingsRegistry,
GlobalAppSlotCount
};
bool startsWithPrefix(const char* value, const char* prefix) {
return std::strncmp(value, prefix, std::strlen(prefix)) == 0;
}
bool getOrCreateGlobalMapInSlot(JSContext* cx, GlobalAppSlot slot, JS::MutableHandleObject mapOut) {
mapOut.set(nullptr);
JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
if (!global) {
return false;
}
JS::RootedValue value(cx, JS::GetReservedSlot(global, slot));
if (!value.isUndefined()) {
mapOut.set(&value.toObject());
return true;
}
JS::RootedObject map(cx, JS::NewMapObject(cx));
if (!map) {
return false;
}
JS::SetReservedSlot(global, slot, JS::ObjectValue(*map));
mapOut.set(map);
return true;
}
bool getOrCreateInternalModuleBindingsRegistry(JSContext* cx,
JS::MutableHandleObject bindingsRegistryOut) {
return getOrCreateGlobalMapInSlot(
cx, GlobalAppSlotInternalBindingsRegistry, bindingsRegistryOut);
}
bool registerInternalModuleBinding(JSContext* cx,
const char* moduleName,
JS::HandleObject bindingObject) {
JS::RootedObject bindingsRegistry(cx);
if (!getOrCreateInternalModuleBindingsRegistry(cx, &bindingsRegistry)) {
return false;
}
JS::RootedString moduleNameString(cx, JS_NewStringCopyZ(cx, moduleName));
if (!moduleNameString) {
return false;
}
JS::RootedValue moduleNameValue(cx, JS::StringValue(moduleNameString));
JS::RootedValue bindingValue(cx, JS::ObjectValue(*bindingObject));
return JS::MapSet(cx, bindingsRegistry, moduleNameValue, bindingValue);
}
bool lookUpInternalModuleBinding(JSContext* cx,
JS::HandleString moduleName,
JS::MutableHandleObject bindingOut) {
bindingOut.set(nullptr);
JS::RootedObject bindingsRegistry(cx);
if (!getOrCreateInternalModuleBindingsRegistry(cx, &bindingsRegistry)) {
return false;
}
JS::RootedValue moduleNameValue(cx, JS::StringValue(moduleName));
JS::RootedValue bindingValue(cx);
if (!JS::MapGet(cx, bindingsRegistry, moduleNameValue, &bindingValue)) {
return false;
}
if (!bindingValue.isUndefined()) {
bindingOut.set(&bindingValue.toObject());
}
return true;
}
bool internalModuleFunction(JSContext* cx, unsigned argc, JS::Value* vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
if (args.length() < 1 || !args[0].isString()) {
JS_ReportErrorASCII(cx, "internalModule requires a string module name");
return false;
}
JS::RootedValue callerPrivate(cx, JS::GetScriptedCallerPrivate(cx));
if (!callerPrivate.isObject()) {
JS_ReportErrorASCII(cx, "internalModule is restricted to std:* modules");
return false;
}
JS::RootedObject callerInfo(cx, &callerPrivate.toObject());
JS::RootedValue callerPathValue(cx);
if (!JS_GetProperty(cx, callerInfo, "path", &callerPathValue)) {
return false;
}
if (!callerPathValue.isString()) {
JS_ReportErrorASCII(cx, "internalModule is restricted to std:* modules");
return false;
}
JS::RootedString callerPath(cx, callerPathValue.toString());
JS::UniqueChars callerPathChars = JS_EncodeStringToUTF8(cx, callerPath);
if (!callerPathChars) {
return false;
}
if (!startsWithPrefix(callerPathChars.get(), kStdModulePrefix)) {
JS_ReportErrorUTF8(cx,
"internalModule is restricted to std:* modules (called from %s)",
callerPathChars.get());
return false;
}
JS::RootedString moduleName(cx, args[0].toString());
JS::RootedObject binding(cx);
if (!lookUpInternalModuleBinding(cx, moduleName, &binding)) {
return false;
}
if (!binding) {
JS::UniqueChars moduleNameChars = JS_EncodeStringToUTF8(cx, moduleName);
if (!moduleNameChars) {
return false;
}
JS_ReportErrorUTF8(cx, "No such internal module '%s'", moduleNameChars.get());
return false;
}
args.rval().setObject(*binding);
return true;
}
} // namespace
bool ModuleLoader::init(JSContext* cx, const std::string& loadPath) {
_baseUrl = resolveBaseUrl(cx, loadPath);
@ -92,7 +232,17 @@ bool ModuleLoader::init(JSContext* cx, const std::string& loadPath) {
JSRuntime* rt = JS_GetRuntime(cx);
JS::SetModuleResolveHook(rt, ModuleLoader::moduleResolveHook);
JS::SetModuleDynamicImportHook(rt, ModuleLoader::dynamicModuleImportHook);
return true;
JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
if (!global) {
return false;
}
if (!JS_DefineFunction(
cx, global, "internalModule", internalModuleFunction, 1, JSPROP_PERMANENT)) {
return false;
}
return preloadInternalModules(cx);
}
JSObject* ModuleLoader::loadRootModuleFromPath(JSContext* cx, const std::string& path) {
@ -131,6 +281,33 @@ JSObject* ModuleLoader::loadRootModule(JSContext* cx,
return resolveImportedModule(cx, referencingPrivate, moduleRequest);
}
bool ModuleLoader::preloadInternalModules(JSContext* cx) {
for (const auto& registration : listRegisteredInternalModules()) {
JS::RootedObject binding(cx, JS_NewPlainObject(cx));
if (!binding) {
return false;
}
if (!registration.initialize(cx, binding)) {
return false;
}
if (!registerInternalModuleBinding(cx, registration.moduleName.c_str(), binding)) {
return false;
}
if (registration.setupFile) {
JS::RootedObject setupModule(cx,
loadRootModuleFromSource(cx,
registration.setupFile->name,
registration.setupFile->source));
if (!setupModule) {
return false;
}
}
}
return true;
}
// static
JSObject* ModuleLoader::moduleResolveHook(JSContext* cx,
JS::HandleValue referencingPrivate,
@ -235,7 +412,18 @@ JSString* ModuleLoader::resolveAndNormalize(JSContext* cx,
return nullptr;
}
// check if it's already in the registry
// Root modules loaded from in-memory source (via execSetup) carry a source payload in the
// referencing info. For those loads, keep the existing behavior and bypass file-system lookup.
bool hasSource{false};
JS::RootedObject referencingInfoObject(cx, &referencingInfo.toObject());
if (!JS_HasProperty(cx, referencingInfoObject, "source", &hasSource)) {
return nullptr;
}
if (hasSource) {
return specifierString;
}
// Check if this specifier is already in the in-memory module registry.
JS::Rooted<JSString*> path(cx, specifierString);
if (!path) {
return nullptr;
@ -248,38 +436,32 @@ JSString* ModuleLoader::resolveAndNormalize(JSContext* cx,
return specifierString;
}
// check if it has a source
bool hasSource;
JS::RootedObject referencingInfoObject(cx, &referencingInfo.toObject());
if (!JS_HasProperty(cx, referencingInfoObject, "source", &hasSource)) {
return nullptr;
}
if (hasSource) {
JS::UniqueChars specifierChars = JS_EncodeStringToUTF8(cx, specifierString);
uassert(ErrorCodes::JSInterpreterFailure,
"Failed to UTF-8 encode module specifier",
specifierChars);
// STD modules are identified by module specifier and don't map to filesystem paths.
if (startsWithPrefix(specifierChars.get(), kStdModulePrefix)) {
return specifierString;
}
// otherwise try to read content from the file system
JS::RootedString refPath(cx);
if (!getScriptPath(cx, referencingInfo, &refPath)) {
return nullptr;
}
if (!refPath) {
JS_ReportErrorASCII(cx, "No path set for referencing module");
return nullptr;
}
JS::UniqueChars specifierChars = JS_EncodeStringToUTF8(cx, specifierString);
uassert(ErrorCodes::JSInterpreterFailure,
"Failed to UTF-8 encode module specifier",
specifierChars);
boost::filesystem::path specifierPath(specifierChars.get());
JS::UniqueChars refPathChars = JS_EncodeStringToUTF8(cx, refPath);
uassert(ErrorCodes::JSInterpreterFailure,
"Failed to UTF-8 encode referencing module path",
refPathChars);
// otherwise try to read content from the file system
boost::filesystem::path specifierPath(specifierChars.get());
boost::filesystem::path refAbsPath(refPathChars.get());
if (is_directory(specifierPath)) {
@ -373,16 +555,18 @@ JSObject* ModuleLoader::loadAndParse(JSContext* cx,
return module;
}
JS::RootedString source(cx, fetchSource(cx, path, referencingPrivate));
if (!source) {
return nullptr;
}
JS::UniqueChars filename = JS_EncodeStringToLatin1(cx, path);
if (!filename) {
return nullptr;
}
JS::CompileOptions options(cx);
options.setFileAndLine(filename.get(), 1);
JS::RootedString source(cx, fetchSource(cx, path, referencingPrivate));
if (!source) {
JS::RootedObject info(cx, createScriptPrivateInfo(cx, path));
if (!info) {
return nullptr;
}
@ -397,16 +581,13 @@ JSObject* ModuleLoader::loadAndParse(JSContext* cx,
return nullptr;
}
JS::CompileOptions options(cx);
options.setFileAndLine(filename.get(), 1);
module = JS::CompileModule(cx, options, srcBuf);
if (!module) {
return nullptr;
}
JS::RootedObject info(cx, createScriptPrivateInfo(cx, path));
if (!info) {
return nullptr;
}
JS::SetModulePrivate(module, JS::ObjectValue(*info));
if (!addModuleToRegistry(cx, path, module)) {
@ -437,24 +618,12 @@ JSString* ModuleLoader::fetchSource(JSContext* cx,
return fileAsString(cx, resolvedPath);
}
enum GlobalAppSlot { GlobalAppSlotModuleRegistry, GlobalAppSlotCount };
JSObject* ModuleLoader::getOrCreateModuleRegistry(JSContext* cx) {
JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
if (!global) {
JS::RootedObject registry(cx);
if (!getOrCreateGlobalMapInSlot(cx, GlobalAppSlotModuleRegistry, &registry)) {
return nullptr;
}
JS::RootedValue value(cx, JS::GetReservedSlot(global, GlobalAppSlotModuleRegistry));
if (!value.isUndefined()) {
return &value.toObject();
}
JS::RootedObject registry(cx, JS::NewMapObject(cx));
if (!registry) {
return nullptr;
}
JS::SetReservedSlot(global, GlobalAppSlotModuleRegistry, JS::ObjectValue(*registry));
return registry;
}

View File

@ -89,6 +89,7 @@ private:
JS::HandleValue referencingInfo);
JSObject* getOrCreateModuleRegistry(JSContext* cx);
JSString* fetchSource(JSContext* cx, JS::HandleString path, JS::HandleValue referencingPrivate);
bool preloadInternalModules(JSContext* cx);
bool getScriptPath(JSContext* cx,
JS::HandleValue privateValue,
JS::MutableHandleString pathOut);

View File

@ -290,16 +290,28 @@ MONGOJS_CPP_JSFILES = [
":error_codes_js",
]
# Converts core JS file content into CPP structures to be loaded directly
# Converts std JS file content into CPP structures to be loaded directly
# into the native binary via bytecode upon startup.
MONGOJS_STD_JS_MODULES = [
("std:performance", "std/performance.js"),
]
render_template(
name = "mongojs_cpp",
srcs = MONGOJS_CPP_JSFILES,
srcs = MONGOJS_CPP_JSFILES + [file for _, file in MONGOJS_STD_JS_MODULES],
cmd = [
"$(location mongojs.cpp)",
] + [
"$(location {})".format(file)
for file in MONGOJS_CPP_JSFILES
] + [
item
for name, file in MONGOJS_STD_JS_MODULES
for item in [
"--module",
name,
"$(location {})".format(file),
]
],
output = "mongojs.cpp",
python_file = "//buildscripts:jstoh.py",
@ -342,6 +354,28 @@ mongo_cc_library(
],
)
mongo_cc_library(
name = "std_internal_modules",
srcs = glob(["std/*.cpp"]),
auto_header = False,
copts = select({
"@platforms//os:windows": [
# The default MSVC preprocessor elides commas in some cases as a
# convenience, but this behavior breaks compilation of jspubtd.h.
# Enabling the newer preprocessor fixes the problem.
"/Zc:preprocessor",
"/wd5104",
"/wd5105",
],
"//conditions:default": [],
}),
deps = [
":mongojs",
"//src/mongo/scripting/mozjs/shell:internal_module_registry",
"//src/third_party/mozjs",
],
)
mongo_cc_library(
name = "shell_options_register",
srcs = [

View File

@ -0,0 +1,90 @@
# `std` Modules
This directory contains shell standard modules that are imported as `std:<name>` from jstests.
Example:
```js
import {performance} from "std:performance";
```
## What are `std` modules
A `std` module is a built-in module shipped with the `mongo` test runner. Each module is comprised
of:
- a public JavaScript module file (`<name>.js`) which defines the public API of the module.
- an internal C++ binding implementation (`<name>.cpp`) offering bindings between C++ and
JavaScript.
- a TypeScript declaration file for the public API (`<name>.d.ts`), documenting the API for editor
discoverability.
## What is `internalModule`
The C++ bindings for internal modules declare methods and class that can be used for interoperate
between the two environments. These bindings are exclusively limited to use in the public JavaScript
API file, so as to not pollute the global namespace. This helps us localize documentation and
improve discoverability.
## How to Define a New `std` Module
When adding a new module `<name>`, update all of the following:
1. Add public API file: `src/mongo/shell/std/<name>.js`
- Export the stable API users import from `std:<name>`.
- Use `internalModule("<name>")` only inside this `std:*` module implementation.
2. Add internal binding file: `src/mongo/shell/std/<name>.cpp`
- Define an initializer with signature `bool init(JSContext*, JS::HandleObject target)`.
- Add functions/properties onto `target`.
- Register with
`MONGO_REGISTER_INTERNAL_MODULE_WITH_SETUP("<name>", initFn, &::mongo::JSFiles::std_<name>)`.
3. Add public typings: `src/mongo/shell/std/<name>.d.ts`
- Describe exported symbols from `<name>.js`.
4. Register the std module name in `src/mongo/shell/BUILD.bazel`
- Add `("std:<name>", "std/<name>.js")` to `MONGOJS_STD_JS_MODULES`.
5. Update allowed internal module names in `src/mongo/scripting/mozjs/common/global.d.ts`
- Add `"<name>"` to the `InternalModuleName` set used by `internalModule()`.
Name consistency matters:
- JS import name uses `std:<name>`
- internal binding lookup uses `<name>` (without the `std:` prefix)
- all entries above should refer to the same module concept
## How the Implementation Works Internally
### Build-time wiring
1. `src/mongo/shell/BUILD.bazel` lists `MONGOJS_STD_JS_MODULES`.
2. `buildscripts/jstoh.py` consumes these entries via `--module` and generates embedded `JSFile`
entries in `mongojs.cpp`.
3. `std_internal_modules` compiles all `std/*.cpp` files and links them with the internal module
registry.
### Runtime wiring
1. Each `std/<name>.cpp` registration macro creates a static `InternalModuleRegistrar`.
2. Registrars populate the internal module registry (`internal_module_registry.*`) at startup.
3. `ModuleLoader::init()` defines a global `internalModule()` function and calls
`preloadInternalModules()`.
4. `preloadInternalModules()`:
- creates a binding object per registered internal module
- calls each module's C++ initializer to populate it
- stores the binding in an internal registry map keyed by module name
- loads the module setup JS (`std:<name>`) from embedded `JSFile` source
5. The setup JS module (for example `std/performance.js`) runs as a `std:*` module and is allowed to
call `internalModule("<name>")`.
6. Non-`std:*` callers are rejected by `internalModule()` at runtime.
This split keeps native internals private while exposing a stable, documented JavaScript API.

View File

@ -0,0 +1,72 @@
/**
* Copyright (C) 2026-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/scripting/mozjs/shell/internal_module_registry.h"
#include <chrono>
#include <jsapi.h>
#include <js/CallArgs.h>
#include <js/PropertySpec.h>
#include <js/Value.h>
namespace mongo::JSFiles {
extern const JSFile std_performance;
} // namespace mongo::JSFiles
namespace mongo::mozjs::std_modules {
namespace {
constexpr double kNanosPerMillis = 1e6;
const auto kPerformanceProcessStart = std::chrono::steady_clock::now();
double performanceNowMillis() {
const auto elapsedNs = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - kPerformanceProcessStart);
return static_cast<double>(elapsedNs.count()) / kNanosPerMillis;
}
bool now(JSContext* cx, unsigned argc, JS::Value* vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
args.rval().setDouble(performanceNowMillis());
return true;
}
} // namespace
bool initializePerformanceBinding(JSContext* cx, JS::HandleObject target) {
return JS_DefineFunction(cx, target, "now", now, 0, JSPROP_ENUMERATE);
}
MONGO_REGISTER_INTERNAL_MODULE_WITH_SETUP("performance",
initializePerformanceBinding,
&::mongo::JSFiles::std_performance);
} // namespace mongo::mozjs::std_modules

11
src/mongo/shell/std/performance.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
export interface PerformanceApi {
/**
* Returns a high-resolution, monotonic timestamp in milliseconds.
*
* The timestamp is relative to shell process startup and is not tied to
* wall-clock time.
*/
now(): number;
}
export declare const performance: PerformanceApi;

View File

@ -0,0 +1,7 @@
const {now: internalNow} = internalModule("performance");
export const performance = Object.freeze({
now() {
return internalNow();
},
});