Skip to content

Commit d93de1f

Browse files
committed
add /history
1 parent 2106ae1 commit d93de1f

1 file changed

Lines changed: 166 additions & 18 deletions

File tree

latency.js

Lines changed: 166 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ const http = require('http')
33
const WebSocket = require('ws')
44
const fetch = require('node-fetch')
55

6+
const fs = require('fs');
7+
const DATA_FILE = 'latency_data.jsonl';
8+
9+
const MAX_STORAGE_TIME_MS = 24 * 60 * 60 * 1000; // сутки
10+
let measureCount = 0;
11+
12+
613
const NAMES = [
714
'scv',
815
'admin',
@@ -109,31 +116,140 @@ app.get('/', (req, res) => {
109116
});
110117
111118
const socket = new WebSocket('ws://' + location.host);
112-
socket.onmessage = (event) => {
113-
const msg = JSON.parse(event.data);
114-
const points = msg.averages;
115-
116-
chart.data.datasets[0].data = points.map(p => ({ x: new Date(p.time), y: p.avg5s, customData: p.reqInfo }));
117-
chart.data.datasets[1].data = points.map(p => ({ x: new Date(p.time), y: p.avg1min, customData: p.reqInfo }));
118-
chart.data.datasets[2].data = points.map(p => ({ x: new Date(p.time), y: p.avg1h, customData: p.reqInfo }));
119-
120-
if (points.length > 0) {
121-
const latestTime = new Date(points[points.length - 1].time).getTime();
122-
const RANGE = 5 * 60 * 1000; // 5 минут в мс
123-
chart.options.scales.x.min = latestTime - RANGE / 2;
124-
chart.options.scales.x.max = latestTime + RANGE / 2;
119+
socket.onmessage = (event) => {
120+
const msg = JSON.parse(event.data);
121+
const points = msg.averages;
122+
123+
chart.data.datasets[0].data = points.map(p => ({ x: new Date(p.time), y: p.avg5s, customData: p.reqInfo }));
124+
chart.data.datasets[1].data = points.map(p => ({ x: new Date(p.time), y: p.avg1min, customData: p.reqInfo }));
125+
chart.data.datasets[2].data = points.map(p => ({ x: new Date(p.time), y: p.avg1h, customData: p.reqInfo }));
126+
127+
if (points.length > 0) {
128+
const latestTime = new Date(points[points.length - 1].time).getTime();
129+
const RANGE = 5 * 60 * 1000; // 5 минут в мс
130+
chart.options.scales.x.min = latestTime - RANGE;
131+
chart.options.scales.x.max = latestTime;
132+
}
133+
134+
chart.data.datasets[0].borderColor = msg.colors[0];
135+
chart.data.datasets[1].borderColor = msg.colors[1];
136+
chart.data.datasets[2].borderColor = msg.colors[2];
137+
chart.update();
138+
};
139+
</script>
140+
</body>
141+
</html>`);
142+
});
143+
144+
app.get('/history', (req, res) => {
145+
const timeParam = parseInt(req.query.time || '60'); // по умолчанию 60 минут
146+
const timeRangeMs = timeParam * 60 * 1000;
147+
const now = Date.now();
148+
const points = [];
149+
150+
if (!fs.existsSync(DATA_FILE)) {
151+
return res.send('No data available.');
152+
}
153+
154+
const lines = fs.readFileSync(DATA_FILE, 'utf8').split('\n');
155+
for (const line of lines) {
156+
if (!line.trim()) continue;
157+
try {
158+
const point = JSON.parse(line);
159+
const pointTime = new Date(point.time).getTime();
160+
if (now - pointTime <= timeRangeMs) {
161+
points.push(point);
125162
}
163+
} catch (e) {
164+
console.error('Error parsing history line:', e.message);
165+
}
166+
}
126167

127-
chart.data.datasets[0].borderColor = msg.colors[0];
128-
chart.data.datasets[1].borderColor = msg.colors[1];
129-
chart.data.datasets[2].borderColor = msg.colors[2];
130-
chart.update();
131-
};
168+
res.send(`<!DOCTYPE html>
169+
<html>
170+
<head>
171+
<meta charset="utf-8">
172+
<title>Latency History (Last ${timeParam} min)</title>
173+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
174+
<script src="https://cdn.jsdelivr.net/npm/luxon@3/build/global/luxon.min.js"></script>
175+
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@1"></script>
176+
<style>
177+
body { background-color: #1e1e1e; color: #ccc; }
178+
canvas { background-color: #2e2e2e; }
179+
</style>
180+
</head>
181+
<body>
182+
<h2>Latency History - Last ${timeParam} minutes</h2>
183+
<canvas id="latencyChart" width="800" height="400"></canvas>
184+
<script>
185+
const ctx = document.getElementById('latencyChart').getContext('2d');
186+
const chart = new Chart(ctx, {
187+
type: 'line',
188+
data: {
189+
datasets: [
190+
{
191+
label: 'Latency',
192+
data: [],
193+
borderWidth: 2,
194+
fill: false,
195+
borderColor: 'rgba(0,200,255,1)',
196+
}
197+
]
198+
},
199+
options: {
200+
animation: false,
201+
plugins: {
202+
legend: {
203+
labels: { color: '#ccc' },
204+
display: true,
205+
position: 'top'
206+
},
207+
tooltip: {
208+
callbacks: {
209+
label: function(context) {
210+
const point = context.raw;
211+
const latency = point.y;
212+
const reqInfo = point.customData || 'n/a';
213+
return 'Latency: ' + latency.toFixed(1) + ' ms | ' + reqInfo;
214+
}
215+
}
216+
}
217+
},
218+
scales: {
219+
x: {
220+
type: 'time',
221+
time: {
222+
unit: 'minute',
223+
displayFormats: { minute: 'HH:mm:ss' }
224+
},
225+
ticks: { color: '#ccc' },
226+
grid: { color: '#444' }
227+
},
228+
y: {
229+
beginAtZero: true,
230+
title: { display: true, text: 'Latency (ms)', color: '#ccc' },
231+
ticks: { color: '#ccc' },
232+
grid: { color: '#444' }
233+
}
234+
}
235+
}
236+
});
237+
238+
const dataPoints = ${JSON.stringify(points)};
239+
chart.data.datasets[0].data = dataPoints.map(p => ({ x: new Date(p.time), y: p.latency, customData: p.reqInfo }));
240+
if (dataPoints.length > 0) {
241+
const latestTime = new Date(dataPoints[dataPoints.length - 1].time).getTime();
242+
const RANGE = ${timeParam} * 60 * 1000;
243+
chart.options.scales.x.min = latestTime - RANGE;
244+
chart.options.scales.x.max = latestTime;
245+
}
246+
chart.update();
132247
</script>
133248
</body>
134249
</html>`);
135250
});
136251

252+
137253
wss.on('connection', ws => {
138254
console.log('Client connected');
139255
ws.send(JSON.stringify({ averages: calculateAverages(latencyData), colors: ['rgba(0,255,0,1)', 'rgba(0,200,0,1)', 'rgba(0,150,0,1)'] }));
@@ -155,6 +271,15 @@ function measureLatency() {
155271
latencyData.push(point);
156272
if (latencyData.length > MAX_POINTS) latencyData.shift();
157273

274+
measureCount++;
275+
if (measureCount % 100 === 0) {
276+
pruneOldData();
277+
}
278+
279+
fs.appendFile(DATA_FILE, JSON.stringify(point) + '\n', (err) => {
280+
if (err) console.error('Error writing data file:', err.message);
281+
});
282+
158283
const averages = calculateAverages(latencyData);
159284
const colors = statusToColors(res.status);
160285
const dataToSend = JSON.stringify({ averages, colors });
@@ -208,6 +333,29 @@ function calculateAverages(data) {
208333
});
209334
}
210335

336+
function pruneOldData() {
337+
if (!fs.existsSync(DATA_FILE)) return;
338+
const now = Date.now();
339+
try {
340+
const lines = fs.readFileSync(DATA_FILE, 'utf8').split('\n');
341+
const recentLines = lines.filter(line => {
342+
if (!line.trim()) return false;
343+
try {
344+
const point = JSON.parse(line);
345+
const pointTime = new Date(point.time).getTime();
346+
return now - pointTime <= MAX_STORAGE_TIME_MS;
347+
} catch (e) {
348+
return false;
349+
}
350+
});
351+
fs.writeFileSync(DATA_FILE, recentLines.join('\n') + '\n');
352+
console.log(`Pruned old data, remaining points: ${recentLines.length}`);
353+
} catch (e) {
354+
console.error('Error pruning data file:', e.message);
355+
}
356+
}
357+
358+
211359
function average(arr) {
212360
if (arr.length === 0) return 0;
213361
return arr.reduce((sum, v) => sum + v, 0) / arr.length;

0 commit comments

Comments
 (0)