Skip to content

Commit 2368914

Browse files
authored
Track require() source locations and additional metadata (#10)
- After running `trackRequires(true, { trackSource: true });`, the `require()` traces will now have an `args` object which contains the source location which can be rendered as a table in the <https://ui.perfetto.dev> view. - Metadata for other measurements can be passed using the <https://developer.mozilla.org/en-US/docs/Web/API/Performance/measure#detail> property and it would be set as the `args` object on the trace object. Signed-off-by: Darshan Sen <raisinten@gmail.com>
1 parent d19de94 commit 2368914

File tree

7 files changed

+98
-10
lines changed

7 files changed

+98
-10
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ process.on("beforeExit", () => {
4646
});
4747

4848
// Enables tracking require() calls.
49-
trackRequires(true);
49+
trackRequires(true, { trackSource: true });
5050

5151
// The assert module takes milliseconds to load, so it would be distinctly
5252
// visible in the performance trace.

docs/api/README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,23 @@ Call `traceEvents.getEvents()` to get the PerformanceEntry objects in the [Trace
2020
"ph": "X",
2121
"pid": 1,
2222
"ts": 34509,
23-
"dur": 1004141
23+
"dur": 1004141,
24+
"args": null
2425
},
2526
{
2627
"name": "A",
2728
"cat": "measure",
2829
"ph": "X",
2930
"pid": 1,
3031
"ts": 33837,
31-
"dur": 2002098
32+
"dur": 2002098,
33+
"args": { "foo": "bar" }
3234
}
3335
]
3436
```
3537

36-
## `trackRequires(bool)` (only available in [CommonJS](https://nodejs.org/api/modules.html#modules-commonjs-modules))
38+
## `trackRequires(switch, options)` (only available in [CommonJS](https://nodejs.org/api/modules.html#modules-commonjs-modules))
3739

38-
Call `trackRequires(true)` to enable tracking `require()`s and call `trackRequires(false)` to disable tracking `require()`s.
40+
- `switch` (**bool**) - Call `trackRequires(true)` to enable tracking `require()`s and call `trackRequires(false)` to disable tracking `require()`s.
41+
- `options` (**object**)
42+
- `trackSource` (**bool**) - Call `trackRequires(true, { trackSource: true })` to enable tracking `require()`s and also capture the source locations. This creates a throw-away error object and parses the `stack` property, so it introduces an overhead.

docs/examples/tracing-requires/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Before the process exits, the `TraceEvents` object is destroyed and the trace ev
88

99
https://github.com/RaisinTen/perftrace/blob/7cb1ce34d19ed8816e15094b3ef4fd03bb157c76/docs/examples/tracing-requires/index.js#L6-L10
1010

11-
The `require()` calls can be tracked by passing `true` to `trackRequires()` like it is being done in:
11+
The `require()` calls can be tracked by calling `trackRequires()` like it is being done in:
1212

1313
https://github.com/RaisinTen/perftrace/blob/7cb1ce34d19ed8816e15094b3ef4fd03bb157c76/docs/examples/tracing-requires/index.js#L12
1414

docs/examples/tracing-requires/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ process.on("beforeExit", () => {
99
writeFileSync("events.json", JSON.stringify(events));
1010
});
1111

12-
trackRequires(true);
12+
trackRequires(true, { trackSource: true });
1313

1414
// Express Hello world example
1515
// Refs: https://expressjs.com/en/starter/hello-world.html
87.7 KB
Loading

index.cjs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
const { PerformanceObserver, performance } = require('node:perf_hooks');
22

3+
function getSource() {
4+
const error = new Error();
5+
const stack = error.stack.split("\n");
6+
// Ignore the first 3 elements of the error stack to get the relevant source:
7+
// Error
8+
// at getSource (.../perftrace/index.cjs:...:...)
9+
// at Module.require (.../perftrace/index.cjs:...:...)
10+
// ...
11+
const source = stack.slice(3);
12+
return source;
13+
}
14+
315
class TraceEvents {
416
_eventObjects;
517
_observer;
@@ -16,6 +28,7 @@ class TraceEvents {
1628
pid: 1,
1729
ts: Math.round(measure.startTime * 1000),
1830
dur: Math.round(measure.duration * 1000),
31+
args: measure.detail,
1932
});
2033
});
2134
});
@@ -37,12 +50,21 @@ class TraceEvents {
3750
const Module = require('module');
3851
const originalRequire = Module.prototype.require;
3952

40-
function trackRequires(shouldTrackRequires) {
53+
function trackRequires(shouldTrackRequires, options) {
4154
if (shouldTrackRequires) {
4255
Module.prototype.require = function () {
56+
let source = null;
57+
if (options?.trackSource) {
58+
source = getSource();
59+
}
4360
performance.mark(`require("${arguments[0]}")`);
4461
const ret = originalRequire.apply(this, arguments);
45-
performance.measure(`require("${arguments[0]}")`, `require("${arguments[0]}")`);
62+
performance.measure(
63+
`require("${arguments[0]}")`,
64+
{
65+
detail: source,
66+
start: `require("${arguments[0]}")`,
67+
});
4668
return ret;
4769
};
4870
} else {

test/node-test.cjs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { ok } = require("node:assert");
1+
const { ok, deepStrictEqual, strictEqual } = require("node:assert");
22
const { TraceEvents, trackRequires } = require("../index.cjs");
33
const { after, before, describe, test } = require("node:test");
44
const { performance } = require("node:perf_hooks");
@@ -73,6 +73,10 @@ describe("track requires", async () => {
7373
traceEvents = new TraceEvents();
7474
trackRequires(true);
7575
require("node:tls");
76+
trackRequires(true, { trackSource: true });
77+
require("node:os");
78+
trackRequires(true, { trackSource: false });
79+
require("node:async_hooks");
7680
trackRequires(false);
7781
require("node:net");
7882

@@ -83,10 +87,68 @@ describe("track requires", async () => {
8387
const events = traceEvents.getEvents();
8488
const requireTls = events.find((element) => element.name === `require("node:tls")`);
8589
ok(requireTls);
90+
const requireOs = events.find((element) => element.name === `require("node:os")`);
91+
ok(requireOs);
92+
const requireAsyncHooks = events.find((element) => element.name === `require("node:async_hooks")`);
93+
ok(requireAsyncHooks);
8694
const requireNet = events.find((element) => element.name === `require("node:net")`);
8795
ok(!requireNet);
8896
});
8997

98+
test("source location", () => {
99+
const events = traceEvents.getEvents();
100+
101+
const requireTls = events.find((element) => element.name === `require("node:tls")`);
102+
ok(requireTls);
103+
strictEqual(requireTls.args, null);
104+
105+
const requireOs = events.find((element) => element.name === `require("node:os")`);
106+
ok(requireOs);
107+
ok(requireOs.args);
108+
ok(requireOs.args.length > 5);
109+
ok(requireOs.args[1].includes(__filename));
110+
111+
const requireAsyncHooks = events.find((element) => element.name === `require("node:async_hooks")`);
112+
ok(requireAsyncHooks);
113+
strictEqual(requireAsyncHooks.args, null);
114+
});
115+
116+
after(() => {
117+
traceEvents.destroy();
118+
});
119+
});
120+
121+
describe("metadata", async () => {
122+
let traceEvents;
123+
const metadata = { foo: "bar", number: 2 };
124+
125+
await before(async () => {
126+
return new Promise((resolve, reject) => {
127+
setTimeout(() => {
128+
resolve();
129+
}, 2000);
130+
131+
traceEvents = new TraceEvents();
132+
133+
performance.mark("UwU");
134+
setTimeout(() => {
135+
performance.measure(
136+
"UwU",
137+
{
138+
detail: metadata,
139+
start: "UwU",
140+
});
141+
}, 1000);
142+
});
143+
});
144+
145+
test("check metadata", () => {
146+
const events = traceEvents.getEvents();
147+
const UwU = events.find((element) => element.name === "UwU");
148+
ok(UwU);
149+
deepStrictEqual(UwU.args, metadata);
150+
});
151+
90152
after(() => {
91153
traceEvents.destroy();
92154
});

0 commit comments

Comments
 (0)