@rodit/rodit-auth-be 9.11.14
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 +54 -0
- package/README.md +3543 -0
- package/index.js +1884 -0
- package/lib/auth/authentication.js +1971 -0
- package/lib/auth/roditmanager.js +627 -0
- package/lib/auth/sessionmanager.js +1302 -0
- package/lib/auth/tokenservice.js +2418 -0
- package/lib/blockchain/blockchainservice.js +1715 -0
- package/lib/blockchain/statemanager.js +1614 -0
- package/lib/middleware/authenticationmw.js +2301 -0
- package/lib/middleware/environcredentialstoremw.js +176 -0
- package/lib/middleware/filecredentialstoremw.js +158 -0
- package/lib/middleware/loggingmw.js +82 -0
- package/lib/middleware/performanceexamplemw.js +58 -0
- package/lib/middleware/performancemw.js +172 -0
- package/lib/middleware/ratelimitmw.js +171 -0
- package/lib/middleware/validatepermissionsmw.js +439 -0
- package/lib/middleware/vaultcredentialstoremw.js +617 -0
- package/lib/middleware/versioningmw.js +142 -0
- package/lib/middleware/webhookhandlermw.js +1388 -0
- package/package.json +57 -0
- package/services/configsdk.js +588 -0
- package/services/env.js +34 -0
- package/services/error-response.js +29 -0
- package/services/logger.js +160 -0
- package/services/performanceservice.js +568 -0
- package/services/utils.js +1024 -0
- package/services/versionmanager.js +81 -0
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance monitoring service for tracing and metrics collection
|
|
3
|
+
* Copyright (c) 2026 Discernible IO. All rights reserved.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { ulid } = require('ulid');
|
|
7
|
+
const logger = require('./logger');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
|
|
10
|
+
const RPM_WINDOW_SEC = 60;
|
|
11
|
+
const MAX_ENDPOINT_KEYS = 80;
|
|
12
|
+
const MAX_DURATION_SAMPLES = 200;
|
|
13
|
+
const OTHER_KEY = '*';
|
|
14
|
+
|
|
15
|
+
class PerformanceService {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.traces = new Map();
|
|
18
|
+
this._processStartedAt = Date.now();
|
|
19
|
+
this._lastResetAt = null;
|
|
20
|
+
/** @type {Map<number, number>} unix second -> request count */
|
|
21
|
+
this._requestsBySecond = new Map();
|
|
22
|
+
/** @type {Map<string, object>} */
|
|
23
|
+
this._endpointStats = new Map();
|
|
24
|
+
this.metrics = {
|
|
25
|
+
requestCount: 0,
|
|
26
|
+
errorCount: 0,
|
|
27
|
+
totalDuration: 0,
|
|
28
|
+
maxDuration: 0,
|
|
29
|
+
minDuration: Number.MAX_SAFE_INTEGER,
|
|
30
|
+
blockchainCalls: 0,
|
|
31
|
+
blockchainDuration: 0,
|
|
32
|
+
authenticationCalls: 0,
|
|
33
|
+
authenticationDuration: 0
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Normalize URL path for cardinality control (group numeric / id-like segments).
|
|
39
|
+
* @param {string} rawUrl
|
|
40
|
+
*/
|
|
41
|
+
static normalizeEndpointKey(rawUrl) {
|
|
42
|
+
try {
|
|
43
|
+
const pathname = rawUrl.includes("://")
|
|
44
|
+
? new URL(rawUrl).pathname
|
|
45
|
+
: rawUrl.split("?")[0];
|
|
46
|
+
return pathname
|
|
47
|
+
.replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "/:uuid")
|
|
48
|
+
.replace(/\/[0-9a-hjkmnp-tv-z]{26}\b/gi, "/:ulid")
|
|
49
|
+
.replace(/\/\d+/g, "/:id");
|
|
50
|
+
} catch {
|
|
51
|
+
return String(rawUrl).split("?")[0];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_pruneRequestBuckets(nowSec) {
|
|
56
|
+
const cutoff = nowSec - RPM_WINDOW_SEC - 5;
|
|
57
|
+
for (const sec of this._requestsBySecond.keys()) {
|
|
58
|
+
if (sec < cutoff) {
|
|
59
|
+
this._requestsBySecond.delete(sec);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_incrementRpmWindow() {
|
|
65
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
66
|
+
this._pruneRequestBuckets(nowSec);
|
|
67
|
+
this._requestsBySecond.set(nowSec, (this._requestsBySecond.get(nowSec) || 0) + 1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_computeRequestsPerMinute() {
|
|
71
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
72
|
+
this._pruneRequestBuckets(nowSec);
|
|
73
|
+
let sum = 0;
|
|
74
|
+
for (let s = nowSec - RPM_WINDOW_SEC + 1; s <= nowSec; s++) {
|
|
75
|
+
sum += this._requestsBySecond.get(s) || 0;
|
|
76
|
+
}
|
|
77
|
+
return sum;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
_percentile(sorted, p) {
|
|
81
|
+
if (!sorted.length) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const idx = Math.ceil((p / 100) * sorted.length) - 1;
|
|
85
|
+
return sorted[Math.max(0, Math.min(sorted.length - 1, idx))];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
_computeLoadLevel(rpm, requestCount, errorCount) {
|
|
89
|
+
const errRate = requestCount > 0 ? errorCount / requestCount : 0;
|
|
90
|
+
if (errRate > 0.15) {
|
|
91
|
+
return "high";
|
|
92
|
+
}
|
|
93
|
+
if (rpm > 600 || errRate > 0.05) {
|
|
94
|
+
return "high";
|
|
95
|
+
}
|
|
96
|
+
if (rpm > 120 || errRate > 0.01) {
|
|
97
|
+
return "medium";
|
|
98
|
+
}
|
|
99
|
+
return "low";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
_serializeEndpointMetrics() {
|
|
103
|
+
const out = {};
|
|
104
|
+
for (const [key, st] of this._endpointStats.entries()) {
|
|
105
|
+
const samples = [...st.durations].sort((a, b) => a - b);
|
|
106
|
+
const count = st.count;
|
|
107
|
+
const errCount = st.errorCount;
|
|
108
|
+
out[key] = {
|
|
109
|
+
count,
|
|
110
|
+
errorCount: errCount,
|
|
111
|
+
totalDurationMs: st.totalDurationMs,
|
|
112
|
+
avgMs: count ? Math.round(st.totalDurationMs / count) : 0,
|
|
113
|
+
minMs: st.minMs === Number.MAX_SAFE_INTEGER ? null : st.minMs,
|
|
114
|
+
maxMs: st.maxMs === 0 ? null : st.maxMs,
|
|
115
|
+
p50Ms: this._percentile(samples, 50),
|
|
116
|
+
p95Ms: this._percentile(samples, 95),
|
|
117
|
+
p99Ms: this._percentile(samples, 99)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Initialize the performance monitoring service
|
|
125
|
+
*
|
|
126
|
+
*/
|
|
127
|
+
initialize() {
|
|
128
|
+
logger.info('Performance monitoring service initialized', {
|
|
129
|
+
component: 'PerformanceService',
|
|
130
|
+
method: 'initialize'
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Record a new request
|
|
138
|
+
*
|
|
139
|
+
* @param {Object} req - Express request object
|
|
140
|
+
*/
|
|
141
|
+
recordRequest(req) {
|
|
142
|
+
// Update total request count metric
|
|
143
|
+
this.metrics.requestCount++;
|
|
144
|
+
this._incrementRpmWindow();
|
|
145
|
+
|
|
146
|
+
logger.debug('Request recorded', {
|
|
147
|
+
component: 'PerformanceService',
|
|
148
|
+
method: 'recordRequest',
|
|
149
|
+
path: req.path,
|
|
150
|
+
requestMethod: req.method
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Record a metric
|
|
156
|
+
* Uses the standardized logger.metric method for consistent metric collection
|
|
157
|
+
* while also updating internal state for load monitoring
|
|
158
|
+
*
|
|
159
|
+
* @param {string} metricName - Name of the metric
|
|
160
|
+
* @param {number} value - Value to record
|
|
161
|
+
* @param {Object} tags - Additional tags for the metric
|
|
162
|
+
*/
|
|
163
|
+
recordMetric(metricName, value, tags = {}) {
|
|
164
|
+
// Always use the standardized logger.metric method for metrics
|
|
165
|
+
logger.metric(metricName, value, {
|
|
166
|
+
...tags,
|
|
167
|
+
component: 'PerformanceService'
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Update internal metrics for load monitoring and reporting
|
|
171
|
+
switch(metricName) {
|
|
172
|
+
case 'request_count':
|
|
173
|
+
case 'http_request_duration_ms':
|
|
174
|
+
this.metrics.requestCount += (metricName === 'request_count' ? value : 1);
|
|
175
|
+
break;
|
|
176
|
+
case 'error_count':
|
|
177
|
+
case 'http_errors_total':
|
|
178
|
+
this.metrics.errorCount += value;
|
|
179
|
+
break;
|
|
180
|
+
case 'authentication_duration':
|
|
181
|
+
case 'authentication_duration_ms':
|
|
182
|
+
this.metrics.authenticationCalls++;
|
|
183
|
+
this.metrics.authenticationDuration += value;
|
|
184
|
+
break;
|
|
185
|
+
case 'blockchain_duration':
|
|
186
|
+
case 'blockchain_duration_ms':
|
|
187
|
+
this.metrics.blockchainCalls++;
|
|
188
|
+
this.metrics.blockchainDuration += value;
|
|
189
|
+
break;
|
|
190
|
+
case 'authentication_error':
|
|
191
|
+
case 'blockchain_error':
|
|
192
|
+
this.metrics.errorCount += value;
|
|
193
|
+
break;
|
|
194
|
+
case 'request_duration':
|
|
195
|
+
this.metrics.totalDuration += value;
|
|
196
|
+
this.metrics.maxDuration = Math.max(this.metrics.maxDuration, value);
|
|
197
|
+
this.metrics.minDuration = Math.min(this.metrics.minDuration, value);
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Per-endpoint latency and error stats (in-process, bounded cardinality).
|
|
204
|
+
*
|
|
205
|
+
* @param {string} method
|
|
206
|
+
* @param {string} url
|
|
207
|
+
* @param {number} durationMs
|
|
208
|
+
* @param {number} statusCode
|
|
209
|
+
*/
|
|
210
|
+
recordEndpointMetric(method, url, durationMs, statusCode) {
|
|
211
|
+
const path = PerformanceService.normalizeEndpointKey(url);
|
|
212
|
+
let key = `${method} ${path}`;
|
|
213
|
+
if (this._endpointStats.size >= MAX_ENDPOINT_KEYS && !this._endpointStats.has(key)) {
|
|
214
|
+
key = `${method} ${OTHER_KEY}`;
|
|
215
|
+
}
|
|
216
|
+
let st = this._endpointStats.get(key);
|
|
217
|
+
if (!st) {
|
|
218
|
+
st = {
|
|
219
|
+
count: 0,
|
|
220
|
+
errorCount: 0,
|
|
221
|
+
totalDurationMs: 0,
|
|
222
|
+
minMs: Number.MAX_SAFE_INTEGER,
|
|
223
|
+
maxMs: 0,
|
|
224
|
+
durations: []
|
|
225
|
+
};
|
|
226
|
+
this._endpointStats.set(key, st);
|
|
227
|
+
}
|
|
228
|
+
st.count++;
|
|
229
|
+
if (statusCode >= 400) {
|
|
230
|
+
st.errorCount++;
|
|
231
|
+
}
|
|
232
|
+
st.totalDurationMs += durationMs;
|
|
233
|
+
st.minMs = Math.min(st.minMs, durationMs);
|
|
234
|
+
st.maxMs = Math.max(st.maxMs, durationMs);
|
|
235
|
+
if (st.durations.length < MAX_DURATION_SAMPLES) {
|
|
236
|
+
st.durations.push(durationMs);
|
|
237
|
+
} else {
|
|
238
|
+
const i = Math.floor(Math.random() * st.durations.length);
|
|
239
|
+
st.durations[i] = durationMs;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Start a trace for performance monitoring
|
|
245
|
+
*
|
|
246
|
+
* @param {string} operationName - Name of the operation being traced
|
|
247
|
+
* @param {Object} metadata - Additional metadata for the trace
|
|
248
|
+
* @returns {string} Trace ID
|
|
249
|
+
*/
|
|
250
|
+
startTrace(operationName, metadata = {}) {
|
|
251
|
+
const traceId = metadata.traceId || ulid();
|
|
252
|
+
const startTime = Date.now();
|
|
253
|
+
|
|
254
|
+
this.traces.set(traceId, {
|
|
255
|
+
id: traceId,
|
|
256
|
+
operation: operationName,
|
|
257
|
+
startTime,
|
|
258
|
+
metadata,
|
|
259
|
+
spans: [],
|
|
260
|
+
completed: false
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Log trace start as a metric
|
|
264
|
+
logger.metric('trace_started_total', 1, {
|
|
265
|
+
operation: operationName,
|
|
266
|
+
component: 'PerformanceService',
|
|
267
|
+
request_id: metadata.requestId
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
logger.debug(`Started trace for ${operationName}`, {
|
|
271
|
+
component: 'PerformanceService',
|
|
272
|
+
method: 'startTrace',
|
|
273
|
+
traceId,
|
|
274
|
+
operation: operationName,
|
|
275
|
+
metadata: JSON.stringify(metadata)
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return traceId;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Add a span to an existing trace
|
|
283
|
+
*
|
|
284
|
+
* @param {string} traceId - ID of the parent trace
|
|
285
|
+
* @param {string} spanName - Name of the span
|
|
286
|
+
* @param {Object} metadata - Additional metadata for the span
|
|
287
|
+
* @returns {Object} Span object with stop function
|
|
288
|
+
*/
|
|
289
|
+
startSpan(traceId, spanName, metadata = {}) {
|
|
290
|
+
const trace = this.traces.get(traceId);
|
|
291
|
+
|
|
292
|
+
if (!trace) {
|
|
293
|
+
logger.warn('Attempted to add span to non-existent trace', {
|
|
294
|
+
component: 'PerformanceService',
|
|
295
|
+
method: 'startSpan',
|
|
296
|
+
traceId,
|
|
297
|
+
spanName
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
id: ulid(),
|
|
302
|
+
stop: () => {}
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const spanId = ulid();
|
|
307
|
+
const span = {
|
|
308
|
+
id: spanId,
|
|
309
|
+
name: spanName,
|
|
310
|
+
startTime: Date.now(),
|
|
311
|
+
metadata: { ...metadata },
|
|
312
|
+
parentId: traceId
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
trace.spans.push(span);
|
|
316
|
+
|
|
317
|
+
logger.debug('Span started', {
|
|
318
|
+
component: 'PerformanceService',
|
|
319
|
+
method: 'startSpan',
|
|
320
|
+
traceId,
|
|
321
|
+
spanId,
|
|
322
|
+
spanName
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
id: spanId,
|
|
327
|
+
stop: () => this.stopSpan(traceId, spanId)
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Stop a span and record its duration
|
|
333
|
+
*
|
|
334
|
+
* @param {string} traceId - ID of the parent trace
|
|
335
|
+
* @param {string} spanId - ID of the span to stop
|
|
336
|
+
*/
|
|
337
|
+
stopSpan(traceId, spanId) {
|
|
338
|
+
const trace = this.traces.get(traceId);
|
|
339
|
+
|
|
340
|
+
if (!trace) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const span = trace.spans.find(s => s.id === spanId);
|
|
345
|
+
|
|
346
|
+
if (!span) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
span.endTime = Date.now();
|
|
351
|
+
span.duration = span.endTime - span.startTime;
|
|
352
|
+
|
|
353
|
+
// Track specific metrics based on span class
|
|
354
|
+
if (span.name.includes('blockchain')) {
|
|
355
|
+
this.metrics.blockchainCalls++;
|
|
356
|
+
this.metrics.blockchainDuration += span.duration;
|
|
357
|
+
} else if (span.name.includes('auth')) {
|
|
358
|
+
this.metrics.authenticationCalls++;
|
|
359
|
+
this.metrics.authenticationDuration += span.duration;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const logLevel = this._getDurationLogLevel(span.duration);
|
|
363
|
+
|
|
364
|
+
logger[logLevel]('Span completed', {
|
|
365
|
+
component: 'PerformanceService',
|
|
366
|
+
method: 'stopSpan',
|
|
367
|
+
traceId,
|
|
368
|
+
spanId,
|
|
369
|
+
spanName: span.name,
|
|
370
|
+
duration: span.duration
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Complete a trace with results
|
|
376
|
+
*
|
|
377
|
+
* @param {string} traceId - ID of the trace to complete
|
|
378
|
+
* @param {Object} results - Results of the operation
|
|
379
|
+
* @returns {boolean} Whether the trace was successfully completed
|
|
380
|
+
*/
|
|
381
|
+
completeTrace(traceId, results = {}) {
|
|
382
|
+
if (!this.traces.has(traceId)) {
|
|
383
|
+
logger.warn(`Attempted to complete unknown trace: ${traceId}`, {
|
|
384
|
+
component: 'PerformanceService',
|
|
385
|
+
method: 'completeTrace'
|
|
386
|
+
});
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const trace = this.traces.get(traceId);
|
|
391
|
+
if (trace.completed) {
|
|
392
|
+
logger.warn(`Attempted to complete already completed trace: ${traceId}`, {
|
|
393
|
+
component: 'PerformanceService',
|
|
394
|
+
method: 'completeTrace'
|
|
395
|
+
});
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const endTime = Date.now();
|
|
400
|
+
const duration = endTime - trace.startTime;
|
|
401
|
+
|
|
402
|
+
// Update the trace with completion info
|
|
403
|
+
trace.completed = true;
|
|
404
|
+
trace.endTime = endTime;
|
|
405
|
+
trace.duration = duration;
|
|
406
|
+
trace.results = results;
|
|
407
|
+
|
|
408
|
+
// Log trace completion as a metric
|
|
409
|
+
logger.metric('trace_duration_ms', duration, {
|
|
410
|
+
operation: trace.operation,
|
|
411
|
+
component: 'PerformanceService',
|
|
412
|
+
status: results.success !== false ? 'success' : 'failure',
|
|
413
|
+
error: results.error ? 'true' : 'false',
|
|
414
|
+
status_code: results.statusCode || 0
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// If there was an error, log an error metric
|
|
418
|
+
if (results.error) {
|
|
419
|
+
logger.metric('trace_errors_total', 1, {
|
|
420
|
+
operation: trace.operation,
|
|
421
|
+
component: 'PerformanceService',
|
|
422
|
+
error_type: typeof results.error === 'string' ? results.error : 'unknown'
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
logger.debug(`Completed trace for ${trace.operation}`, {
|
|
427
|
+
component: 'PerformanceService',
|
|
428
|
+
method: 'completeTrace',
|
|
429
|
+
traceId,
|
|
430
|
+
operation: trace.operation,
|
|
431
|
+
duration,
|
|
432
|
+
success: results.success !== false,
|
|
433
|
+
error: results.error,
|
|
434
|
+
metadata: trace.metadata ? JSON.stringify(trace.metadata) : null
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* End a trace (alias for completeTrace)
|
|
442
|
+
*
|
|
443
|
+
* @param {string} traceId - ID of the trace to end
|
|
444
|
+
* @param {Object} result - Result of the operation
|
|
445
|
+
* @returns {Object} Completed trace with metrics
|
|
446
|
+
*/
|
|
447
|
+
endTrace(traceId, result = {}) {
|
|
448
|
+
return this.completeTrace(traceId, result);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Get a trace by ID
|
|
453
|
+
*
|
|
454
|
+
* @param {string} traceId - ID of the trace to retrieve
|
|
455
|
+
* @returns {Object} Trace object
|
|
456
|
+
*/
|
|
457
|
+
getTrace(traceId) {
|
|
458
|
+
return this.traces.get(traceId);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Get current performance metrics
|
|
463
|
+
*
|
|
464
|
+
* @returns {Object} Current metrics
|
|
465
|
+
*/
|
|
466
|
+
getMetrics() {
|
|
467
|
+
const rpm = this._computeRequestsPerMinute();
|
|
468
|
+
const reqCount = this.metrics.requestCount || 0;
|
|
469
|
+
const errCount = this.metrics.errorCount || 0;
|
|
470
|
+
const minDur =
|
|
471
|
+
this.metrics.minDuration === Number.MAX_SAFE_INTEGER ? null : this.metrics.minDuration;
|
|
472
|
+
|
|
473
|
+
return {
|
|
474
|
+
...this.metrics,
|
|
475
|
+
minDuration: minDur === null ? 0 : minDur,
|
|
476
|
+
requestsPerMinute: rpm,
|
|
477
|
+
currentLoadLevel: this._computeLoadLevel(rpm, reqCount, errCount),
|
|
478
|
+
endpointMetrics: this._serializeEndpointMetrics(),
|
|
479
|
+
countersScope: 'process',
|
|
480
|
+
instance: {
|
|
481
|
+
hostname: os.hostname(),
|
|
482
|
+
pid: process.pid,
|
|
483
|
+
processStartedAt: new Date(this._processStartedAt).toISOString(),
|
|
484
|
+
lastCountersResetAt: this._lastResetAt ? new Date(this._lastResetAt).toISOString() : null
|
|
485
|
+
},
|
|
486
|
+
rollingWindow: {
|
|
487
|
+
id: 'http_requests',
|
|
488
|
+
spanSeconds: RPM_WINDOW_SEC,
|
|
489
|
+
note: 'requestsPerMinute sums HTTP requests recorded in the rolling window (per process)'
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Reset performance metrics
|
|
496
|
+
*/
|
|
497
|
+
resetMetrics() {
|
|
498
|
+
this.metrics = {
|
|
499
|
+
requestCount: 0,
|
|
500
|
+
errorCount: 0,
|
|
501
|
+
totalDuration: 0,
|
|
502
|
+
maxDuration: 0,
|
|
503
|
+
minDuration: Number.MAX_SAFE_INTEGER,
|
|
504
|
+
blockchainCalls: 0,
|
|
505
|
+
blockchainDuration: 0,
|
|
506
|
+
authenticationCalls: 0,
|
|
507
|
+
authenticationDuration: 0
|
|
508
|
+
};
|
|
509
|
+
this._lastResetAt = Date.now();
|
|
510
|
+
this._requestsBySecond.clear();
|
|
511
|
+
this._endpointStats.clear();
|
|
512
|
+
|
|
513
|
+
logger.info('Performance metrics reset', {
|
|
514
|
+
component: 'PerformanceService',
|
|
515
|
+
method: 'resetMetrics'
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Get appropriate log level based on duration
|
|
521
|
+
* @private
|
|
522
|
+
*
|
|
523
|
+
* @param {number} duration - Operation duration in ms
|
|
524
|
+
* @returns {string} Log level to use
|
|
525
|
+
*/
|
|
526
|
+
_getDurationLogLevel(duration) {
|
|
527
|
+
if (duration > 1000) {
|
|
528
|
+
return 'warn'; // Over 1 second
|
|
529
|
+
} else if (duration > 500) {
|
|
530
|
+
return 'info'; // 500ms - 1 second
|
|
531
|
+
} else {
|
|
532
|
+
return 'debug'; // Under 500ms
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Get health resource usage metrics
|
|
538
|
+
*
|
|
539
|
+
* @returns {Object} System resource metrics
|
|
540
|
+
*/
|
|
541
|
+
getSystemMetrics() {
|
|
542
|
+
const cpuUsage = process.cpuUsage();
|
|
543
|
+
const memoryUsage = process.memoryUsage();
|
|
544
|
+
|
|
545
|
+
return {
|
|
546
|
+
cpu: {
|
|
547
|
+
user: cpuUsage.user,
|
|
548
|
+
system: cpuUsage.system,
|
|
549
|
+
loadAvg: os.loadavg()
|
|
550
|
+
},
|
|
551
|
+
memory: {
|
|
552
|
+
rss: memoryUsage.rss,
|
|
553
|
+
heapTotal: memoryUsage.heapTotal,
|
|
554
|
+
heapUsed: memoryUsage.heapUsed,
|
|
555
|
+
external: memoryUsage.external,
|
|
556
|
+
arrayBuffers: memoryUsage.arrayBuffers
|
|
557
|
+
},
|
|
558
|
+
uptime: process.uptime(),
|
|
559
|
+
timestamp: Date.now()
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Create and export singleton instance
|
|
565
|
+
const performanceService = new PerformanceService();
|
|
566
|
+
// Initialize the service
|
|
567
|
+
performanceService.initialize();
|
|
568
|
+
module.exports = performanceService;
|