@@ -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).
65466595func (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