mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-10-15 22:39:56 +00:00
Stats endpoints read hourly rollup tables filled once per closed hour by one advisory-locked task, instead of materialized views that re-scanned every observation on each refresh. IATA-set tables give exact distinct counts for any IATA set or region. Cleanup holds raw rows for unrolled hours (up to 24h). Adds /stats/series; ingest keeps packet observation counts, observer last IATA, channel message counts and trace tag summaries. 001_baseline.sql is rewritten, so pre-release 2.0 databases must be recreated. See docs/historical-stats.md.
69 lines
2.2 KiB
Go
69 lines
2.2 KiB
Go
// Copyright 2026 Beacon Contributors
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
sqlc "github.com/MeshCore-Beacon/beacon-server/db/sqlc"
|
|
"github.com/MeshCore-Beacon/beacon-server/internal/api"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
func (s *Store) GetSignalStats(ctx context.Context, since, until time.Time, iatas []string) (*api.SignalStats, error) {
|
|
rows, err := s.q.GetSignalStats(ctx, sqlc.GetSignalStatsParams{
|
|
Since: pgtype.Timestamptz{Time: since, Valid: true}, Until: pgtype.Timestamptz{Time: until, Valid: true}, Iatas: iatas,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stats := &api.SignalStats{Since: since.UnixMilli(), Until: until.UnixMilli(),
|
|
SNR: api.SignalMetric{Histogram: signalBins(-30, 5, 12)}, RSSI: api.SignalMetric{Histogram: signalBins(-140, 10, 14)}, Hourly: []api.SignalHour{}}
|
|
for _, row := range rows {
|
|
// GROUPING bits distinguish real null buckets from dimensions not in the set.
|
|
switch row.Kind {
|
|
case 7:
|
|
stats.Receptions = row.Receptions
|
|
stats.SNR.Samples, stats.SNR.Average = row.SnrSamples, signalAverage(row.SnrAverage, row.SnrSamples)
|
|
stats.RSSI.Samples, stats.RSSI.Average = row.RssiSamples, signalAverage(row.RssiAverage, row.RssiSamples)
|
|
case 3:
|
|
stats.Hourly = append(stats.Hourly, api.SignalHour{Hour: row.Hour.Time.UnixMilli(), Receptions: row.Receptions,
|
|
SNRSamples: row.SnrSamples, SNRAverage: signalAverage(row.SnrAverage, row.SnrSamples),
|
|
RSSISamples: row.RssiSamples, RSSIAverage: signalAverage(row.RssiAverage, row.RssiSamples)})
|
|
case 5:
|
|
if row.SnrBin >= 0 && int(row.SnrBin) < len(stats.SNR.Histogram) {
|
|
stats.SNR.Histogram[row.SnrBin].Count = row.Receptions
|
|
}
|
|
case 6:
|
|
if row.RssiBin >= 0 && int(row.RssiBin) < len(stats.RSSI.Histogram) {
|
|
stats.RSSI.Histogram[row.RssiBin].Count = row.Receptions
|
|
}
|
|
}
|
|
}
|
|
return stats, nil
|
|
}
|
|
|
|
func signalAverage(value float64, count int64) *float64 {
|
|
if count == 0 {
|
|
return nil
|
|
}
|
|
return &value
|
|
}
|
|
|
|
func signalBins(lower, width float64, count int) []api.SignalBin {
|
|
bins := make([]api.SignalBin, count+2)
|
|
for i := range bins {
|
|
if i > 0 {
|
|
v := lower + float64(i-1)*width
|
|
bins[i].Lower = &v
|
|
}
|
|
if i <= count {
|
|
v := lower + float64(i)*width
|
|
bins[i].Upper = &v
|
|
}
|
|
}
|
|
return bins
|
|
}
|