bodhi-node-profiler
Version:
A lightweight, zero-configuration performance profiler for Node.js applications with real-time dashboard
231 lines (207 loc) • 6.94 kB
JavaScript
let charts = {
cpu: null,
memory: null,
eventLoop: null,
api: null
};
const startTime = Date.now();
const createChart = (ctx, label, options = {}) => {
return new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: label,
data: [],
borderColor: 'rgb(99, 102, 241)',
backgroundColor: 'rgba(99, 102, 241, 0.1)',
borderWidth: 2,
fill: true,
tension: 0.4,
pointRadius: 0,
pointHitRadius: 20
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: {
duration: 0
},
plugins: {
legend: {
display: false
},
tooltip: {
mode: 'index',
intersect: false,
callbacks: {
label: function(context) {
return `${label}: ${context.parsed.y.toFixed(2)}${options.unit || ''}`;
}
}
}
},
scales: {
x: {
grid: {
display: false
}
},
y: {
beginAtZero: true,
grid: {
color: 'rgba(0, 0, 0, 0.05)'
},
...options.scales?.y
}
}
}
});
};
const initializeCharts = () => {
charts.cpu = createChart(document.getElementById('cpuChart').getContext('2d'), 'CPU Usage', {
unit: '%',
scales: {
y: {
max: 100,
ticks: {
callback: value => value + '%'
}
}
}
});
charts.memory = createChart(document.getElementById('memoryChart').getContext('2d'), 'Memory', {
unit: ' MB',
scales: {
y: {
ticks: {
callback: value => value.toFixed(0) + ' MB'
}
}
}
});
charts.eventLoop = createChart(document.getElementById('eventLoopChart').getContext('2d'), 'Delay', {
unit: ' ms',
scales: {
y: {
ticks: {
callback: value => value.toFixed(1) + ' ms'
}
}
}
});
charts.api = createChart(document.getElementById('apiChart').getContext('2d'), 'Response Time', {
unit: ' ms',
scales: {
y: {
ticks: {
callback: value => value.toFixed(0) + ' ms'
}
}
}
});
};
const updateCharts = (metrics) => {
const timestamp = moment(metrics.timestamp).format('HH:mm:ss');
// Update CPU Chart
charts.cpu.data.labels.push(timestamp);
charts.cpu.data.datasets[0].data.push(parseFloat(metrics.cpu.percentage));
if (charts.cpu.data.labels.length > 30) {
charts.cpu.data.labels.shift();
charts.cpu.data.datasets[0].data.shift();
}
charts.cpu.update();
// Update Memory Chart
const memoryUsageMB = metrics.memory.heapUsed / (1024 * 1024);
charts.memory.data.labels.push(timestamp);
charts.memory.data.datasets[0].data.push(memoryUsageMB);
if (charts.memory.data.labels.length > 30) {
charts.memory.data.labels.shift();
charts.memory.data.datasets[0].data.shift();
}
charts.memory.update();
// Update Event Loop Chart
charts.eventLoop.data.labels.push(timestamp);
charts.eventLoop.data.datasets[0].data.push(metrics.eventLoopDelay);
if (charts.eventLoop.data.labels.length > 30) {
charts.eventLoop.data.labels.shift();
charts.eventLoop.data.datasets[0].data.shift();
}
charts.eventLoop.update();
// Update API Chart
if (metrics.api) {
charts.api.data.labels.push(timestamp);
charts.api.data.datasets[0].data.push(metrics.api.responseTime);
if (charts.api.data.labels.length > 30) {
charts.api.data.labels.shift();
charts.api.data.datasets[0].data.shift();
}
charts.api.update();
}
};
const updateMetricCard = (id, value, unit, trend, thresholds) => {
const statusEl = document.getElementById(`${id}Status`);
const valueEl = document.getElementById(`${id}Value`);
const trendEl = document.getElementById(`${id}Trend`);
valueEl.textContent = `${value.toFixed(1)}${unit}`;
let status;
if (value >= thresholds.high) {
status = { class: 'bg-red-100 text-red-800', text: 'High' };
} else if (value >= thresholds.warning) {
status = { class: 'bg-yellow-100 text-yellow-800', text: 'Warning' };
} else {
status = { class: 'bg-green-100 text-green-800', text: 'Good' };
}
statusEl.className = `px-3 py-1 rounded-full text-sm font-medium ${status.class}`;
statusEl.textContent = status.text;
if (trend) {
const trendText = trend > 0 ? `↑ ${trend.toFixed(1)}% from last min` : `↓ ${Math.abs(trend).toFixed(1)}% from last min`;
trendEl.textContent = trendText;
}
};
const updateSystemMetrics = (metrics) => {
// CPU Metrics
updateMetricCard('cpu', parseFloat(metrics.cpu.percentage), '%', null, {
warning: 60,
high: 80
});
// Memory Metrics
const memoryUsage = (metrics.memory.heapUsed / metrics.memory.heapTotal) * 100;
updateMetricCard('memory', memoryUsage, '%', null, {
warning: 70,
high: 85
});
// Event Loop Metrics
updateMetricCard('eventLoop', metrics.eventLoopDelay, ' ms', null, {
warning: 50,
high: 100
});
// API Metrics
if (metrics.api) {
updateMetricCard('api', metrics.api.responseTime, ' ms', null, {
warning: 500,
high: 1000
});
}
// System Info
document.getElementById('totalMemory').textContent = `${(metrics.memory.heapTotal / (1024 * 1024)).toFixed(0)} MB`;
document.getElementById('uptime').textContent = moment.duration(Date.now() - startTime).humanize();
document.getElementById('lastUpdated').textContent = moment(metrics.timestamp).format('HH:mm:ss');
};
const fetchMetrics = async () => {
try {
const response = await fetch('/profiler/stats');
const metrics = await response.json();
updateCharts(metrics);
updateSystemMetrics(metrics);
} catch (error) {
console.error('Error fetching metrics:', error);
}
};
// Initialize
document.addEventListener('DOMContentLoaded', () => {
initializeCharts();
fetchMetrics(); // Initial fetch
setInterval(fetchMetrics, 1000);
});