Initial commit: DashCaddy v1.0
Full codebase including API server (32 modules + routes), dashboard frontend, DashCA certificate distribution, installer script, and deployment skills.
This commit is contained in:
115
dashcaddy-api/metrics.js
Normal file
115
dashcaddy-api/metrics.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 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();
|
||||
Reference in New Issue
Block a user