bodhi-node-profiler
Version:
A lightweight, zero-configuration performance profiler for Node.js applications with real-time dashboard
252 lines (224 loc) • 7.92 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 updateSystemMetrics = (metrics) => {
// Update CPU Usage
const cpuElement = document.getElementById('cpu-usage');
const cpuValue = metrics.cpu.percentage.toFixed(1);
cpuElement.textContent = `${cpuValue}%`;
updateStatus('cpu-status', cpuValue);
// Update Memory Usage
const memoryElement = document.getElementById('memory-usage');
const memoryValue = metrics.memory.percentage;
memoryElement.textContent = `${memoryValue}%`;
updateStatus('memory-status', memoryValue);
// Update Event Loop Delay
const eventLoopElement = document.getElementById('event-loop-delay');
const eventLoopValue = metrics.eventLoop.latency.toFixed(1);
eventLoopElement.textContent = `${eventLoopValue} ms`;
updateStatus('event-loop-status', eventLoopValue);
// Update API Response
const apiElement = document.getElementById('api-response');
const apiValue = metrics.api.responseTime.toFixed(1);
apiElement.textContent = `${apiValue} ms`;
updateStatus('api-status', apiValue);
// Update System Information
const totalMemoryElement = document.getElementById('total-memory');
totalMemoryElement.textContent = `${metrics.memory.heapTotal} MB`;
const uptimeElement = document.getElementById('uptime');
const uptime = parseInt(metrics.uptime);
let uptimeText = '';
if (uptime < 60) {
uptimeText = `${uptime}s`;
} else if (uptime < 3600) {
uptimeText = `${Math.floor(uptime / 60)}m ${uptime % 60}s`;
} else {
const hours = Math.floor(uptime / 3600);
const minutes = Math.floor((uptime % 3600) / 60);
uptimeText = `${hours}h ${minutes}m`;
}
uptimeElement.textContent = uptimeText;
const lastUpdatedElement = document.getElementById('last-updated');
const date = new Date(metrics.timestamp);
const timeStr = date.toLocaleTimeString();
lastUpdatedElement.textContent = timeStr;
// Hide all "Collecting data..." messages
document.querySelectorAll('.collecting-data').forEach(el => {
el.style.display = 'none';
});
};
const updateStatus = (id, value) => {
const statusEl = document.getElementById(id);
let status;
const numValue = parseFloat(value);
if (numValue >= 80) {
status = { class: 'bg-red-100 text-red-800', text: 'High' };
} else if (numValue >= 60) {
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;
};
const updateCharts = (metrics) => {
const timestamp = new Date(metrics.timestamp);
// Update CPU chart
charts.cpu.data.labels.push(formatTime(timestamp));
charts.cpu.data.datasets[0].data.push(metrics.cpu.percentage);
if (charts.cpu.data.labels.length > 20) {
charts.cpu.data.labels.shift();
charts.cpu.data.datasets[0].data.shift();
}
charts.cpu.update('none');
// Update Memory chart
charts.memory.data.labels.push(formatTime(timestamp));
charts.memory.data.datasets[0].data.push(metrics.memory.percentage);
if (charts.memory.data.labels.length > 20) {
charts.memory.data.labels.shift();
charts.memory.data.datasets[0].data.shift();
}
charts.memory.update('none');
// Update Event Loop chart
charts.eventLoop.data.labels.push(formatTime(timestamp));
charts.eventLoop.data.datasets[0].data.push(metrics.eventLoop.latency);
if (charts.eventLoop.data.labels.length > 20) {
charts.eventLoop.data.labels.shift();
charts.eventLoop.data.datasets[0].data.shift();
}
charts.eventLoop.update('none');
// Update API Response Time chart
charts.api.data.labels.push(formatTime(timestamp));
charts.api.data.datasets[0].data.push(metrics.api.responseTime);
if (charts.api.data.labels.length > 20) {
charts.api.data.labels.shift();
charts.api.data.datasets[0].data.shift();
}
charts.api.update('none');
};
const formatTime = (date) => {
return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
};
const fetchMetrics = async () => {
try {
console.log('Fetching metrics...');
const response = await fetch('/profiler/stats');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const metrics = await response.json();
console.log('Received metrics:', metrics);
updateCharts(metrics);
updateSystemMetrics(metrics);
} catch (error) {
console.error('Error fetching metrics:', error);
}
};
// Initialize
document.addEventListener('DOMContentLoaded', () => {
console.log('Initializing dashboard...');
initializeCharts();
fetchMetrics(); // Initial fetch
setInterval(fetchMetrics, 1000); // Poll every second
});