perfstack 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/LICENSE +15 -0
- package/README.md +120 -0
- package/dist/index.cjs +491 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +190 -0
- package/dist/index.d.ts +190 -0
- package/dist/index.js +457 -0
- package/dist/index.js.map +1 -0
- package/package.json +80 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
// src/histogram.ts
|
|
2
|
+
var Histogram = class {
|
|
3
|
+
samples = [];
|
|
4
|
+
max;
|
|
5
|
+
constructor(max = 1e3) {
|
|
6
|
+
this.max = max;
|
|
7
|
+
}
|
|
8
|
+
record(value) {
|
|
9
|
+
this.samples.push(value);
|
|
10
|
+
if (this.samples.length > this.max) {
|
|
11
|
+
this.samples.shift();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
summary() {
|
|
15
|
+
const count = this.samples.length;
|
|
16
|
+
if (count === 0) {
|
|
17
|
+
return { count: 0, min: 0, max: 0, avg: 0, p50: 0, p90: 0, p95: 0, p99: 0 };
|
|
18
|
+
}
|
|
19
|
+
const sorted = [...this.samples].sort((a, b) => a - b);
|
|
20
|
+
const sum = sorted.reduce((s, v) => s + v, 0);
|
|
21
|
+
return {
|
|
22
|
+
count,
|
|
23
|
+
min: sorted[0],
|
|
24
|
+
max: sorted[sorted.length - 1],
|
|
25
|
+
avg: round(sum / count),
|
|
26
|
+
p50: percentile(sorted, 0.5),
|
|
27
|
+
p90: percentile(sorted, 0.9),
|
|
28
|
+
p95: percentile(sorted, 0.95),
|
|
29
|
+
p99: percentile(sorted, 0.99)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
reset() {
|
|
33
|
+
this.samples = [];
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
function percentile(sortedAscending, p) {
|
|
37
|
+
if (sortedAscending.length === 0) return 0;
|
|
38
|
+
const idx = Math.min(sortedAscending.length - 1, Math.floor(p * sortedAscending.length));
|
|
39
|
+
return round(sortedAscending[idx]);
|
|
40
|
+
}
|
|
41
|
+
function round(n) {
|
|
42
|
+
return Math.round(n * 100) / 100;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/tracer.ts
|
|
46
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
47
|
+
import { randomBytes } from "crypto";
|
|
48
|
+
var Tracer = class {
|
|
49
|
+
storage = new AsyncLocalStorage();
|
|
50
|
+
spans = [];
|
|
51
|
+
max;
|
|
52
|
+
constructor(options = {}) {
|
|
53
|
+
this.max = options.maxSpans ?? 500;
|
|
54
|
+
}
|
|
55
|
+
start(name, kind = "custom", metadata = {}) {
|
|
56
|
+
const startedAt = Date.now();
|
|
57
|
+
const id = `s_${randomBytes(6).toString("hex")}`;
|
|
58
|
+
const parent = this.storage.getStore();
|
|
59
|
+
const traceId = parent?.traceId ?? `t_${randomBytes(8).toString("hex")}`;
|
|
60
|
+
const parentId = parent?.spanId;
|
|
61
|
+
return {
|
|
62
|
+
end: (info = {}) => {
|
|
63
|
+
const status = info.error ? "error" : info.status ?? "ok";
|
|
64
|
+
const errorMsg = info.error instanceof Error ? info.error.message : info.error ? String(info.error) : void 0;
|
|
65
|
+
const span = {
|
|
66
|
+
id,
|
|
67
|
+
parentId,
|
|
68
|
+
traceId,
|
|
69
|
+
name,
|
|
70
|
+
kind,
|
|
71
|
+
startedAt,
|
|
72
|
+
durationMs: Date.now() - startedAt,
|
|
73
|
+
metadata: { ...metadata, ...info.extra ?? {} },
|
|
74
|
+
status,
|
|
75
|
+
error: errorMsg
|
|
76
|
+
};
|
|
77
|
+
this.push(span);
|
|
78
|
+
return span;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async withSpan(name, fn, options = {}) {
|
|
83
|
+
const startedAt = Date.now();
|
|
84
|
+
const parent = this.storage.getStore();
|
|
85
|
+
const traceId = parent?.traceId ?? `t_${randomBytes(8).toString("hex")}`;
|
|
86
|
+
const spanId = `s_${randomBytes(6).toString("hex")}`;
|
|
87
|
+
return this.storage.run({ traceId, spanId }, async () => {
|
|
88
|
+
try {
|
|
89
|
+
const result = await fn();
|
|
90
|
+
this.push({
|
|
91
|
+
id: spanId,
|
|
92
|
+
parentId: parent?.spanId,
|
|
93
|
+
traceId,
|
|
94
|
+
name,
|
|
95
|
+
kind: options.kind ?? "custom",
|
|
96
|
+
startedAt,
|
|
97
|
+
durationMs: Date.now() - startedAt,
|
|
98
|
+
metadata: options.metadata ?? {},
|
|
99
|
+
status: "ok"
|
|
100
|
+
});
|
|
101
|
+
return result;
|
|
102
|
+
} catch (err) {
|
|
103
|
+
this.push({
|
|
104
|
+
id: spanId,
|
|
105
|
+
parentId: parent?.spanId,
|
|
106
|
+
traceId,
|
|
107
|
+
name,
|
|
108
|
+
kind: options.kind ?? "custom",
|
|
109
|
+
startedAt,
|
|
110
|
+
durationMs: Date.now() - startedAt,
|
|
111
|
+
metadata: options.metadata ?? {},
|
|
112
|
+
status: "error",
|
|
113
|
+
error: err instanceof Error ? err.message : String(err)
|
|
114
|
+
});
|
|
115
|
+
throw err;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
runInContext(traceId, fn) {
|
|
120
|
+
const spanId = `s_${randomBytes(6).toString("hex")}`;
|
|
121
|
+
return this.storage.run({ traceId, spanId }, fn);
|
|
122
|
+
}
|
|
123
|
+
currentTraceId() {
|
|
124
|
+
return this.storage.getStore()?.traceId;
|
|
125
|
+
}
|
|
126
|
+
recent(limit = 100) {
|
|
127
|
+
return this.spans.slice(-limit);
|
|
128
|
+
}
|
|
129
|
+
byTrace(traceId) {
|
|
130
|
+
return this.spans.filter((s) => s.traceId === traceId);
|
|
131
|
+
}
|
|
132
|
+
reset() {
|
|
133
|
+
this.spans = [];
|
|
134
|
+
}
|
|
135
|
+
push(span) {
|
|
136
|
+
this.spans.push(span);
|
|
137
|
+
if (this.spans.length > this.max) this.spans.shift();
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// src/profiler.ts
|
|
142
|
+
var Profiler = class {
|
|
143
|
+
startedAt;
|
|
144
|
+
httpHistogram = new Histogram();
|
|
145
|
+
queryHistogram = new Histogram();
|
|
146
|
+
routes = /* @__PURE__ */ new Map();
|
|
147
|
+
slowestQueries = [];
|
|
148
|
+
memorySamples = [];
|
|
149
|
+
peakRss = 0;
|
|
150
|
+
peakHeapUsed = 0;
|
|
151
|
+
totalRequests = 0;
|
|
152
|
+
totalErrors = 0;
|
|
153
|
+
totalQueries = 0;
|
|
154
|
+
memoryInterval;
|
|
155
|
+
tracer;
|
|
156
|
+
opts;
|
|
157
|
+
constructor(options = {}) {
|
|
158
|
+
this.startedAt = options.startedAt ?? Date.now();
|
|
159
|
+
this.opts = {
|
|
160
|
+
slowRequestThreshold: options.slowRequestThreshold ?? 500,
|
|
161
|
+
slowQueryThreshold: options.slowQueryThreshold ?? 200,
|
|
162
|
+
memorySampleIntervalMs: options.memorySampleIntervalMs ?? 5e3,
|
|
163
|
+
memorySamplesMax: options.memorySamplesMax ?? 120,
|
|
164
|
+
routeBucketsMax: options.routeBucketsMax ?? 200,
|
|
165
|
+
enableMemorySampling: options.enableMemorySampling ?? false
|
|
166
|
+
};
|
|
167
|
+
this.tracer = new Tracer();
|
|
168
|
+
if (this.opts.enableMemorySampling) this.startMemorySampling();
|
|
169
|
+
}
|
|
170
|
+
recordHttp(record) {
|
|
171
|
+
this.totalRequests += 1;
|
|
172
|
+
if (record.statusCode >= 500) this.totalErrors += 1;
|
|
173
|
+
this.httpHistogram.record(record.durationMs);
|
|
174
|
+
const key = `${record.method} ${record.path}`;
|
|
175
|
+
let bucket = this.routes.get(key);
|
|
176
|
+
if (!bucket) {
|
|
177
|
+
if (this.routes.size >= this.opts.routeBucketsMax) {
|
|
178
|
+
const oldest = this.routes.keys().next().value;
|
|
179
|
+
if (oldest !== void 0) this.routes.delete(oldest);
|
|
180
|
+
}
|
|
181
|
+
bucket = { method: record.method, path: record.path, histogram: new Histogram(), errors: 0 };
|
|
182
|
+
this.routes.set(key, bucket);
|
|
183
|
+
}
|
|
184
|
+
bucket.histogram.record(record.durationMs);
|
|
185
|
+
if (record.statusCode >= 500) bucket.errors += 1;
|
|
186
|
+
}
|
|
187
|
+
recordQuery(record) {
|
|
188
|
+
this.totalQueries += 1;
|
|
189
|
+
this.queryHistogram.record(record.durationMs);
|
|
190
|
+
if (record.durationMs >= this.opts.slowQueryThreshold) {
|
|
191
|
+
this.slowestQueries.push(record);
|
|
192
|
+
this.slowestQueries.sort((a, b) => b.durationMs - a.durationMs);
|
|
193
|
+
if (this.slowestQueries.length > 50) this.slowestQueries.length = 50;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
takeMemorySample() {
|
|
197
|
+
const m = process.memoryUsage();
|
|
198
|
+
const snap = {
|
|
199
|
+
takenAt: Date.now(),
|
|
200
|
+
rss: m.rss,
|
|
201
|
+
heapTotal: m.heapTotal,
|
|
202
|
+
heapUsed: m.heapUsed,
|
|
203
|
+
external: m.external,
|
|
204
|
+
arrayBuffers: m.arrayBuffers
|
|
205
|
+
};
|
|
206
|
+
this.memorySamples.push(snap);
|
|
207
|
+
if (this.memorySamples.length > this.opts.memorySamplesMax) this.memorySamples.shift();
|
|
208
|
+
if (snap.rss > this.peakRss) this.peakRss = snap.rss;
|
|
209
|
+
if (snap.heapUsed > this.peakHeapUsed) this.peakHeapUsed = snap.heapUsed;
|
|
210
|
+
return snap;
|
|
211
|
+
}
|
|
212
|
+
startMemorySampling() {
|
|
213
|
+
if (this.memoryInterval) return;
|
|
214
|
+
this.takeMemorySample();
|
|
215
|
+
const interval = setInterval(() => this.takeMemorySample(), this.opts.memorySampleIntervalMs);
|
|
216
|
+
interval.unref?.();
|
|
217
|
+
this.memoryInterval = interval;
|
|
218
|
+
}
|
|
219
|
+
stopMemorySampling() {
|
|
220
|
+
if (this.memoryInterval) {
|
|
221
|
+
clearInterval(this.memoryInterval);
|
|
222
|
+
this.memoryInterval = void 0;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
report() {
|
|
226
|
+
if (this.memorySamples.length === 0) this.takeMemorySample();
|
|
227
|
+
const routes = Array.from(this.routes.values()).map((bucket) => ({
|
|
228
|
+
method: bucket.method,
|
|
229
|
+
path: bucket.path,
|
|
230
|
+
errorCount: bucket.errors,
|
|
231
|
+
...bucket.histogram.summary()
|
|
232
|
+
}));
|
|
233
|
+
const slowestRoutes = [...routes].sort((a, b) => b.p95 - a.p95).slice(0, 10);
|
|
234
|
+
return {
|
|
235
|
+
uptimeMs: Date.now() - this.startedAt,
|
|
236
|
+
startedAt: this.startedAt,
|
|
237
|
+
takenAt: Date.now(),
|
|
238
|
+
totalRequests: this.totalRequests,
|
|
239
|
+
totalErrors: this.totalErrors,
|
|
240
|
+
totalQueries: this.totalQueries,
|
|
241
|
+
http: this.httpHistogram.summary(),
|
|
242
|
+
routes,
|
|
243
|
+
slowestRoutes,
|
|
244
|
+
queries: this.queryHistogram.summary(),
|
|
245
|
+
slowestQueries: [...this.slowestQueries],
|
|
246
|
+
memory: {
|
|
247
|
+
current: this.memorySamples.at(-1) ?? null,
|
|
248
|
+
samples: [...this.memorySamples],
|
|
249
|
+
peakRss: this.peakRss,
|
|
250
|
+
peakHeapUsed: this.peakHeapUsed
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
reset() {
|
|
255
|
+
this.httpHistogram.reset();
|
|
256
|
+
this.queryHistogram.reset();
|
|
257
|
+
this.routes.clear();
|
|
258
|
+
this.slowestQueries = [];
|
|
259
|
+
this.memorySamples = [];
|
|
260
|
+
this.peakRss = 0;
|
|
261
|
+
this.peakHeapUsed = 0;
|
|
262
|
+
this.totalRequests = 0;
|
|
263
|
+
this.totalErrors = 0;
|
|
264
|
+
this.totalQueries = 0;
|
|
265
|
+
this.tracer.reset();
|
|
266
|
+
}
|
|
267
|
+
isSlowRequest(durationMs) {
|
|
268
|
+
return durationMs >= this.opts.slowRequestThreshold;
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// src/express.ts
|
|
273
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
274
|
+
function expressMiddleware(profiler, options = {}) {
|
|
275
|
+
const { ignorePaths = [], traceHeader = "x-trace-id", exposeTraceHeader = true } = options;
|
|
276
|
+
return function perfstackMiddleware(req, res, next) {
|
|
277
|
+
const path = req.originalUrl ?? req.url ?? "/";
|
|
278
|
+
if (ignorePaths.some((p) => path === p || path.startsWith(`${p}/`))) {
|
|
279
|
+
next();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const traceHeaderVal = req.headers[traceHeader.toLowerCase()];
|
|
283
|
+
const traceId = typeof traceHeaderVal === "string" && traceHeaderVal || Array.isArray(traceHeaderVal) && traceHeaderVal[0] || `req_${randomBytes2(8).toString("hex")}`;
|
|
284
|
+
if (exposeTraceHeader) res.setHeader(traceHeader, traceId);
|
|
285
|
+
const startedAt = Date.now();
|
|
286
|
+
profiler.tracer.runInContext(traceId, () => {
|
|
287
|
+
res.on("finish", () => {
|
|
288
|
+
profiler.recordHttp({
|
|
289
|
+
method: req.method ?? "GET",
|
|
290
|
+
path: req.route?.path ?? simplifyPath(path),
|
|
291
|
+
statusCode: res.statusCode,
|
|
292
|
+
durationMs: Date.now() - startedAt,
|
|
293
|
+
startedAt,
|
|
294
|
+
traceId
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
next();
|
|
298
|
+
});
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function dashboardMiddleware(profiler) {
|
|
302
|
+
return function perfstackDashboard(req, res) {
|
|
303
|
+
const path = req.originalUrl ?? req.url ?? "/";
|
|
304
|
+
if (path.endsWith(".json")) {
|
|
305
|
+
res.setHeader("content-type", "application/json");
|
|
306
|
+
res.statusCode = 200;
|
|
307
|
+
res.end(JSON.stringify(profiler.report(), null, 2));
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
311
|
+
res.statusCode = 200;
|
|
312
|
+
res.end(renderDashboard(profiler));
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function simplifyPath(p) {
|
|
316
|
+
return p.split("?")[0].replace(/\/[0-9a-f]{24}\b/gi, "/:id").replace(/\/\d+\b/g, "/:id");
|
|
317
|
+
}
|
|
318
|
+
function renderDashboard(profiler) {
|
|
319
|
+
const report = profiler.report();
|
|
320
|
+
return `<!doctype html>
|
|
321
|
+
<html><head><meta charset="utf-8" />
|
|
322
|
+
<title>perfstack</title>
|
|
323
|
+
<style>
|
|
324
|
+
body{font:14px/1.4 ui-monospace,Menlo,monospace;background:#0b1020;color:#e6e6e6;margin:0;padding:24px}
|
|
325
|
+
h1,h2{font-weight:600;margin:0 0 12px}h2{margin-top:24px}
|
|
326
|
+
table{border-collapse:collapse;width:100%;margin-top:8px}
|
|
327
|
+
th,td{padding:6px 10px;text-align:left;border-bottom:1px solid #1d2440}
|
|
328
|
+
th{color:#8aa6ff;font-weight:600}
|
|
329
|
+
.bad{color:#ff8a8a}.good{color:#8aff9c}
|
|
330
|
+
.card{background:#121838;border-radius:8px;padding:16px;margin-bottom:16px}
|
|
331
|
+
.grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}
|
|
332
|
+
.k{color:#8a93b3}.v{font-size:20px;font-weight:600}
|
|
333
|
+
</style></head>
|
|
334
|
+
<body>
|
|
335
|
+
<h1>perfstack</h1>
|
|
336
|
+
<div class="grid">
|
|
337
|
+
<div class="card"><div class="k">uptime</div><div class="v">${Math.round(report.uptimeMs / 1e3)}s</div></div>
|
|
338
|
+
<div class="card"><div class="k">requests</div><div class="v">${report.totalRequests}</div></div>
|
|
339
|
+
<div class="card"><div class="k">errors</div><div class="v ${report.totalErrors ? "bad" : "good"}">${report.totalErrors}</div></div>
|
|
340
|
+
<div class="card"><div class="k">queries</div><div class="v">${report.totalQueries}</div></div>
|
|
341
|
+
</div>
|
|
342
|
+
|
|
343
|
+
<h2>HTTP latency (ms)</h2>
|
|
344
|
+
<table><tr><th>count</th><th>min</th><th>avg</th><th>p50</th><th>p90</th><th>p95</th><th>p99</th><th>max</th></tr>
|
|
345
|
+
<tr><td>${report.http.count}</td><td>${report.http.min}</td><td>${report.http.avg}</td><td>${report.http.p50}</td><td>${report.http.p90}</td><td>${report.http.p95}</td><td>${report.http.p99}</td><td>${report.http.max}</td></tr>
|
|
346
|
+
</table>
|
|
347
|
+
|
|
348
|
+
<h2>Slowest routes (p95)</h2>
|
|
349
|
+
<table><tr><th>route</th><th>count</th><th>p95</th><th>p99</th><th>max</th><th>errors</th></tr>
|
|
350
|
+
${report.slowestRoutes.map((r) => `<tr><td>${escapeHtml(r.method)} ${escapeHtml(r.path)}</td><td>${r.count}</td><td>${r.p95}</td><td>${r.p99}</td><td>${r.max}</td><td>${r.errorCount}</td></tr>`).join("\n")}
|
|
351
|
+
</table>
|
|
352
|
+
|
|
353
|
+
<h2>Slowest queries</h2>
|
|
354
|
+
<table><tr><th>collection</th><th>op</th><th>duration</th></tr>
|
|
355
|
+
${report.slowestQueries.map((q) => `<tr><td>${escapeHtml(q.collection)}</td><td>${escapeHtml(q.op)}</td><td>${q.durationMs}ms</td></tr>`).join("\n")}
|
|
356
|
+
</table>
|
|
357
|
+
|
|
358
|
+
<h2>Memory</h2>
|
|
359
|
+
<table><tr><th>rss</th><th>heap used</th><th>heap total</th><th>peak rss</th><th>peak heap</th></tr>
|
|
360
|
+
<tr>
|
|
361
|
+
<td>${fmtBytes(report.memory.current?.rss ?? 0)}</td>
|
|
362
|
+
<td>${fmtBytes(report.memory.current?.heapUsed ?? 0)}</td>
|
|
363
|
+
<td>${fmtBytes(report.memory.current?.heapTotal ?? 0)}</td>
|
|
364
|
+
<td>${fmtBytes(report.memory.peakRss)}</td>
|
|
365
|
+
<td>${fmtBytes(report.memory.peakHeapUsed)}</td>
|
|
366
|
+
</tr>
|
|
367
|
+
</table>
|
|
368
|
+
|
|
369
|
+
</body></html>`;
|
|
370
|
+
}
|
|
371
|
+
function fmtBytes(n) {
|
|
372
|
+
if (n < 1024) return `${n} B`;
|
|
373
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
374
|
+
if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
|
375
|
+
return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
|
376
|
+
}
|
|
377
|
+
function escapeHtml(s) {
|
|
378
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/mongoose.ts
|
|
382
|
+
var HOOKS = [
|
|
383
|
+
"find",
|
|
384
|
+
"findOne",
|
|
385
|
+
"findOneAndUpdate",
|
|
386
|
+
"findOneAndDelete",
|
|
387
|
+
"count",
|
|
388
|
+
"countDocuments",
|
|
389
|
+
"updateOne",
|
|
390
|
+
"updateMany",
|
|
391
|
+
"deleteOne",
|
|
392
|
+
"deleteMany",
|
|
393
|
+
"distinct"
|
|
394
|
+
];
|
|
395
|
+
function mongoosePlugin(profiler) {
|
|
396
|
+
return function perfstackMongoose(schema) {
|
|
397
|
+
for (const hook of HOOKS) {
|
|
398
|
+
schema.pre(hook, function() {
|
|
399
|
+
this._perfstackStart = Date.now();
|
|
400
|
+
this._perfstackOp = hook;
|
|
401
|
+
this._perfstackCollection = this.model?.collection?.name ?? this.model?.modelName ?? "unknown";
|
|
402
|
+
});
|
|
403
|
+
schema.post(hook, function() {
|
|
404
|
+
const startedAt = this._perfstackStart ?? Date.now();
|
|
405
|
+
profiler.recordQuery({
|
|
406
|
+
collection: this._perfstackCollection ?? "unknown",
|
|
407
|
+
op: this._perfstackOp ?? hook,
|
|
408
|
+
durationMs: Date.now() - startedAt,
|
|
409
|
+
startedAt,
|
|
410
|
+
traceId: profiler.tracer.currentTraceId()
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
schema.pre("aggregate", function() {
|
|
415
|
+
this._perfstackStart = Date.now();
|
|
416
|
+
const model = typeof this.model === "function" ? this.model() : void 0;
|
|
417
|
+
this._perfstackCollection = model?.collection?.name ?? model?.modelName ?? "unknown";
|
|
418
|
+
});
|
|
419
|
+
schema.post("aggregate", function() {
|
|
420
|
+
const startedAt = this._perfstackStart ?? Date.now();
|
|
421
|
+
profiler.recordQuery({
|
|
422
|
+
collection: this._perfstackCollection ?? "unknown",
|
|
423
|
+
op: "aggregate",
|
|
424
|
+
durationMs: Date.now() - startedAt,
|
|
425
|
+
startedAt,
|
|
426
|
+
traceId: profiler.tracer.currentTraceId()
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/index.ts
|
|
433
|
+
function init(app, options = {}) {
|
|
434
|
+
const profiler = new Profiler(options);
|
|
435
|
+
const middleware = expressMiddleware(profiler);
|
|
436
|
+
const dashboard = dashboardMiddleware(profiler);
|
|
437
|
+
const plugin = mongoosePlugin(profiler);
|
|
438
|
+
if (app) {
|
|
439
|
+
app.use(middleware);
|
|
440
|
+
if (typeof app.get === "function") {
|
|
441
|
+
app.get(options.dashboardPath ?? "/__perf", dashboard);
|
|
442
|
+
app.get((options.dashboardPath ?? "/__perf") + ".json", dashboard);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return { profiler, middleware, dashboard, mongoosePlugin: plugin };
|
|
446
|
+
}
|
|
447
|
+
export {
|
|
448
|
+
Histogram,
|
|
449
|
+
Profiler,
|
|
450
|
+
Tracer,
|
|
451
|
+
dashboardMiddleware,
|
|
452
|
+
expressMiddleware,
|
|
453
|
+
init,
|
|
454
|
+
mongoosePlugin,
|
|
455
|
+
percentile
|
|
456
|
+
};
|
|
457
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/histogram.ts","../src/tracer.ts","../src/profiler.ts","../src/express.ts","../src/mongoose.ts","../src/index.ts"],"sourcesContent":["import type { HistogramSummary } from \"./types.js\";\n\nexport class Histogram {\n private samples: number[] = [];\n private max: number;\n\n constructor(max = 1000) {\n this.max = max;\n }\n\n record(value: number): void {\n this.samples.push(value);\n if (this.samples.length > this.max) {\n this.samples.shift();\n }\n }\n\n summary(): HistogramSummary {\n const count = this.samples.length;\n if (count === 0) {\n return { count: 0, min: 0, max: 0, avg: 0, p50: 0, p90: 0, p95: 0, p99: 0 };\n }\n const sorted = [...this.samples].sort((a, b) => a - b);\n const sum = sorted.reduce((s, v) => s + v, 0);\n return {\n count,\n min: sorted[0]!,\n max: sorted[sorted.length - 1]!,\n avg: round(sum / count),\n p50: percentile(sorted, 0.5),\n p90: percentile(sorted, 0.9),\n p95: percentile(sorted, 0.95),\n p99: percentile(sorted, 0.99),\n };\n }\n\n reset(): void {\n this.samples = [];\n }\n}\n\nexport function percentile(sortedAscending: number[], p: number): number {\n if (sortedAscending.length === 0) return 0;\n const idx = Math.min(sortedAscending.length - 1, Math.floor(p * sortedAscending.length));\n return round(sortedAscending[idx]!);\n}\n\nfunction round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { randomBytes } from \"node:crypto\";\nimport type { Span } from \"./types.js\";\n\ninterface TraceContext {\n traceId: string;\n spanId: string;\n}\n\nexport class Tracer {\n private storage = new AsyncLocalStorage<TraceContext>();\n private spans: Span[] = [];\n private max: number;\n\n constructor(options: { maxSpans?: number } = {}) {\n this.max = options.maxSpans ?? 500;\n }\n\n start(name: string, kind: Span[\"kind\"] = \"custom\", metadata: Record<string, unknown> = {}): {\n end: (info?: { error?: unknown; status?: Span[\"status\"]; extra?: Record<string, unknown> }) => Span;\n } {\n const startedAt = Date.now();\n const id = `s_${randomBytes(6).toString(\"hex\")}`;\n const parent = this.storage.getStore();\n const traceId = parent?.traceId ?? `t_${randomBytes(8).toString(\"hex\")}`;\n const parentId = parent?.spanId;\n\n return {\n end: (info = {}) => {\n const status: Span[\"status\"] = info.error ? \"error\" : info.status ?? \"ok\";\n const errorMsg = info.error instanceof Error ? info.error.message : info.error ? String(info.error) : undefined;\n const span: Span = {\n id,\n parentId,\n traceId,\n name,\n kind,\n startedAt,\n durationMs: Date.now() - startedAt,\n metadata: { ...metadata, ...(info.extra ?? {}) },\n status,\n error: errorMsg,\n };\n this.push(span);\n return span;\n },\n };\n }\n\n async withSpan<T>(\n name: string,\n fn: () => Promise<T>,\n options: { kind?: Span[\"kind\"]; metadata?: Record<string, unknown> } = {},\n ): Promise<T> {\n const startedAt = Date.now();\n const parent = this.storage.getStore();\n const traceId = parent?.traceId ?? `t_${randomBytes(8).toString(\"hex\")}`;\n const spanId = `s_${randomBytes(6).toString(\"hex\")}`;\n return this.storage.run({ traceId, spanId }, async () => {\n try {\n const result = await fn();\n this.push({\n id: spanId,\n parentId: parent?.spanId,\n traceId,\n name,\n kind: options.kind ?? \"custom\",\n startedAt,\n durationMs: Date.now() - startedAt,\n metadata: options.metadata ?? {},\n status: \"ok\",\n });\n return result;\n } catch (err) {\n this.push({\n id: spanId,\n parentId: parent?.spanId,\n traceId,\n name,\n kind: options.kind ?? \"custom\",\n startedAt,\n durationMs: Date.now() - startedAt,\n metadata: options.metadata ?? {},\n status: \"error\",\n error: err instanceof Error ? err.message : String(err),\n });\n throw err;\n }\n });\n }\n\n runInContext<T>(traceId: string, fn: () => T): T {\n const spanId = `s_${randomBytes(6).toString(\"hex\")}`;\n return this.storage.run({ traceId, spanId }, fn);\n }\n\n currentTraceId(): string | undefined {\n return this.storage.getStore()?.traceId;\n }\n\n recent(limit = 100): Span[] {\n return this.spans.slice(-limit);\n }\n\n byTrace(traceId: string): Span[] {\n return this.spans.filter((s) => s.traceId === traceId);\n }\n\n reset(): void {\n this.spans = [];\n }\n\n private push(span: Span): void {\n this.spans.push(span);\n if (this.spans.length > this.max) this.spans.shift();\n }\n}\n","import { Histogram } from \"./histogram.js\";\nimport { Tracer } from \"./tracer.js\";\nimport type {\n HttpRecord,\n MemorySnapshot,\n PerfReport,\n PerfstackOptions,\n QueryRecord,\n RouteSummary,\n} from \"./types.js\";\n\ninterface RouteBucket {\n method: string;\n path: string;\n histogram: Histogram;\n errors: number;\n}\n\nexport class Profiler {\n private startedAt: number;\n private httpHistogram = new Histogram();\n private queryHistogram = new Histogram();\n private routes = new Map<string, RouteBucket>();\n private slowestQueries: QueryRecord[] = [];\n private memorySamples: MemorySnapshot[] = [];\n private peakRss = 0;\n private peakHeapUsed = 0;\n private totalRequests = 0;\n private totalErrors = 0;\n private totalQueries = 0;\n private memoryInterval?: ReturnType<typeof setInterval>;\n readonly tracer: Tracer;\n private opts: Required<Pick<\n PerfstackOptions,\n | \"slowRequestThreshold\"\n | \"slowQueryThreshold\"\n | \"memorySampleIntervalMs\"\n | \"memorySamplesMax\"\n | \"routeBucketsMax\"\n | \"enableMemorySampling\"\n >>;\n\n constructor(options: PerfstackOptions = {}) {\n this.startedAt = options.startedAt ?? Date.now();\n this.opts = {\n slowRequestThreshold: options.slowRequestThreshold ?? 500,\n slowQueryThreshold: options.slowQueryThreshold ?? 200,\n memorySampleIntervalMs: options.memorySampleIntervalMs ?? 5_000,\n memorySamplesMax: options.memorySamplesMax ?? 120,\n routeBucketsMax: options.routeBucketsMax ?? 200,\n enableMemorySampling: options.enableMemorySampling ?? false,\n };\n this.tracer = new Tracer();\n if (this.opts.enableMemorySampling) this.startMemorySampling();\n }\n\n recordHttp(record: HttpRecord): void {\n this.totalRequests += 1;\n if (record.statusCode >= 500) this.totalErrors += 1;\n this.httpHistogram.record(record.durationMs);\n const key = `${record.method} ${record.path}`;\n let bucket = this.routes.get(key);\n if (!bucket) {\n if (this.routes.size >= this.opts.routeBucketsMax) {\n const oldest = this.routes.keys().next().value;\n if (oldest !== undefined) this.routes.delete(oldest);\n }\n bucket = { method: record.method, path: record.path, histogram: new Histogram(), errors: 0 };\n this.routes.set(key, bucket);\n }\n bucket.histogram.record(record.durationMs);\n if (record.statusCode >= 500) bucket.errors += 1;\n }\n\n recordQuery(record: QueryRecord): void {\n this.totalQueries += 1;\n this.queryHistogram.record(record.durationMs);\n if (record.durationMs >= this.opts.slowQueryThreshold) {\n this.slowestQueries.push(record);\n this.slowestQueries.sort((a, b) => b.durationMs - a.durationMs);\n if (this.slowestQueries.length > 50) this.slowestQueries.length = 50;\n }\n }\n\n takeMemorySample(): MemorySnapshot {\n const m = process.memoryUsage();\n const snap: MemorySnapshot = {\n takenAt: Date.now(),\n rss: m.rss,\n heapTotal: m.heapTotal,\n heapUsed: m.heapUsed,\n external: m.external,\n arrayBuffers: m.arrayBuffers,\n };\n this.memorySamples.push(snap);\n if (this.memorySamples.length > this.opts.memorySamplesMax) this.memorySamples.shift();\n if (snap.rss > this.peakRss) this.peakRss = snap.rss;\n if (snap.heapUsed > this.peakHeapUsed) this.peakHeapUsed = snap.heapUsed;\n return snap;\n }\n\n startMemorySampling(): void {\n if (this.memoryInterval) return;\n this.takeMemorySample();\n const interval = setInterval(() => this.takeMemorySample(), this.opts.memorySampleIntervalMs);\n (interval as { unref?: () => void }).unref?.();\n this.memoryInterval = interval;\n }\n\n stopMemorySampling(): void {\n if (this.memoryInterval) {\n clearInterval(this.memoryInterval);\n this.memoryInterval = undefined;\n }\n }\n\n report(): PerfReport {\n if (this.memorySamples.length === 0) this.takeMemorySample();\n const routes: RouteSummary[] = Array.from(this.routes.values()).map((bucket) => ({\n method: bucket.method,\n path: bucket.path,\n errorCount: bucket.errors,\n ...bucket.histogram.summary(),\n }));\n const slowestRoutes = [...routes].sort((a, b) => b.p95 - a.p95).slice(0, 10);\n return {\n uptimeMs: Date.now() - this.startedAt,\n startedAt: this.startedAt,\n takenAt: Date.now(),\n totalRequests: this.totalRequests,\n totalErrors: this.totalErrors,\n totalQueries: this.totalQueries,\n http: this.httpHistogram.summary(),\n routes,\n slowestRoutes,\n queries: this.queryHistogram.summary(),\n slowestQueries: [...this.slowestQueries],\n memory: {\n current: this.memorySamples.at(-1) ?? null,\n samples: [...this.memorySamples],\n peakRss: this.peakRss,\n peakHeapUsed: this.peakHeapUsed,\n },\n };\n }\n\n reset(): void {\n this.httpHistogram.reset();\n this.queryHistogram.reset();\n this.routes.clear();\n this.slowestQueries = [];\n this.memorySamples = [];\n this.peakRss = 0;\n this.peakHeapUsed = 0;\n this.totalRequests = 0;\n this.totalErrors = 0;\n this.totalQueries = 0;\n this.tracer.reset();\n }\n\n isSlowRequest(durationMs: number): boolean {\n return durationMs >= this.opts.slowRequestThreshold;\n }\n}\n","import { randomBytes } from \"node:crypto\";\nimport type { Profiler } from \"./profiler.js\";\n\ntype ReqLike = {\n method?: string;\n originalUrl?: string;\n url?: string;\n route?: { path?: string };\n headers: Record<string, string | string[] | undefined>;\n};\ntype ResLike = {\n statusCode: number;\n on(event: \"finish\", cb: () => void): void;\n setHeader(name: string, value: string): void;\n};\ntype NextLike = () => void;\n\nexport interface ExpressMiddlewareOptions {\n ignorePaths?: string[];\n traceHeader?: string;\n exposeTraceHeader?: boolean;\n}\n\nexport function expressMiddleware(\n profiler: Profiler,\n options: ExpressMiddlewareOptions = {},\n): (req: ReqLike, res: ResLike, next: NextLike) => void {\n const { ignorePaths = [], traceHeader = \"x-trace-id\", exposeTraceHeader = true } = options;\n return function perfstackMiddleware(req, res, next) {\n const path = req.originalUrl ?? req.url ?? \"/\";\n if (ignorePaths.some((p) => path === p || path.startsWith(`${p}/`))) {\n next();\n return;\n }\n\n const traceHeaderVal = req.headers[traceHeader.toLowerCase()];\n const traceId =\n (typeof traceHeaderVal === \"string\" && traceHeaderVal) ||\n (Array.isArray(traceHeaderVal) && traceHeaderVal[0]) ||\n `req_${randomBytes(8).toString(\"hex\")}`;\n\n if (exposeTraceHeader) res.setHeader(traceHeader, traceId);\n\n const startedAt = Date.now();\n\n profiler.tracer.runInContext(traceId, () => {\n res.on(\"finish\", () => {\n profiler.recordHttp({\n method: req.method ?? \"GET\",\n path: req.route?.path ?? simplifyPath(path),\n statusCode: res.statusCode,\n durationMs: Date.now() - startedAt,\n startedAt,\n traceId,\n });\n });\n next();\n });\n };\n}\n\nexport function dashboardMiddleware(\n profiler: Profiler,\n): (req: ReqLike, res: ResLikeWithSend, next: NextLike) => void {\n return function perfstackDashboard(req, res) {\n const path = req.originalUrl ?? req.url ?? \"/\";\n if (path.endsWith(\".json\")) {\n res.setHeader(\"content-type\", \"application/json\");\n res.statusCode = 200;\n res.end(JSON.stringify(profiler.report(), null, 2));\n return;\n }\n res.setHeader(\"content-type\", \"text/html; charset=utf-8\");\n res.statusCode = 200;\n res.end(renderDashboard(profiler));\n };\n}\n\ntype ResLikeWithSend = ResLike & {\n statusCode: number;\n end(payload?: string): void;\n};\n\nfunction simplifyPath(p: string): string {\n return p\n .split(\"?\")[0]!\n .replace(/\\/[0-9a-f]{24}\\b/gi, \"/:id\")\n .replace(/\\/\\d+\\b/g, \"/:id\");\n}\n\nfunction renderDashboard(profiler: Profiler): string {\n const report = profiler.report();\n return `<!doctype html>\n<html><head><meta charset=\"utf-8\" />\n<title>perfstack</title>\n<style>\nbody{font:14px/1.4 ui-monospace,Menlo,monospace;background:#0b1020;color:#e6e6e6;margin:0;padding:24px}\nh1,h2{font-weight:600;margin:0 0 12px}h2{margin-top:24px}\ntable{border-collapse:collapse;width:100%;margin-top:8px}\nth,td{padding:6px 10px;text-align:left;border-bottom:1px solid #1d2440}\nth{color:#8aa6ff;font-weight:600}\n.bad{color:#ff8a8a}.good{color:#8aff9c}\n.card{background:#121838;border-radius:8px;padding:16px;margin-bottom:16px}\n.grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}\n.k{color:#8a93b3}.v{font-size:20px;font-weight:600}\n</style></head>\n<body>\n<h1>perfstack</h1>\n<div class=\"grid\">\n <div class=\"card\"><div class=\"k\">uptime</div><div class=\"v\">${Math.round(report.uptimeMs / 1000)}s</div></div>\n <div class=\"card\"><div class=\"k\">requests</div><div class=\"v\">${report.totalRequests}</div></div>\n <div class=\"card\"><div class=\"k\">errors</div><div class=\"v ${report.totalErrors ? \"bad\" : \"good\"}\">${report.totalErrors}</div></div>\n <div class=\"card\"><div class=\"k\">queries</div><div class=\"v\">${report.totalQueries}</div></div>\n</div>\n\n<h2>HTTP latency (ms)</h2>\n<table><tr><th>count</th><th>min</th><th>avg</th><th>p50</th><th>p90</th><th>p95</th><th>p99</th><th>max</th></tr>\n<tr><td>${report.http.count}</td><td>${report.http.min}</td><td>${report.http.avg}</td><td>${report.http.p50}</td><td>${report.http.p90}</td><td>${report.http.p95}</td><td>${report.http.p99}</td><td>${report.http.max}</td></tr>\n</table>\n\n<h2>Slowest routes (p95)</h2>\n<table><tr><th>route</th><th>count</th><th>p95</th><th>p99</th><th>max</th><th>errors</th></tr>\n${report.slowestRoutes\n .map((r) => `<tr><td>${escapeHtml(r.method)} ${escapeHtml(r.path)}</td><td>${r.count}</td><td>${r.p95}</td><td>${r.p99}</td><td>${r.max}</td><td>${r.errorCount}</td></tr>`)\n .join(\"\\n\")}\n</table>\n\n<h2>Slowest queries</h2>\n<table><tr><th>collection</th><th>op</th><th>duration</th></tr>\n${report.slowestQueries\n .map((q) => `<tr><td>${escapeHtml(q.collection)}</td><td>${escapeHtml(q.op)}</td><td>${q.durationMs}ms</td></tr>`)\n .join(\"\\n\")}\n</table>\n\n<h2>Memory</h2>\n<table><tr><th>rss</th><th>heap used</th><th>heap total</th><th>peak rss</th><th>peak heap</th></tr>\n<tr>\n <td>${fmtBytes(report.memory.current?.rss ?? 0)}</td>\n <td>${fmtBytes(report.memory.current?.heapUsed ?? 0)}</td>\n <td>${fmtBytes(report.memory.current?.heapTotal ?? 0)}</td>\n <td>${fmtBytes(report.memory.peakRss)}</td>\n <td>${fmtBytes(report.memory.peakHeapUsed)}</td>\n</tr>\n</table>\n\n</body></html>`;\n}\n\nfunction fmtBytes(n: number): string {\n if (n < 1024) return `${n} B`;\n if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;\n if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;\n return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n}\n","import type { Profiler } from \"./profiler.js\";\n\ntype SchemaLike = {\n pre(hook: string, fn: (this: any) => void): unknown;\n post(hook: string, fn: (this: any, result: unknown) => void): unknown;\n};\n\nconst HOOKS = [\n \"find\",\n \"findOne\",\n \"findOneAndUpdate\",\n \"findOneAndDelete\",\n \"count\",\n \"countDocuments\",\n \"updateOne\",\n \"updateMany\",\n \"deleteOne\",\n \"deleteMany\",\n \"distinct\",\n];\n\nexport function mongoosePlugin(profiler: Profiler) {\n return function perfstackMongoose(schema: SchemaLike) {\n for (const hook of HOOKS) {\n schema.pre(hook, function (this: any) {\n this._perfstackStart = Date.now();\n this._perfstackOp = hook;\n this._perfstackCollection = this.model?.collection?.name ?? this.model?.modelName ?? \"unknown\";\n });\n schema.post(hook, function (this: any) {\n const startedAt = this._perfstackStart ?? Date.now();\n profiler.recordQuery({\n collection: this._perfstackCollection ?? \"unknown\",\n op: this._perfstackOp ?? hook,\n durationMs: Date.now() - startedAt,\n startedAt,\n traceId: profiler.tracer.currentTraceId(),\n });\n });\n }\n schema.pre(\"aggregate\", function (this: any) {\n this._perfstackStart = Date.now();\n const model = typeof this.model === \"function\" ? this.model() : undefined;\n this._perfstackCollection = model?.collection?.name ?? model?.modelName ?? \"unknown\";\n });\n schema.post(\"aggregate\", function (this: any) {\n const startedAt = this._perfstackStart ?? Date.now();\n profiler.recordQuery({\n collection: this._perfstackCollection ?? \"unknown\",\n op: \"aggregate\",\n durationMs: Date.now() - startedAt,\n startedAt,\n traceId: profiler.tracer.currentTraceId(),\n });\n });\n };\n}\n","export { Profiler } from \"./profiler.js\";\nexport { Histogram, percentile } from \"./histogram.js\";\nexport { Tracer } from \"./tracer.js\";\nexport { expressMiddleware, dashboardMiddleware } from \"./express.js\";\nexport { mongoosePlugin } from \"./mongoose.js\";\nexport type {\n HttpRecord,\n QueryRecord,\n MemorySnapshot,\n PerfReport,\n PerfstackOptions,\n RouteSummary,\n HistogramSummary,\n Span,\n} from \"./types.js\";\n\nimport { Profiler } from \"./profiler.js\";\nimport { dashboardMiddleware, expressMiddleware } from \"./express.js\";\nimport { mongoosePlugin } from \"./mongoose.js\";\n\ntype AnyApp = {\n use: (...args: unknown[]) => unknown;\n get?: (path: string, handler: (...args: unknown[]) => unknown) => unknown;\n};\n\nexport interface InitResult {\n profiler: Profiler;\n middleware: ReturnType<typeof expressMiddleware>;\n dashboard: ReturnType<typeof dashboardMiddleware>;\n mongoosePlugin: ReturnType<typeof mongoosePlugin>;\n}\n\nexport function init(\n app?: AnyApp,\n options: ConstructorParameters<typeof Profiler>[0] & { dashboardPath?: string } = {},\n): InitResult {\n const profiler = new Profiler(options);\n const middleware = expressMiddleware(profiler);\n const dashboard = dashboardMiddleware(profiler);\n const plugin = mongoosePlugin(profiler);\n if (app) {\n app.use(middleware);\n if (typeof app.get === \"function\") {\n app.get(options.dashboardPath ?? \"/__perf\", dashboard as unknown as (...a: unknown[]) => unknown);\n app.get((options.dashboardPath ?? \"/__perf\") + \".json\", dashboard as unknown as (...a: unknown[]) => unknown);\n }\n }\n return { profiler, middleware, dashboard, mongoosePlugin: plugin };\n}\n"],"mappings":"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","names":["randomBytes"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "perfstack",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Fullstack performance profiler: HTTP latency, slow MongoDB queries, memory snapshots, span tracing, and a built-in dashboard endpoint.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"CHANGELOG.md"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"dashboard",
|
|
31
|
+
"express",
|
|
32
|
+
"memory",
|
|
33
|
+
"mern-packages",
|
|
34
|
+
"merndev",
|
|
35
|
+
"mongodb",
|
|
36
|
+
"nodejs",
|
|
37
|
+
"npm-pm",
|
|
38
|
+
"observability",
|
|
39
|
+
"performance",
|
|
40
|
+
"perfstack",
|
|
41
|
+
"profiling",
|
|
42
|
+
"tracing",
|
|
43
|
+
"typescript"
|
|
44
|
+
],
|
|
45
|
+
"dependencies": {},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/express": "^4.17.21",
|
|
48
|
+
"@types/node": "^20.11.0",
|
|
49
|
+
"@types/supertest": "^6.0.2",
|
|
50
|
+
"express": "^4.19.2",
|
|
51
|
+
"supertest": "^7.0.0",
|
|
52
|
+
"tsup": "^8.0.0",
|
|
53
|
+
"typescript": "^5.4.0",
|
|
54
|
+
"vitest": "^1.4.0"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"express": "^4.0.0 || ^5.0.0"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"express": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"engines": {
|
|
65
|
+
"node": ">=18"
|
|
66
|
+
},
|
|
67
|
+
"author": "Aftab Ahmad Khan (https://github.com/aftab-ahmad-khan-dev)",
|
|
68
|
+
"repository": {
|
|
69
|
+
"type": "git",
|
|
70
|
+
"url": "git+https://github.com/NPM-Packages-Modules/mern.git",
|
|
71
|
+
"directory": "perfstack"
|
|
72
|
+
},
|
|
73
|
+
"bugs": {
|
|
74
|
+
"url": "https://github.com/NPM-Packages-Modules/mern/issues"
|
|
75
|
+
},
|
|
76
|
+
"homepage": "https://github.com/NPM-Packages-Modules/mern/tree/main/perfstack",
|
|
77
|
+
"publishConfig": {
|
|
78
|
+
"access": "public"
|
|
79
|
+
}
|
|
80
|
+
}
|