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:
parent
0b730ee3c2
commit
41b16e48d9
@ -37,9 +37,7 @@ def jsToHeader(target, source):
|
|||||||
def lineToChars(s):
|
def lineToChars(s):
|
||||||
return ",".join(str(ord(c)) for c in (s.rstrip() + "\n")) + ","
|
return ",".join(str(ord(c)) for c in (s.rstrip() + "\n")) + ","
|
||||||
|
|
||||||
for s in source:
|
for module_name, filename, objname in source:
|
||||||
filename = str(s)
|
|
||||||
objname = os.path.split(filename)[1].split(".")[0]
|
|
||||||
stringname = "_jscode_raw_" + objname
|
stringname = "_jscode_raw_" + objname
|
||||||
|
|
||||||
h.append("constexpr char " + stringname + "[] = {")
|
h.append("constexpr char " + stringname + "[] = {")
|
||||||
@ -53,7 +51,7 @@ def jsToHeader(target, source):
|
|||||||
h.append("extern const JSFile %s;" % objname)
|
h.append("extern const JSFile %s;" % objname)
|
||||||
h.append(
|
h.append(
|
||||||
'const JSFile %s = { "%s", StringData(%s, sizeof(%s) - 1) };'
|
'const JSFile %s = { "%s", StringData(%s, sizeof(%s) - 1) };'
|
||||||
% (objname, filename.replace("\\", "/"), stringname, stringname)
|
% (objname, module_name, stringname, stringname)
|
||||||
)
|
)
|
||||||
|
|
||||||
h.append("} // namespace JSFiles")
|
h.append("} // namespace JSFiles")
|
||||||
@ -69,9 +67,36 @@ def jsToHeader(target, source):
|
|||||||
out.close()
|
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 __name__ == "__main__":
|
||||||
if len(sys.argv) < 3:
|
if len(sys.argv) < 3:
|
||||||
print("Must specify [target] [source] ")
|
print("Must specify [target] [source] ")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
jsToHeader(sys.argv[1], sys.argv[2:])
|
jsToHeader(sys.argv[1], parse_args(sys.argv[2:]))
|
||||||
|
|||||||
@ -25,6 +25,7 @@ export default [
|
|||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: {
|
globals: {
|
||||||
...globals.mongo,
|
...globals.mongo,
|
||||||
|
internalModule: true,
|
||||||
|
|
||||||
// jstests/global.d.ts
|
// jstests/global.d.ts
|
||||||
TestData: true,
|
TestData: true,
|
||||||
|
|||||||
@ -2,7 +2,13 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"disableSizeLimit": true,
|
"disableSizeLimit": true,
|
||||||
"target": "ES2020"
|
"target": "ES2020",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"paths": {
|
||||||
|
"std:*": [
|
||||||
|
"src/mongo/shell/std/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"jstests/**/*.js",
|
"jstests/**/*.js",
|
||||||
@ -11,6 +17,8 @@
|
|||||||
"src/mongo/scripting/**/*.d.ts",
|
"src/mongo/scripting/**/*.d.ts",
|
||||||
"src/mongo/shell/*.js",
|
"src/mongo/shell/*.js",
|
||||||
"src/mongo/shell/*.d.ts",
|
"src/mongo/shell/*.d.ts",
|
||||||
|
"src/mongo/shell/std/**/*.js",
|
||||||
|
"src/mongo/shell/std/**/*.d.ts",
|
||||||
"src/third_party/fast_check/**/*"
|
"src/third_party/fast_check/**/*"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
|
|||||||
@ -111,6 +111,7 @@ const expectedGlobalVars = [
|
|||||||
"getJSHeapLimitMB",
|
"getJSHeapLimitMB",
|
||||||
"globalThis",
|
"globalThis",
|
||||||
"hex_md5",
|
"hex_md5",
|
||||||
|
"internalModule",
|
||||||
"isFinite",
|
"isFinite",
|
||||||
"highWaterMarkResumeTokenType",
|
"highWaterMarkResumeTokenType",
|
||||||
"isNaN",
|
"isNaN",
|
||||||
|
|||||||
23
jstests/noPassthrough/shell/js/module_loader.js
Normal file
23
jstests/noPassthrough/shell/js/module_loader.js
Normal 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}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
22
jstests/noPassthrough/shell/js/std_performance.js
Normal file
22
jstests/noPassthrough/shell/js/std_performance.js
Normal 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");
|
||||||
|
});
|
||||||
|
});
|
||||||
16
src/mongo/scripting/mozjs/common/global.d.ts
vendored
16
src/mongo/scripting/mozjs/common/global.d.ts
vendored
@ -6,3 +6,19 @@ declare function getJSHeapLimitMB();
|
|||||||
declare function print();
|
declare function print();
|
||||||
declare function sleep();
|
declare function sleep();
|
||||||
declare function version();
|
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>;
|
||||||
|
|||||||
@ -29,6 +29,26 @@ mongo_js_library(
|
|||||||
visibility = ["//visibility:public"],
|
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(
|
mongo_cc_library(
|
||||||
name = "mozjs_shell",
|
name = "mozjs_shell",
|
||||||
srcs = glob(
|
srcs = glob(
|
||||||
@ -38,6 +58,7 @@ mongo_cc_library(
|
|||||||
"asan_handles_test.cpp",
|
"asan_handles_test.cpp",
|
||||||
"implscope_test.cpp",
|
"implscope_test.cpp",
|
||||||
"module_loader_test.cpp",
|
"module_loader_test.cpp",
|
||||||
|
"internal_module_registry.cpp",
|
||||||
],
|
],
|
||||||
) + [
|
) + [
|
||||||
":scripting_util_gen",
|
":scripting_util_gen",
|
||||||
@ -55,11 +76,13 @@ mongo_cc_library(
|
|||||||
"//conditions:default": [],
|
"//conditions:default": [],
|
||||||
}),
|
}),
|
||||||
deps = [
|
deps = [
|
||||||
|
":internal_module_registry",
|
||||||
"//src/mongo/client:clientdriver_network",
|
"//src/mongo/client:clientdriver_network",
|
||||||
"//src/mongo/db:service_context",
|
"//src/mongo/db:service_context",
|
||||||
"//src/mongo/db/auth:security_token_auth",
|
"//src/mongo/db/auth:security_token_auth",
|
||||||
"//src/mongo/scripting:scripting_common",
|
"//src/mongo/scripting:scripting_common",
|
||||||
"//src/mongo/scripting/mozjs/common:mozjs_common",
|
"//src/mongo/scripting/mozjs/common:mozjs_common",
|
||||||
|
"//src/mongo/shell:std_internal_modules",
|
||||||
"//src/mongo/util:buildinfo",
|
"//src/mongo/util:buildinfo",
|
||||||
"//src/mongo/util/concurrency:spin_lock",
|
"//src/mongo/util/concurrency:spin_lock",
|
||||||
"//src/third_party/mozjs",
|
"//src/third_party/mozjs",
|
||||||
|
|||||||
86
src/mongo/scripting/mozjs/shell/internal_module_registry.cpp
Normal file
86
src/mongo/scripting/mozjs/shell/internal_module_registry.cpp
Normal 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
|
||||||
80
src/mongo/scripting/mozjs/shell/internal_module_registry.h
Normal file
80
src/mongo/scripting/mozjs/shell/internal_module_registry.h
Normal 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
|
||||||
@ -41,6 +41,7 @@
|
|||||||
#include "mongo/logv2/log.h"
|
#include "mongo/logv2/log.h"
|
||||||
#include "mongo/scripting/mongo_path_util.h"
|
#include "mongo/scripting/mongo_path_util.h"
|
||||||
#include "mongo/scripting/mozjs/shell/implscope.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/scripting/mozjs/shell/module_loader.h"
|
||||||
#include "mongo/util/file.h"
|
#include "mongo/util/file.h"
|
||||||
|
|
||||||
@ -53,6 +54,7 @@
|
|||||||
|
|
||||||
#include <boost/none.hpp>
|
#include <boost/none.hpp>
|
||||||
#include <boost/optional/optional.hpp>
|
#include <boost/optional/optional.hpp>
|
||||||
|
#include <js/CallArgs.h>
|
||||||
#include <js/CharacterEncoding.h>
|
#include <js/CharacterEncoding.h>
|
||||||
#include <js/CompileOptions.h>
|
#include <js/CompileOptions.h>
|
||||||
#include <js/Context.h>
|
#include <js/Context.h>
|
||||||
@ -63,6 +65,7 @@
|
|||||||
#include <js/PropertyAndElement.h>
|
#include <js/PropertyAndElement.h>
|
||||||
#include <js/PropertyDescriptor.h>
|
#include <js/PropertyDescriptor.h>
|
||||||
#include <js/RootingAPI.h>
|
#include <js/RootingAPI.h>
|
||||||
|
#include <js/ScriptPrivate.h>
|
||||||
#include <js/String.h>
|
#include <js/String.h>
|
||||||
#include <js/TypeDecls.h>
|
#include <js/TypeDecls.h>
|
||||||
#include <js/Utility.h>
|
#include <js/Utility.h>
|
||||||
@ -75,6 +78,143 @@
|
|||||||
|
|
||||||
namespace mongo {
|
namespace mongo {
|
||||||
namespace mozjs {
|
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) {
|
bool ModuleLoader::init(JSContext* cx, const std::string& loadPath) {
|
||||||
_baseUrl = resolveBaseUrl(cx, loadPath);
|
_baseUrl = resolveBaseUrl(cx, loadPath);
|
||||||
@ -92,7 +232,17 @@ bool ModuleLoader::init(JSContext* cx, const std::string& loadPath) {
|
|||||||
JSRuntime* rt = JS_GetRuntime(cx);
|
JSRuntime* rt = JS_GetRuntime(cx);
|
||||||
JS::SetModuleResolveHook(rt, ModuleLoader::moduleResolveHook);
|
JS::SetModuleResolveHook(rt, ModuleLoader::moduleResolveHook);
|
||||||
JS::SetModuleDynamicImportHook(rt, ModuleLoader::dynamicModuleImportHook);
|
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) {
|
JSObject* ModuleLoader::loadRootModuleFromPath(JSContext* cx, const std::string& path) {
|
||||||
@ -131,6 +281,33 @@ JSObject* ModuleLoader::loadRootModule(JSContext* cx,
|
|||||||
return resolveImportedModule(cx, referencingPrivate, moduleRequest);
|
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
|
// static
|
||||||
JSObject* ModuleLoader::moduleResolveHook(JSContext* cx,
|
JSObject* ModuleLoader::moduleResolveHook(JSContext* cx,
|
||||||
JS::HandleValue referencingPrivate,
|
JS::HandleValue referencingPrivate,
|
||||||
@ -235,7 +412,18 @@ JSString* ModuleLoader::resolveAndNormalize(JSContext* cx,
|
|||||||
return nullptr;
|
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);
|
JS::Rooted<JSString*> path(cx, specifierString);
|
||||||
if (!path) {
|
if (!path) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -248,38 +436,32 @@ JSString* ModuleLoader::resolveAndNormalize(JSContext* cx,
|
|||||||
return specifierString;
|
return specifierString;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if it has a source
|
JS::UniqueChars specifierChars = JS_EncodeStringToUTF8(cx, specifierString);
|
||||||
bool hasSource;
|
uassert(ErrorCodes::JSInterpreterFailure,
|
||||||
JS::RootedObject referencingInfoObject(cx, &referencingInfo.toObject());
|
"Failed to UTF-8 encode module specifier",
|
||||||
if (!JS_HasProperty(cx, referencingInfoObject, "source", &hasSource)) {
|
specifierChars);
|
||||||
return nullptr;
|
|
||||||
}
|
// STD modules are identified by module specifier and don't map to filesystem paths.
|
||||||
if (hasSource) {
|
if (startsWithPrefix(specifierChars.get(), kStdModulePrefix)) {
|
||||||
return specifierString;
|
return specifierString;
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise try to read content from the file system
|
|
||||||
|
|
||||||
JS::RootedString refPath(cx);
|
JS::RootedString refPath(cx);
|
||||||
if (!getScriptPath(cx, referencingInfo, &refPath)) {
|
if (!getScriptPath(cx, referencingInfo, &refPath)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!refPath) {
|
if (!refPath) {
|
||||||
JS_ReportErrorASCII(cx, "No path set for referencing module");
|
JS_ReportErrorASCII(cx, "No path set for referencing module");
|
||||||
return nullptr;
|
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);
|
JS::UniqueChars refPathChars = JS_EncodeStringToUTF8(cx, refPath);
|
||||||
uassert(ErrorCodes::JSInterpreterFailure,
|
uassert(ErrorCodes::JSInterpreterFailure,
|
||||||
"Failed to UTF-8 encode referencing module path",
|
"Failed to UTF-8 encode referencing module path",
|
||||||
refPathChars);
|
refPathChars);
|
||||||
|
|
||||||
|
// otherwise try to read content from the file system
|
||||||
|
boost::filesystem::path specifierPath(specifierChars.get());
|
||||||
boost::filesystem::path refAbsPath(refPathChars.get());
|
boost::filesystem::path refAbsPath(refPathChars.get());
|
||||||
|
|
||||||
if (is_directory(specifierPath)) {
|
if (is_directory(specifierPath)) {
|
||||||
@ -373,16 +555,18 @@ JSObject* ModuleLoader::loadAndParse(JSContext* cx,
|
|||||||
return module;
|
return module;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JS::RootedString source(cx, fetchSource(cx, path, referencingPrivate));
|
||||||
|
if (!source) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
JS::UniqueChars filename = JS_EncodeStringToLatin1(cx, path);
|
JS::UniqueChars filename = JS_EncodeStringToLatin1(cx, path);
|
||||||
if (!filename) {
|
if (!filename) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
JS::CompileOptions options(cx);
|
JS::RootedObject info(cx, createScriptPrivateInfo(cx, path));
|
||||||
options.setFileAndLine(filename.get(), 1);
|
if (!info) {
|
||||||
|
|
||||||
JS::RootedString source(cx, fetchSource(cx, path, referencingPrivate));
|
|
||||||
if (!source) {
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,16 +581,13 @@ JSObject* ModuleLoader::loadAndParse(JSContext* cx,
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JS::CompileOptions options(cx);
|
||||||
|
options.setFileAndLine(filename.get(), 1);
|
||||||
module = JS::CompileModule(cx, options, srcBuf);
|
module = JS::CompileModule(cx, options, srcBuf);
|
||||||
if (!module) {
|
if (!module) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
JS::RootedObject info(cx, createScriptPrivateInfo(cx, path));
|
|
||||||
if (!info) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
JS::SetModulePrivate(module, JS::ObjectValue(*info));
|
JS::SetModulePrivate(module, JS::ObjectValue(*info));
|
||||||
|
|
||||||
if (!addModuleToRegistry(cx, path, module)) {
|
if (!addModuleToRegistry(cx, path, module)) {
|
||||||
@ -437,24 +618,12 @@ JSString* ModuleLoader::fetchSource(JSContext* cx,
|
|||||||
return fileAsString(cx, resolvedPath);
|
return fileAsString(cx, resolvedPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum GlobalAppSlot { GlobalAppSlotModuleRegistry, GlobalAppSlotCount };
|
|
||||||
JSObject* ModuleLoader::getOrCreateModuleRegistry(JSContext* cx) {
|
JSObject* ModuleLoader::getOrCreateModuleRegistry(JSContext* cx) {
|
||||||
JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
|
JS::RootedObject registry(cx);
|
||||||
if (!global) {
|
if (!getOrCreateGlobalMapInSlot(cx, GlobalAppSlotModuleRegistry, ®istry)) {
|
||||||
return nullptr;
|
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;
|
return registry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -89,6 +89,7 @@ private:
|
|||||||
JS::HandleValue referencingInfo);
|
JS::HandleValue referencingInfo);
|
||||||
JSObject* getOrCreateModuleRegistry(JSContext* cx);
|
JSObject* getOrCreateModuleRegistry(JSContext* cx);
|
||||||
JSString* fetchSource(JSContext* cx, JS::HandleString path, JS::HandleValue referencingPrivate);
|
JSString* fetchSource(JSContext* cx, JS::HandleString path, JS::HandleValue referencingPrivate);
|
||||||
|
bool preloadInternalModules(JSContext* cx);
|
||||||
bool getScriptPath(JSContext* cx,
|
bool getScriptPath(JSContext* cx,
|
||||||
JS::HandleValue privateValue,
|
JS::HandleValue privateValue,
|
||||||
JS::MutableHandleString pathOut);
|
JS::MutableHandleString pathOut);
|
||||||
|
|||||||
@ -290,16 +290,28 @@ MONGOJS_CPP_JSFILES = [
|
|||||||
":error_codes_js",
|
":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.
|
# into the native binary via bytecode upon startup.
|
||||||
|
MONGOJS_STD_JS_MODULES = [
|
||||||
|
("std:performance", "std/performance.js"),
|
||||||
|
]
|
||||||
|
|
||||||
render_template(
|
render_template(
|
||||||
name = "mongojs_cpp",
|
name = "mongojs_cpp",
|
||||||
srcs = MONGOJS_CPP_JSFILES,
|
srcs = MONGOJS_CPP_JSFILES + [file for _, file in MONGOJS_STD_JS_MODULES],
|
||||||
cmd = [
|
cmd = [
|
||||||
"$(location mongojs.cpp)",
|
"$(location mongojs.cpp)",
|
||||||
] + [
|
] + [
|
||||||
"$(location {})".format(file)
|
"$(location {})".format(file)
|
||||||
for file in MONGOJS_CPP_JSFILES
|
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",
|
output = "mongojs.cpp",
|
||||||
python_file = "//buildscripts:jstoh.py",
|
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(
|
mongo_cc_library(
|
||||||
name = "shell_options_register",
|
name = "shell_options_register",
|
||||||
srcs = [
|
srcs = [
|
||||||
|
|||||||
90
src/mongo/shell/std/README.md
Normal file
90
src/mongo/shell/std/README.md
Normal 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.
|
||||||
72
src/mongo/shell/std/performance.cpp
Normal file
72
src/mongo/shell/std/performance.cpp
Normal 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
11
src/mongo/shell/std/performance.d.ts
vendored
Normal 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;
|
||||||
7
src/mongo/shell/std/performance.js
Normal file
7
src/mongo/shell/std/performance.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
const {now: internalNow} = internalModule("performance");
|
||||||
|
|
||||||
|
export const performance = Object.freeze({
|
||||||
|
now() {
|
||||||
|
return internalNow();
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user