test: ssr graphs
This commit is contained in:
parent
d62f776f7b
commit
b1dce432a5
|
|
@ -41,6 +41,26 @@ function latencyChartSSR(pings: any[]): string {
|
||||||
return '<div class="h-full flex items-center justify-center text-gray-600 text-sm">Not enough data</div>';
|
return '<div class="h-full flex items-center justify-center text-gray-600 text-sm">Not enough data</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const w = 800, h = 128;
|
||||||
|
const pad = { top: 8, bottom: 8 };
|
||||||
|
const cH = h - pad.top - pad.bottom;
|
||||||
|
|
||||||
|
// Build ordered list of unique runs, evenly spaced (matches canvas)
|
||||||
|
const runTimes: Record<string, number[]> = {};
|
||||||
|
for (const p of data) {
|
||||||
|
const rid = p.run_id || p.checked_at;
|
||||||
|
if (!runTimes[rid]) runTimes[rid] = [];
|
||||||
|
runTimes[rid].push(new Date(p.checked_at).getTime());
|
||||||
|
}
|
||||||
|
const runs = Object.keys(runTimes).sort((a, b) => {
|
||||||
|
const avgA = runTimes[a].reduce((x: number, y: number) => x + y, 0) / runTimes[a].length;
|
||||||
|
const avgB = runTimes[b].reduce((x: number, y: number) => x + y, 0) / runTimes[b].length;
|
||||||
|
return avgA - avgB;
|
||||||
|
});
|
||||||
|
const runIndex: Record<string, number> = {};
|
||||||
|
runs.forEach((rid, i) => { runIndex[rid] = i; });
|
||||||
|
const maxIdx = Math.max(runs.length - 1, 1);
|
||||||
|
|
||||||
// Group by region
|
// Group by region
|
||||||
const byRegion: Record<string, any[]> = {};
|
const byRegion: Record<string, any[]> = {};
|
||||||
for (const p of data) {
|
for (const p of data) {
|
||||||
|
|
@ -51,17 +71,24 @@ function latencyChartSSR(pings: any[]): string {
|
||||||
|
|
||||||
const regions = Object.keys(byRegion);
|
const regions = Object.keys(byRegion);
|
||||||
const allValues = data.map((c: any) => c.latency_ms);
|
const allValues = data.map((c: any) => c.latency_ms);
|
||||||
const max = Math.max(...allValues, 1);
|
const yMax = Math.max(...allValues, 1);
|
||||||
const min = Math.min(...allValues, 0);
|
const yMin = Math.min(...allValues, 0);
|
||||||
const range = max - min || 1;
|
const yRange = yMax - yMin || 1;
|
||||||
const w = 800;
|
|
||||||
const h = 128;
|
|
||||||
|
|
||||||
// Find the overall time range to align lines on a shared x-axis
|
function toX(p: any): number {
|
||||||
const allTimes = data.map((c: any) => new Date(c.checked_at).getTime());
|
const idx = runIndex[p.run_id || p.checked_at] || 0;
|
||||||
const tMin = Math.min(...allTimes);
|
return (idx / maxIdx) * w;
|
||||||
const tMax = Math.max(...allTimes);
|
}
|
||||||
const tRange = tMax - tMin || 1;
|
function toY(v: number): number {
|
||||||
|
return pad.top + cH - ((v - yMin) / yRange) * cH;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grid lines
|
||||||
|
let grid = '';
|
||||||
|
for (let i = 0; i <= 4; i++) {
|
||||||
|
const y = (pad.top + (cH / 4) * i).toFixed(1);
|
||||||
|
grid += `<line x1="0" y1="${y}" x2="${w}" y2="${y}" stroke="rgba(75,85,99,0.3)" stroke-width="0.5"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
let paths = '';
|
let paths = '';
|
||||||
let dots = '';
|
let dots = '';
|
||||||
|
|
@ -70,21 +97,27 @@ function latencyChartSSR(pings: any[]): string {
|
||||||
for (const region of regions) {
|
for (const region of regions) {
|
||||||
const color = REGION_COLORS[region] || '#6b7280';
|
const color = REGION_COLORS[region] || '#6b7280';
|
||||||
const label = REGION_LABELS[region] || region;
|
const label = REGION_LABELS[region] || region;
|
||||||
const rPings = byRegion[region].slice().sort((a: any, b: any) =>
|
const rPings = byRegion[region].slice().sort((a: any, b: any) => toX(a) - toX(b));
|
||||||
new Date(a.checked_at).getTime() - new Date(b.checked_at).getTime()
|
|
||||||
);
|
|
||||||
|
|
||||||
const points = rPings.map((p: any) => {
|
const pts = rPings.map((p: any) => ({ x: toX(p), y: toY(p.latency_ms), up: p.up }));
|
||||||
const x = ((new Date(p.checked_at).getTime() - tMin) / tRange) * w;
|
if (pts.length < 2) continue;
|
||||||
const y = h - ((p.latency_ms - min) / range) * (h - 16) - 8;
|
|
||||||
return { x, y, up: p.up };
|
|
||||||
});
|
|
||||||
|
|
||||||
if (points.length < 1) continue;
|
// Catmull-Rom spline (tension 0.15, matches canvas)
|
||||||
|
const t = 0.15;
|
||||||
const pathD = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' ');
|
let d = `M${pts[0].x.toFixed(1)},${pts[0].y.toFixed(1)}`;
|
||||||
paths += `<path d="${pathD}" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`;
|
for (let i = 0; i < pts.length - 1; i++) {
|
||||||
dots += points.filter(p => !p.up).map(p =>
|
const p0 = pts[Math.max(i - 1, 0)];
|
||||||
|
const p1 = pts[i];
|
||||||
|
const p2 = pts[i + 1];
|
||||||
|
const p3 = pts[Math.min(i + 2, pts.length - 1)];
|
||||||
|
const cp1x = p1.x + (p2.x - p0.x) * t;
|
||||||
|
const cp1y = p1.y + (p2.y - p0.y) * t;
|
||||||
|
const cp2x = p2.x - (p3.x - p1.x) * t;
|
||||||
|
const cp2y = p2.y - (p3.y - p1.y) * t;
|
||||||
|
d += ` C${cp1x.toFixed(1)},${cp1y.toFixed(1)} ${cp2x.toFixed(1)},${cp2y.toFixed(1)} ${p2.x.toFixed(1)},${p2.y.toFixed(1)}`;
|
||||||
|
}
|
||||||
|
paths += `<path d="${d}" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`;
|
||||||
|
dots += pts.filter(p => !p.up).map(p =>
|
||||||
`<circle cx="${p.x.toFixed(1)}" cy="${p.y.toFixed(1)}" r="3" fill="#f87171"/>`
|
`<circle cx="${p.x.toFixed(1)}" cy="${p.y.toFixed(1)}" r="3" fill="#f87171"/>`
|
||||||
).join('');
|
).join('');
|
||||||
legend += `<span class="flex items-center gap-1"><span style="background:${color}" class="inline-block w-2 h-2 rounded-full"></span><span>${label}</span></span>`;
|
legend += `<span class="flex items-center gap-1"><span style="background:${color}" class="inline-block w-2 h-2 rounded-full"></span><span>${label}</span></span>`;
|
||||||
|
|
@ -92,10 +125,10 @@ function latencyChartSSR(pings: any[]): string {
|
||||||
|
|
||||||
return `<div class="relative w-full h-full">
|
return `<div class="relative w-full h-full">
|
||||||
<svg viewBox="0 0 ${w} ${h}" class="w-full h-full" preserveAspectRatio="none">
|
<svg viewBox="0 0 ${w} ${h}" class="w-full h-full" preserveAspectRatio="none">
|
||||||
${paths}${dots}
|
${grid}${paths}${dots}
|
||||||
</svg>
|
</svg>
|
||||||
<span class="absolute top-0 left-1 text-gray-500 text-xs leading-none pointer-events-none">${max}ms</span>
|
<span class="absolute top-0 left-1 text-gray-500 text-xs leading-none pointer-events-none">${yMax}ms</span>
|
||||||
<span class="absolute bottom-0 left-1 text-gray-500 text-xs leading-none pointer-events-none">${min}ms</span>
|
<span class="absolute bottom-0 left-1 text-gray-500 text-xs leading-none pointer-events-none">${yMin}ms</span>
|
||||||
${regions.length > 1 ? `<div class="absolute top-0 right-1 flex gap-2 text-xs text-gray-500">${legend}</div>` : ''}
|
${regions.length > 1 ? `<div class="absolute top-0 right-1 flex gap-2 text-xs text-gray-500">${legend}</div>` : ''}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue