@yihuangdb/storage-object
Version:
A Node.js storage object layer library using Redis OM
224 lines ⢠8.35 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.profiler = exports.PerformanceProfiler = void 0;
class PerformanceProfiler {
static instance;
metrics = [];
enabled = false;
slowThreshold = 100; // ms
timers = new Map();
constructor() {
// Enable profiling in development or when PROFILE env is set
this.enabled = process.env.NODE_ENV === 'development' ||
process.env.PROFILE === 'true' ||
process.env.DEBUG_PERFORMANCE === 'true';
}
static getInstance() {
if (!PerformanceProfiler.instance) {
PerformanceProfiler.instance = new PerformanceProfiler();
}
return PerformanceProfiler.instance;
}
enable() {
this.enabled = true;
console.log('š Performance profiling enabled');
}
disable() {
this.enabled = false;
}
setSlowThreshold(ms) {
this.slowThreshold = ms;
}
startTimer(operation, details) {
if (!this.enabled) {
return;
}
const key = `${operation}_${Date.now()}_${Math.random()}`;
this.timers.set(key, performance.now());
if (details) {
// Log operation start with details
if (process.env.DEBUG_PERFORMANCE === 'true') {
console.log(`ā±ļø START: ${operation}`, details);
}
}
}
endTimer(operation, details) {
if (!this.enabled) {
return 0;
}
// Find the most recent timer for this operation
let timerKey = null;
let startTime = null;
for (const [key, time] of this.timers.entries()) {
if (key.startsWith(`${operation}_`)) {
timerKey = key;
startTime = time;
break;
}
}
if (!timerKey || startTime === null) {
console.warn(`No timer found for operation: ${operation}`);
return 0;
}
const endTime = performance.now();
const duration = endTime - startTime;
this.timers.delete(timerKey);
const metric = {
operation,
duration,
timestamp: new Date(),
details,
};
this.metrics.push(metric);
// Log slow operations
if (duration > this.slowThreshold) {
console.warn(`š SLOW OPERATION: ${operation} took ${duration.toFixed(2)}ms`, details || '');
}
else if (process.env.DEBUG_PERFORMANCE === 'true') {
console.log(`ā±ļø END: ${operation} took ${duration.toFixed(2)}ms`, details || '');
}
return duration;
}
async measure(operation, fn, details) {
if (!this.enabled) {
return fn();
}
const startTime = performance.now();
try {
const result = await fn();
const duration = performance.now() - startTime;
const metric = {
operation,
duration,
timestamp: new Date(),
details,
};
this.metrics.push(metric);
if (duration > this.slowThreshold) {
console.warn(`š SLOW: ${operation} took ${duration.toFixed(2)}ms`, details || '');
}
return result;
}
catch (error) {
const duration = performance.now() - startTime;
console.error(`ā ERROR in ${operation} after ${duration.toFixed(2)}ms:`, error);
throw error;
}
}
measureSync(operation, fn, details) {
if (!this.enabled) {
return fn();
}
const startTime = performance.now();
try {
const result = fn();
const duration = performance.now() - startTime;
const metric = {
operation,
duration,
timestamp: new Date(),
details,
};
this.metrics.push(metric);
if (duration > this.slowThreshold) {
console.warn(`š SLOW: ${operation} took ${duration.toFixed(2)}ms`, details || '');
}
return result;
}
catch (error) {
const duration = performance.now() - startTime;
console.error(`ā ERROR in ${operation} after ${duration.toFixed(2)}ms:`, error);
throw error;
}
}
getMetrics() {
return [...this.metrics];
}
getAverageByOperation() {
const operationStats = new Map();
for (const metric of this.metrics) {
const existing = operationStats.get(metric.operation) || { avg: 0, count: 0, total: 0, max: 0, min: Infinity };
existing.count++;
existing.total += metric.duration;
existing.avg = existing.total / existing.count;
existing.max = Math.max(existing.max, metric.duration);
existing.min = Math.min(existing.min, metric.duration);
operationStats.set(metric.operation, existing);
}
return operationStats;
}
getSlowestOperations(limit = 10) {
return [...this.metrics]
.sort((a, b) => b.duration - a.duration)
.slice(0, limit);
}
getBottlenecks() {
const totalTime = this.metrics.reduce((sum, m) => sum + m.duration, 0);
const operationTotals = new Map();
for (const metric of this.metrics) {
const current = operationTotals.get(metric.operation) || 0;
operationTotals.set(metric.operation, current + metric.duration);
}
return Array.from(operationTotals.entries())
.map(([operation, time]) => ({
operation,
totalTime: time,
percentage: (time / totalTime) * 100,
}))
.sort((a, b) => b.totalTime - a.totalTime);
}
printReport() {
if (!this.enabled || this.metrics.length === 0) {
console.log('No performance metrics collected');
return;
}
console.log('\n' + '='.repeat(80));
console.log('š PERFORMANCE REPORT');
console.log('='.repeat(80));
// Overall stats
const totalTime = this.metrics.reduce((sum, m) => sum + m.duration, 0);
console.log('\nš Overall Statistics:');
console.log(` Total operations: ${this.metrics.length}`);
console.log(` Total time: ${totalTime.toFixed(2)}ms`);
console.log(` Average time: ${(totalTime / this.metrics.length).toFixed(2)}ms`);
// Operation averages
console.log('\nā±ļø Operation Averages:');
const averages = this.getAverageByOperation();
for (const [op, stats] of averages.entries()) {
console.log(` ${op}:`);
console.log(` Count: ${stats.count}`);
console.log(` Avg: ${stats.avg.toFixed(2)}ms`);
console.log(` Min: ${stats.min.toFixed(2)}ms`);
console.log(` Max: ${stats.max.toFixed(2)}ms`);
console.log(` Total: ${stats.total.toFixed(2)}ms`);
}
// Slowest operations
console.log('\nš Slowest Operations:');
const slowest = this.getSlowestOperations(5);
for (const metric of slowest) {
console.log(` ${metric.operation}: ${metric.duration.toFixed(2)}ms`);
if (metric.details) {
console.log(' Details:', metric.details);
}
}
// Bottlenecks
console.log('\nš„ Bottlenecks (% of total time):');
const bottlenecks = this.getBottlenecks();
for (const bottleneck of bottlenecks.slice(0, 5)) {
const bar = 'ā'.repeat(Math.round(bottleneck.percentage / 2));
console.log(` ${bottleneck.operation}: ${bar} ${bottleneck.percentage.toFixed(1)}% (${bottleneck.totalTime.toFixed(2)}ms)`);
}
console.log('\n' + '='.repeat(80));
}
reset() {
this.metrics = [];
this.timers.clear();
}
exportMetrics() {
return JSON.stringify(this.metrics, null, 2);
}
}
exports.PerformanceProfiler = PerformanceProfiler;
// Export singleton instance for convenience
exports.profiler = PerformanceProfiler.getInstance();
//# sourceMappingURL=performance-profiler.js.map
;