diff --git a/benchmark/util/get-callsite.js b/benchmark/util/get-callsite.js new file mode 100644 index 00000000000000..9270f841a243d3 --- /dev/null +++ b/benchmark/util/get-callsite.js @@ -0,0 +1,65 @@ +'use strict'; + +const common = require('../common'); +const { getCallSite } = require('node:util'); +const assert = require('node:assert'); + +const bench = common.createBenchmark(main, { + n: [1e6], + method: ['ErrorCallSite', 'ErrorCallSiteSerialized', 'CPP'], +}); + +function ErrorGetCallSite() { + const originalStackFormatter = Error.prepareStackTrace; + Error.prepareStackTrace = (_err, stack) => { + if (stack && stack.length > 1) { + // Remove node:util + return stack.slice(1); + } + return stack; + }; + const err = new Error(); + // With the V8 Error API, the stack is not formatted until it is accessed + err.stack; // eslint-disable-line no-unused-expressions + Error.prepareStackTrace = originalStackFormatter; + return err.stack; +} + +function ErrorCallSiteSerialized() { + const callsite = ErrorGetCallSite(); + const serialized = []; + for (let i = 0; i < callsite.length; ++i) { + serialized.push({ + functionName: callsite[i].getFunctionName(), + scriptName: callsite[i].getFileName(), + lineNumber: callsite[i].getLineNumber(), + column: callsite[i].getColumnNumber(), + }); + } + return serialized; +} + +function main({ n, method }) { + let fn; + switch (method) { + case 'ErrorCallSite': + fn = ErrorGetCallSite; + break; + case 'ErrorCallSiteSerialized': + fn = ErrorCallSiteSerialized; + break; + case 'CPP': + fn = getCallSite; + break; + } + let lastStack = {}; + + bench.start(); + for (let i = 0; i < n; i++) { + const stack = fn(); + lastStack = stack; + } + bench.end(n); + // Attempt to avoid dead-code elimination + assert.ok(lastStack); +} diff --git a/doc/api/util.md b/doc/api/util.md index ee6a91af5159d4..6c922bc0154343 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -364,6 +364,63 @@ util.formatWithOptions({ colors: true }, 'See object %O', { foo: 42 }); // when printed to a terminal. ``` +## `util.getCallSite(frames)` + +> Stability: 1.1 - Active development + + + +* `frames` {number} Number of frames returned in the stacktrace. + **Default:** `10`. Allowable range is between 1 and 200. +* Returns: {Object\[]} An array of stacktrace objects + * `functionName` {string} Returns the name of the function associated with this stack frame. + * `scriptName` {string} Returns the name of the resource that contains the script for the + function for this StackFrame. + * `lineNumber` {number} Returns the number, 1-based, of the line for the associate function call. + * `column` {number} Returns the 1-based column offset on the line for the associated function call. + +Returns an array of stacktrace objects containing the stack of +the caller function. + +```js +const util = require('node:util'); + +function exampleFunction() { + const callSites = util.getCallSite(); + + console.log('Call Sites:'); + callSites.forEach((callSite, index) => { + console.log(`CallSite ${index + 1}:`); + console.log(`Function Name: ${callSite.functionName}`); + console.log(`Script Name: ${callSite.scriptName}`); + console.log(`Line Number: ${callSite.lineNumer}`); + console.log(`Column Number: ${callSite.column}`); + }); + // CallSite 1: + // Function Name: exampleFunction + // Script Name: /home/example.js + // Line Number: 5 + // Column Number: 26 + + // CallSite 2: + // Function Name: anotherFunction + // Script Name: /home/example.js + // Line Number: 22 + // Column Number: 3 + + // ... +} + +// A function to simulate another stack layer +function anotherFunction() { + exampleFunction(); +} + +anotherFunction(); +``` + ## `util.getSystemErrorName(err)`