Skip to content

Commit 73606b6

Browse files
committed
feat(dashboard): cumulative traffic totals (in/out, day/week/month/all)
GET /admin/traffic returns aggregated bytes_in (sources) and bytes_out (listeners) over four windows: 24h, 7d, 30d, all-time. Aggregation walks listener_histories per mount summing the deltas between consecutive samples; counter resets (stream restart -> new value smaller than previous) credit the full new value as fresh traffic instead of going negative, so a multi-day window with mid-window restarts still gives a sane total. Frontend adds <TrafficTotalsCard/> on the admin dashboard, four small cards with bytes formatted to the appropriate unit (B/KB/MB/GB/TB).
1 parent 3be4ba0 commit 73606b6

30 files changed

Lines changed: 306 additions & 66 deletions

relay/history.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,85 @@ type HistoricalStat struct {
157157
BytesOut int64 `json:"bytes_out"`
158158
}
159159

160+
// TrafficTotals captures the total bytes transferred across all mounts
161+
// within a single time window.
162+
type TrafficTotals struct {
163+
BytesIn int64 `json:"bytes_in"`
164+
BytesOut int64 `json:"bytes_out"`
165+
Mounts int `json:"mounts"` // distinct mounts that produced any traffic in the window
166+
}
167+
168+
// GetTrafficTotals walks listener_histories within the window and sums
169+
// the per-sample deltas of BytesIn / BytesOut, handling stream restarts
170+
// (where the cumulative counter drops to a smaller value) by treating
171+
// the new sample's whole value as fresh traffic. The first sample of
172+
// each mount in-window is treated as the baseline (delta zero) so we
173+
// don't count bytes that accumulated before the window started.
174+
//
175+
// Returns a single aggregated TrafficTotals across all mounts.
176+
func (hm *HistoryManager) GetTrafficTotals(window time.Duration) TrafficTotals {
177+
if hm == nil || hm.db == nil {
178+
return TrafficTotals{}
179+
}
180+
cutoff := time.Now().Add(-window)
181+
if window <= 0 {
182+
// Sentinel: "all time"
183+
cutoff = time.Time{}
184+
}
185+
186+
type row struct {
187+
Mount string
188+
BytesIn int64
189+
BytesOut int64
190+
}
191+
var rows []row
192+
q := hm.db.Model(&ListenerHistory{}).Order("mount ASC, timestamp ASC")
193+
if !cutoff.IsZero() {
194+
q = q.Where("timestamp > ?", cutoff)
195+
}
196+
if err := q.Find(&rows).Error; err != nil {
197+
logger.L.Errorf("Failed to load traffic totals: %v", err)
198+
return TrafficTotals{}
199+
}
200+
201+
var totals TrafficTotals
202+
mountsSeen := make(map[string]bool)
203+
204+
var curMount string
205+
var prevIn, prevOut int64
206+
first := true
207+
for _, r := range rows {
208+
if r.Mount != curMount {
209+
curMount = r.Mount
210+
prevIn = r.BytesIn
211+
prevOut = r.BytesOut
212+
first = true
213+
}
214+
if first {
215+
first = false
216+
continue // baseline; no delta to add
217+
}
218+
if r.BytesIn >= prevIn {
219+
totals.BytesIn += r.BytesIn - prevIn
220+
} else {
221+
// counter reset (stream restarted) — credit the full new value
222+
totals.BytesIn += r.BytesIn
223+
}
224+
if r.BytesOut >= prevOut {
225+
totals.BytesOut += r.BytesOut - prevOut
226+
} else {
227+
totals.BytesOut += r.BytesOut
228+
}
229+
if r.BytesIn > 0 || r.BytesOut > 0 {
230+
mountsSeen[r.Mount] = true
231+
}
232+
prevIn = r.BytesIn
233+
prevOut = r.BytesOut
234+
}
235+
totals.Mounts = len(mountsSeen)
236+
return totals
237+
}
238+
160239
// GetAllHistoricalStats retrieves metrics for all mounts within a specific duration.
161240
func (hm *HistoryManager) GetAllHistoricalStats(duration time.Duration) map[string][]HistoricalStat {
162241
var results []struct {

server/frontend/dist/assets/EqBars-CLrMRveW.js renamed to server/frontend/dist/assets/EqBars-Dwr9UJcF.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/frontend/dist/assets/Nav-CtQ_u_9g.js renamed to server/frontend/dist/assets/Nav-Bocei5qj.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/frontend/dist/assets/StreamCard-B1hAH_iC.js renamed to server/frontend/dist/assets/StreamCard-RI7LsER9.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/frontend/dist/assets/VolumeKnob-BCstdxfI.js renamed to server/frontend/dist/assets/VolumeKnob-B7nDzJTd.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)