fix: active sessions query, chart update performance

- Active sessions query now finds truly active sessions (started
  anytime, no session.end ever) instead of only today's sessions
- Use uPlot setData() for live WS updates instead of destroying
  and recreating the chart on every event
- Destroy chart only on window change so it recreates with new scale

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
William Valentin
2026-03-14 11:25:06 -07:00
parent e7dd954f6a
commit 063e41616a
2 changed files with 24 additions and 14 deletions
+22 -10
View File
@@ -1137,6 +1137,11 @@
async function loadTimeseries() { async function loadTimeseries() {
try { try {
// Destroy chart so it's recreated with new window scale
if (dashboardChart) {
dashboardChart.destroy();
dashboardChart = null;
}
const data = await api('/v1/stats/timeseries?window=' + dashboardState.window); const data = await api('/v1/stats/timeseries?window=' + dashboardState.window);
if (!isCurrentPath('/')) return; if (!isCurrentPath('/')) return;
dashboardState.timeseries = data; dashboardState.timeseries = data;
@@ -1146,28 +1151,35 @@
} }
} }
function buildChartData() {
const ts = dashboardState.timeseries;
if (!ts || !ts.series || ts.series.length === 0) return null;
return [
ts.series.map(b => Math.floor(new Date(b.ts).getTime() / 1000)),
ts.series.map(b => b.runs),
ts.series.map(b => b.tools),
ts.series.map(b => b.errors),
];
}
function renderTimeseriesChart() { function renderTimeseriesChart() {
const container = document.getElementById('dash-chart'); const container = document.getElementById('dash-chart');
if (!container || !dashboardState.timeseries) return; if (!container || !dashboardState.timeseries) return;
const ts = dashboardState.timeseries; const data = buildChartData();
if (!ts.series || ts.series.length === 0) { if (!data) {
container.innerHTML = '<p class="empty-state" style="padding:2rem">No data for this window</p>'; container.innerHTML = '<p class="empty-state" style="padding:2rem">No data for this window</p>';
return; return;
} }
// If chart already exists, just update the data
if (dashboardChart) { if (dashboardChart) {
dashboardChart.destroy(); dashboardChart.setData(data);
dashboardChart = null; return;
} }
container.innerHTML = ''; container.innerHTML = '';
const timestamps = ts.series.map(b => Math.floor(new Date(b.ts).getTime() / 1000));
const runs = ts.series.map(b => b.runs);
const tools = ts.series.map(b => b.tools);
const errors = ts.series.map(b => b.errors);
const width = container.clientWidth || 600; const width = container.clientWidth || 600;
const height = 200; const height = 200;
@@ -1217,7 +1229,7 @@
], ],
}; };
dashboardChart = new uPlot(opts, [timestamps, runs, tools, errors], container); dashboardChart = new uPlot(opts, data, container);
if (dashboardResizeObserver) { if (dashboardResizeObserver) {
dashboardResizeObserver.disconnect(); dashboardResizeObserver.disconnect();
+2 -4
View File
@@ -36,23 +36,21 @@ func (d *DB) GetSummary(ctx context.Context) (*Summary, error) {
now := time.Now() now := time.Now()
midnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) midnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
// Active sessions: sessions that started today but have not ended today // Active sessions: sessions with a session.start but no session.end (ever)
activeQ := ` activeQ := `
SELECT COUNT(DISTINCT session_id) SELECT COUNT(DISTINCT session_id)
FROM events FROM events
WHERE type = 'session.start' WHERE type = 'session.start'
AND ts >= $1
AND session_id IS NOT NULL AND session_id IS NOT NULL
AND session_id NOT IN ( AND session_id NOT IN (
SELECT DISTINCT session_id SELECT DISTINCT session_id
FROM events FROM events
WHERE type = 'session.end' WHERE type = 'session.end'
AND ts >= $1
AND session_id IS NOT NULL AND session_id IS NOT NULL
) )
` `
var activeSessions int var activeSessions int
if err := d.sql.QueryRowContext(ctx, activeQ, midnight).Scan(&activeSessions); err != nil { if err := d.sql.QueryRowContext(ctx, activeQ).Scan(&activeSessions); err != nil {
return nil, err return nil, err
} }