Full codebase including API server (32 modules + routes), dashboard frontend, DashCA certificate distribution, installer script, and deployment skills.
116 lines
3.2 KiB
JavaScript
116 lines
3.2 KiB
JavaScript
/**
|
|
* Simple metrics collector for DashCaddy API
|
|
* Tracks request counts, durations, errors, and business metrics
|
|
* No external dependencies — all in-memory
|
|
*/
|
|
|
|
class Metrics {
|
|
constructor() {
|
|
this.startTime = Date.now();
|
|
this.requests = {
|
|
total: 0,
|
|
byStatus: {},
|
|
byMethod: {},
|
|
byPath: {}
|
|
};
|
|
this.errors = {
|
|
total: 0,
|
|
byType: {}
|
|
};
|
|
this.business = {
|
|
containersDeployed: 0,
|
|
containersDeleted: 0,
|
|
containerUpdates: 0,
|
|
dnsRecordsCreated: 0,
|
|
backupsCreated: 0,
|
|
totpLogins: 0,
|
|
siteAdded: 0,
|
|
siteRemoved: 0,
|
|
credentialRotations: 0
|
|
};
|
|
}
|
|
|
|
recordRequest(method, path, statusCode, durationMs) {
|
|
this.requests.total++;
|
|
this.requests.byStatus[statusCode] = (this.requests.byStatus[statusCode] || 0) + 1;
|
|
this.requests.byMethod[method] = (this.requests.byMethod[method] || 0) + 1;
|
|
|
|
const normalized = this.normalizePath(path);
|
|
if (!this.requests.byPath[normalized]) {
|
|
this.requests.byPath[normalized] = { count: 0, totalDuration: 0 };
|
|
}
|
|
const entry = this.requests.byPath[normalized];
|
|
entry.count++;
|
|
entry.totalDuration += durationMs;
|
|
}
|
|
|
|
recordError(errorType) {
|
|
this.errors.total++;
|
|
this.errors.byType[errorType] = (this.errors.byType[errorType] || 0) + 1;
|
|
}
|
|
|
|
recordBusinessEvent(eventType) {
|
|
if (eventType in this.business) {
|
|
this.business[eventType]++;
|
|
}
|
|
}
|
|
|
|
normalizePath(p) {
|
|
return p
|
|
.replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '/:id')
|
|
.replace(/\/[0-9a-f]{12,}/gi, '/:id')
|
|
.replace(/\/\d+/g, '/:n');
|
|
}
|
|
|
|
getSummary() {
|
|
const uptimeMs = Date.now() - this.startTime;
|
|
const uptimeSec = Math.floor(uptimeMs / 1000);
|
|
|
|
const topEndpoints = Object.entries(this.requests.byPath)
|
|
.sort((a, b) => b[1].count - a[1].count)
|
|
.slice(0, 15)
|
|
.map(([path, s]) => ({ path, count: s.count, avgMs: Math.round(s.totalDuration / s.count) }));
|
|
|
|
return {
|
|
uptime: { ms: uptimeMs, human: this.formatUptime(uptimeSec) },
|
|
requests: {
|
|
total: this.requests.total,
|
|
perSecond: uptimeSec > 0 ? +(this.requests.total / uptimeSec).toFixed(2) : 0,
|
|
byStatus: this.requests.byStatus,
|
|
byMethod: this.requests.byMethod,
|
|
topEndpoints
|
|
},
|
|
errors: {
|
|
total: this.errors.total,
|
|
rate: this.requests.total > 0 ? +((this.errors.total / this.requests.total) * 100).toFixed(2) : 0,
|
|
byType: this.errors.byType
|
|
},
|
|
business: this.business,
|
|
process: {
|
|
memory: process.memoryUsage(),
|
|
pid: process.pid,
|
|
nodeVersion: process.version
|
|
}
|
|
};
|
|
}
|
|
|
|
formatUptime(sec) {
|
|
const d = Math.floor(sec / 86400);
|
|
const h = Math.floor((sec % 86400) / 3600);
|
|
const m = Math.floor((sec % 3600) / 60);
|
|
const s = sec % 60;
|
|
if (d > 0) return `${d}d ${h}h ${m}m`;
|
|
if (h > 0) return `${h}h ${m}m ${s}s`;
|
|
if (m > 0) return `${m}m ${s}s`;
|
|
return `${s}s`;
|
|
}
|
|
|
|
reset() {
|
|
this.startTime = Date.now();
|
|
this.requests = { total: 0, byStatus: {}, byMethod: {}, byPath: {} };
|
|
this.errors = { total: 0, byType: {} };
|
|
}
|
|
}
|
|
|
|
module.exports = new Metrics();
|