diff options
author | 2022-12-13 20:47:51 -0800 | |
---|---|---|
committer | 2022-12-13 20:47:51 -0800 | |
commit | f61d9e340d35f982e8ad86b705af46464c7f1d2a (patch) | |
tree | eb89549abd0be7f7c63ce8c4eee8127dcca81acf | |
parent | 47a2548cbfbb977e9b1f0c749bd83d47b4c4596b (diff) | |
download | bun-f61d9e340d35f982e8ad86b705af46464c7f1d2a.tar.gz bun-f61d9e340d35f982e8ad86b705af46464c7f1d2a.tar.zst bun-f61d9e340d35f982e8ad86b705af46464c7f1d2a.zip |
[bun:jsc] Introduce `profile` function
-rw-r--r-- | packages/bun-types/jsc.d.ts | 108 | ||||
-rw-r--r-- | src/bun.js/bindings/BunJSCModule.cpp | 53 | ||||
-rw-r--r-- | src/bun.js/bun-jsc.exports.js | 1 |
3 files changed, 162 insertions, 0 deletions
diff --git a/packages/bun-types/jsc.d.ts b/packages/bun-types/jsc.d.ts index 30c7caebd..b909d0348 100644 --- a/packages/bun-types/jsc.d.ts +++ b/packages/bun-types/jsc.d.ts @@ -37,6 +37,114 @@ declare module "bun:jsc" { export function drainMicrotasks(): void; /** + * Run JavaScriptCore's sampling profiler for a particular function + * + * This is pretty low-level. + * + * Things to know: + * - LLint means "Low Level Interpreter", which is the interpreter that runs before any JIT compilation + * - Baseline is the first JIT compilation tier. It's the least optimized, but the fastest to compile + * - DFG means "Data Flow Graph", which is the second JIT compilation tier. It has some optimizations, but is slower to compile + * - FTL means "Faster Than Light", which is the third JIT compilation tier. It has the most optimizations, but is the slowest to compile + */ + export function profile( + callback: CallableFunction, + sampleInterval?: number, + ): { + /** + * A formatted summary of the top functions + * + * Example output: + * ```js + * + * Sampling rate: 100.000000 microseconds. Total samples: 6858 + * Top functions as <numSamples 'functionName#hash:sourceID'> + * 2948 '#<nil>:8' + * 393 'visit#<nil>:8' + * 263 'push#<nil>:8' + * 164 'scan_ref_scoped#<nil>:8' + * 164 'walk#<nil>:8' + * 144 'pop#<nil>:8' + * 107 'extract_candidates#<nil>:8' + * 94 'get#<nil>:8' + * 82 'Function#<nil>:4294967295' + * 79 'set#<nil>:8' + * 67 'forEach#<nil>:5' + * 58 'collapse#<nil>:8' + * ``` + */ + functions: string; + /** + * A formatted summary of the top bytecodes + * + * Example output: + * ```js + * Tier breakdown: + * ----------------------------------- + * LLInt: 106 (1.545640%) + * Baseline: 2355 (34.339458%) + * DFG: 3290 (47.973170%) + * FTL: 833 (12.146398%) + * js builtin: 132 (1.924759%) + * Wasm: 0 (0.000000%) + * Host: 111 (1.618548%) + * RegExp: 15 (0.218723%) + * C/C++: 0 (0.000000%) + * Unknown Executable: 148 (2.158064%) + * + * + * Hottest bytecodes as <numSamples 'functionName#hash:JITType:bytecodeIndex'> + * 273 'visit#<nil>:DFG:bc#63' + * 121 'walk#<nil>:DFG:bc#7' + * 119 '#<nil>:Baseline:bc#1' + * 82 'Function#<nil>:None:<nil>' + * 66 '#<nil>:DFG:bc#11' + * 65 '#<nil>:DFG:bc#33' + * 58 '#<nil>:Baseline:bc#7' + * 53 '#<nil>:Baseline:bc#23' + * 50 'forEach#<nil>:DFG:bc#83' + * 49 'pop#<nil>:FTL:bc#65' + * 47 '#<nil>:DFG:bc#99' + * 45 '#<nil>:DFG:bc#16' + * 44 '#<nil>:DFG:bc#7' + * 44 '#<nil>:Baseline:bc#30' + * 44 'push#<nil>:FTL:bc#214' + * 41 '#<nil>:DFG:bc#50' + * 39 'get#<nil>:DFG:bc#27' + * 39 '#<nil>:Baseline:bc#0' + * 36 '#<nil>:DFG:bc#27' + * 36 'Dictionary#<nil>:DFG:bc#41' + * 36 'visit#<nil>:DFG:bc#81' + * 36 'get#<nil>:FTL:bc#11' + * 32 'push#<nil>:FTL:bc#49' + * 31 '#<nil>:DFG:bc#76' + * 31 '#<nil>:DFG:bc#10' + * 31 '#<nil>:DFG:bc#73' + * 29 'set#<nil>:DFG:bc#28' + * 28 'in_boolean_context#<nil>:DFG:bc#104' + * 28 '#<nil>:Baseline:<nil>' + * 28 'regExpSplitFast#<nil>:None:<nil>' + * 26 'visit#<nil>:DFG:bc#95' + * 26 'pop#<nil>:FTL:bc#120' + * 25 '#<nil>:DFG:bc#23' + * 25 'push#<nil>:FTL:bc#152' + * 24 'push#<nil>:FTL:bc#262' + * 24 '#<nil>:FTL:bc#10' + * 23 'is_identifier_char#<nil>:DFG:bc#22' + * 23 'visit#<nil>:DFG:bc#22' + * 22 '#<nil>:FTL:bc#27' + * 22 'indexOf#<nil>:None:<nil>' + * ``` + */ + bytecodes: string; + + /** + * Stack traces of the top functions + */ + stackTraces: string[]; + }; + + /** * This returns objects which native code has explicitly protected from being * garbage collected * diff --git a/src/bun.js/bindings/BunJSCModule.cpp b/src/bun.js/bindings/BunJSCModule.cpp index fd92b817e..384882883 100644 --- a/src/bun.js/bindings/BunJSCModule.cpp +++ b/src/bun.js/bindings/BunJSCModule.cpp @@ -421,6 +421,58 @@ JSC_DEFINE_HOST_FUNCTION(functionDrainMicrotasks, (JSGlobalObject * globalObject return JSValue::encode(jsUndefined()); } +JSC_DEFINE_HOST_FUNCTION(functionRunProfiler, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + JSC::SamplingProfiler& samplingProfiler = vm.ensureSamplingProfiler(WTF::Stopwatch::create()); + + JSC::JSValue callbackValue = callFrame->argument(0); + auto throwScope = DECLARE_THROW_SCOPE(vm); + if (callbackValue.isUndefinedOrNull() || !callbackValue.isCallable()) { + throwException(globalObject, throwScope, createTypeError(globalObject, "First argument must be a function."_s)); + return JSValue::encode(JSValue {}); + } + + JSC::JSFunction* function = jsCast<JSC::JSFunction*>(callbackValue); + + JSC::JSValue sampleValue = callFrame->argument(1); + if (sampleValue.isNumber()) { + unsigned sampleInterval = sampleValue.toUInt32(globalObject); + samplingProfiler.setTimingInterval(Seconds::fromMicroseconds(sampleInterval)); + } + + JSC::CallData callData = JSC::getCallData(function); + MarkedArgumentBuffer args; + + samplingProfiler.noticeCurrentThreadAsJSCExecutionThread(); + samplingProfiler.start(); + JSC::call(globalObject, function, callData, JSC::jsUndefined(), args); + samplingProfiler.pause(); + if (throwScope.exception()) { + samplingProfiler.shutdown(); + samplingProfiler.clearData(); + return JSValue::encode(JSValue {}); + } + + StringPrintStream topFunctions; + samplingProfiler.reportTopFunctions(topFunctions); + + StringPrintStream byteCodes; + samplingProfiler.reportTopBytecodes(byteCodes); + + JSValue stackTraces = JSONParse(globalObject, samplingProfiler.stackTracesAsJSON()); + + samplingProfiler.shutdown(); + samplingProfiler.clearData(); + + JSObject* result = constructEmptyObject(globalObject, globalObject->objectPrototype(), 3); + result->putDirect(vm, Identifier::fromString(vm, "functions"_s), jsString(vm, topFunctions.toString())); + result->putDirect(vm, Identifier::fromString(vm, "bytecodes"_s), jsString(vm, byteCodes.toString())); + result->putDirect(vm, Identifier::fromString(vm, "stackTraces"_s), stackTraces); + + return JSValue::encode(result); +} + JSC_DECLARE_HOST_FUNCTION(functionGenerateHeapSnapshotForDebugging); JSC_DEFINE_HOST_FUNCTION(functionGenerateHeapSnapshotForDebugging, (JSGlobalObject * globalObject, CallFrame*)) { @@ -476,6 +528,7 @@ JSC::JSObject* createJSCModule(JSC::JSGlobalObject* globalObject) object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "totalCompileTime"_s), 1, functionTotalCompileTime, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "getProtectedObjects"_s), 1, functionGetProtectedObjects, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "generateHeapSnapshotForDebugging"_s), 0, functionGenerateHeapSnapshotForDebugging, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); + object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "profile"_s), 0, functionRunProfiler, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); } return object; diff --git a/src/bun.js/bun-jsc.exports.js b/src/bun.js/bun-jsc.exports.js index ee758a3b3..b35a5e7a6 100644 --- a/src/bun.js/bun-jsc.exports.js +++ b/src/bun.js/bun-jsc.exports.js @@ -27,4 +27,5 @@ export const totalCompileTime = jsc.totalCompileTime; export const getProtectedObjects = jsc.getProtectedObjects; export const generateHeapSnapshotForDebugging = jsc.generateHeapSnapshotForDebugging; +export const profile = jsc.profile; export default jsc; |