Skip to content

Commit 4aee186

Browse files
committed
Update per-network dashboard charts and stats collector
1 parent b5eb53a commit 4aee186

2 files changed

Lines changed: 95 additions & 46 deletions

File tree

pkg/registry/dashboard.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -337,15 +337,15 @@ footer a:hover{color:#58a6ff}
337337
338338
<div class="charts-row" id="charts-row" style="display:none">
339339
<div class="chart-card">
340-
<h2>Last 24 Hours</h2>
340+
<h2>Online Nodes — Last 24 Hours</h2>
341341
<div class="disclaimer">Since last registry restart</div>
342342
<div style="position:relative">
343343
<svg id="chart-hourly" viewBox="0 0 400 180" preserveAspectRatio="xMidYMid meet"></svg>
344344
<div class="chart-tooltip" id="tip-hourly"></div>
345345
</div>
346346
</div>
347347
<div class="chart-card">
348-
<h2>Last 7 Days</h2>
348+
<h2>Online Nodes — Last 7 Days</h2>
349349
<div class="disclaimer">Since last registry restart</div>
350350
<div style="position:relative">
351351
<svg id="chart-daily" viewBox="0 0 400 180" preserveAspectRatio="xMidYMid meet"></svg>
@@ -465,9 +465,9 @@ function showNetDetail(idx){
465465
var d=new Date(s.ts*1000);return ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][d.getDay()]+' '+d.getDate();
466466
},'#3fb950');
467467
}
468-
function drawChart(svg,tip,samples,valFn,labelFn,color){
468+
function drawChart(svg,tip,samples,valFn,labelFn,color,unit){
469469
if(!svg)return;
470-
color=color||'#58a6ff';
470+
color=color||'#58a6ff';unit=unit||'online';
471471
if(!samples||!samples.length){svg.innerHTML='';return}
472472
var W=400,H=180,padL=40,padR=10,padT=10,padB=30;
473473
var cW=W-padL-padR,cH=H-padT-padB;
@@ -510,7 +510,7 @@ function drawChart(svg,tip,samples,valFn,labelFn,color){
510510
if(tip){
511511
svg.querySelectorAll('rect[data-val]').forEach(function(r){
512512
r.addEventListener('mouseenter',function(){
513-
tip.textContent=r.getAttribute('data-lbl')+': '+r.getAttribute('data-val')+' online';
513+
tip.textContent=r.getAttribute('data-lbl')+': '+r.getAttribute('data-val')+' '+unit;
514514
tip.style.display='block';
515515
var svgRect=svg.getBoundingClientRect();
516516
var px=parseFloat(r.getAttribute('data-x'))/W*svgRect.width;
@@ -526,10 +526,10 @@ function renderCharts(hourly,daily){
526526
row.style.display='grid';
527527
drawChart(document.getElementById('chart-hourly'),document.getElementById('tip-hourly'),hourly||[],function(s){return s.online_nodes||0},function(s){
528528
var d=new Date(s.ts*1000);return ('0'+d.getHours()).slice(-2)+':00';
529-
});
529+
},'#58a6ff','online');
530530
drawChart(document.getElementById('chart-daily'),document.getElementById('tip-daily'),daily||[],function(s){return s.online_nodes||0},function(s){
531531
var d=new Date(s.ts*1000);return ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][d.getDay()]+' '+d.getDate();
532-
});
532+
},'#58a6ff','online');
533533
}
534534
function update(){
535535
var url='/api/stats';

pkg/registry/server.go

Lines changed: 88 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5636,7 +5636,7 @@ func (s *Server) sampleStats() statsSampleResult {
56365636
return statsSampleResult{
56375637
global: StatsSample{
56385638
Timestamp: now.Unix(),
5639-
TotalNodes: len(s.nodes),
5639+
TotalNodes: int(s.nextNode - 1),
56405640
OnlineNodes: online,
56415641
TrustLinks: len(s.trustPairs),
56425642
TotalRequests: s.requestCount.Load(),
@@ -6261,65 +6261,45 @@ func (s *Server) load() error {
62616261
slog.Info("loaded invite inboxes", "queues", len(s.inviteInbox))
62626262
}
62636263

6264-
// Restore time-series history ring buffers
6264+
// Restore time-series history ring buffers (deduplicate by bucket)
62656265
if len(snap.HourlyHistory) > 0 {
6266-
for i, sample := range snap.HourlyHistory {
6267-
if i >= 24 {
6268-
break
6269-
}
6266+
deduped := deduplicateSamples(snap.HourlyHistory, 3600, 24)
6267+
for i, sample := range deduped {
62706268
s.hourlyHistory[i] = sample
62716269
}
6272-
s.hourlyIdx = len(snap.HourlyHistory)
6273-
if s.hourlyIdx > 24 {
6274-
s.hourlyIdx = 24
6275-
}
6276-
slog.Info("loaded hourly history", "samples", len(snap.HourlyHistory))
6270+
s.hourlyIdx = len(deduped)
6271+
slog.Info("loaded hourly history", "samples", len(deduped))
62776272
}
62786273
if len(snap.DailyHistory) > 0 {
6279-
for i, sample := range snap.DailyHistory {
6280-
if i >= 7 {
6281-
break
6282-
}
6274+
deduped := deduplicateSamples(snap.DailyHistory, 86400, 7)
6275+
for i, sample := range deduped {
62836276
s.dailyHistory[i] = sample
62846277
}
6285-
s.dailyIdx = len(snap.DailyHistory)
6286-
if s.dailyIdx > 7 {
6287-
s.dailyIdx = 7
6288-
}
6289-
slog.Info("loaded daily history", "samples", len(snap.DailyHistory))
6278+
s.dailyIdx = len(deduped)
6279+
slog.Info("loaded daily history", "samples", len(deduped))
62906280
}
6291-
// Restore per-network history
6281+
// Restore per-network history (deduplicated)
62926282
for netIDStr, entries := range snap.NetHourlyHistory {
62936283
var netID uint16
62946284
if _, err := fmt.Sscanf(netIDStr, "%d", &netID); err == nil {
6285+
deduped := deduplicateNetSamples(entries, 3600, 24)
62956286
ring := newNetHistoryRing(24)
6296-
for i, e := range entries {
6297-
if i >= 24 {
6298-
break
6299-
}
6287+
for i, e := range deduped {
63006288
ring.Samples[i] = e
63016289
}
6302-
ring.Idx = len(entries)
6303-
if ring.Idx > 24 {
6304-
ring.Idx = 24
6305-
}
6290+
ring.Idx = len(deduped)
63066291
s.netHourly[netID] = ring
63076292
}
63086293
}
63096294
for netIDStr, entries := range snap.NetDailyHistory {
63106295
var netID uint16
63116296
if _, err := fmt.Sscanf(netIDStr, "%d", &netID); err == nil {
6297+
deduped := deduplicateNetSamples(entries, 86400, 7)
63126298
ring := newNetHistoryRing(7)
6313-
for i, e := range entries {
6314-
if i >= 7 {
6315-
break
6316-
}
6299+
for i, e := range deduped {
63176300
ring.Samples[i] = e
63186301
}
6319-
ring.Idx = len(entries)
6320-
if ring.Idx > 7 {
6321-
ring.Idx = 7
6322-
}
6302+
ring.Idx = len(deduped)
63236303
s.netDaily[netID] = ring
63246304
}
63256305
}
@@ -6532,7 +6512,7 @@ func (s *Server) GetDashboardStats() DashboardStats {
65326512
}
65336513

65346514
return DashboardStats{
6535-
TotalNodes: len(s.nodes),
6515+
TotalNodes: int(s.nextNode - 1),
65366516
ActiveNodes: activeCount,
65376517
TotalTrustLinks: len(s.trustPairs),
65386518
TotalRequests: s.requestCount.Load(),
@@ -6541,6 +6521,75 @@ func (s *Server) GetDashboardStats() DashboardStats {
65416521
}
65426522
}
65436523

6524+
// deduplicateSamples keeps only the latest sample per time bucket.
6525+
// bucketSecs is 3600 for hourly or 86400 for daily. maxOut caps the result.
6526+
func deduplicateSamples(samples []StatsSample, bucketSecs int64, maxOut int) []StatsSample {
6527+
type entry struct {
6528+
bucket int64
6529+
sample StatsSample
6530+
}
6531+
seen := make(map[int64]int) // bucket -> index in result
6532+
var result []entry
6533+
for _, s := range samples {
6534+
if s.Timestamp == 0 {
6535+
continue
6536+
}
6537+
b := s.Timestamp / bucketSecs
6538+
if idx, ok := seen[b]; ok {
6539+
// Keep the later sample in the same bucket
6540+
if s.Timestamp > result[idx].sample.Timestamp {
6541+
result[idx].sample = s
6542+
}
6543+
} else {
6544+
seen[b] = len(result)
6545+
result = append(result, entry{bucket: b, sample: s})
6546+
}
6547+
}
6548+
// Sort chronologically
6549+
sort.Slice(result, func(i, j int) bool { return result[i].bucket < result[j].bucket })
6550+
out := make([]StatsSample, 0, len(result))
6551+
for _, e := range result {
6552+
out = append(out, e.sample)
6553+
}
6554+
if len(out) > maxOut {
6555+
out = out[len(out)-maxOut:]
6556+
}
6557+
return out
6558+
}
6559+
6560+
// deduplicateNetSamples keeps only the latest NetworkSampleEntry per time bucket.
6561+
func deduplicateNetSamples(samples []NetworkSampleEntry, bucketSecs int64, maxOut int) []NetworkSampleEntry {
6562+
type entry struct {
6563+
bucket int64
6564+
sample NetworkSampleEntry
6565+
}
6566+
seen := make(map[int64]int)
6567+
var result []entry
6568+
for _, s := range samples {
6569+
if s.Timestamp == 0 {
6570+
continue
6571+
}
6572+
b := s.Timestamp / bucketSecs
6573+
if idx, ok := seen[b]; ok {
6574+
if s.Timestamp > result[idx].sample.Timestamp {
6575+
result[idx].sample = s
6576+
}
6577+
} else {
6578+
seen[b] = len(result)
6579+
result = append(result, entry{bucket: b, sample: s})
6580+
}
6581+
}
6582+
sort.Slice(result, func(i, j int) bool { return result[i].bucket < result[j].bucket })
6583+
out := make([]NetworkSampleEntry, 0, len(result))
6584+
for _, e := range result {
6585+
out = append(out, e.sample)
6586+
}
6587+
if len(out) > maxOut {
6588+
out = out[len(out)-maxOut:]
6589+
}
6590+
return out
6591+
}
6592+
65446593
// readHistory returns chronologically-ordered hourly and daily samples.
65456594
// Caller must hold s.mu (at least RLock).
65466595
func (s *Server) readHistory() (hourly, daily []StatsSample) {
@@ -6660,7 +6709,7 @@ func (s *Server) GetDashboardStatsExtended() DashboardStats {
66606709
hourly, daily := s.readHistory()
66616710

66626711
return DashboardStats{
6663-
TotalNodes: len(s.nodes),
6712+
TotalNodes: int(s.nextNode - 1),
66646713
ActiveNodes: activeCount,
66656714
TotalTrustLinks: len(s.trustPairs),
66666715
TotalRequests: s.requestCount.Load(),

0 commit comments

Comments
 (0)