Statistics page: the statistics database's size, and a fullness bar on the disk tiles
The server section shows the statistics database's size (with its -wal and -shm) and how many answers the query log holds, linking to the dashboard's Storage panel; the disk tiles get the same bar as the dashboard. GET /stats/server has database {path, bytes, query_log_rows}.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
01ec37b9b2
commit
1b102a78f6
8 files changed
+78
-13
No files matched your search
@@ -283,6 +283,16 @@ func (s *Server) statsServer(w http.ResponseWriter, r *http.Request, p *auth.Pri
|
||||
labels = append(labels, "statistics database")
|
||||
}
|
||||
out["disks"] = stats.DiskUsage(labels, dirs)
|
||||
// The statistics database: what the statistics cost on disk.
|
||||
if db := s.cfg.Stats.Store().Path(); db != "" && db != ":memory:" {
|
||||
info := map[string]any{"path": db, "bytes": dbBytes(db)}
|
||||
if keep := s.cfg.Stats.Options().RetainQueries; keep > 0 && s.cfg.Stats.Options().QueryLog {
|
||||
if n, err := s.cfg.Stats.Store().QueryLogSize(r.Context()); err == nil {
|
||||
info["query_log_rows"] = n
|
||||
}
|
||||
}
|
||||
out["database"] = info
|
||||
}
|
||||
writeJSON(w, http.StatusOK, out)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,12 +18,7 @@ func (s *Server) storage(ctx context.Context) *api.Storage {
|
||||
if path == "" || path == ":memory:" {
|
||||
return nil
|
||||
}
|
||||
d := api.Database{Label: label, Path: path}
|
||||
for _, suffix := range []string{"", "-wal", "-shm"} {
|
||||
if fi, err := os.Stat(path + suffix); err == nil {
|
||||
d.Bytes += fi.Size()
|
||||
}
|
||||
}
|
||||
d := api.Database{Label: label, Path: path, Bytes: dbBytes(path)}
|
||||
out.Databases = append(out.Databases, d)
|
||||
out.Total += d.Bytes
|
||||
return &out.Databases[len(out.Databases)-1]
|
||||
@@ -70,3 +65,14 @@ func (s *Server) storage(ctx context.Context) *api.Storage {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// dbBytes is the size of a SQLite database: its file, -wal and -shm.
|
||||
func dbBytes(path string) int64 {
|
||||
var n int64
|
||||
for _, suffix := range []string{"", "-wal", "-shm"} {
|
||||
if fi, err := os.Stat(path + suffix); err == nil {
|
||||
n += fi.Size()
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -176,3 +177,24 @@ func TestStatsExploreAPI(t *testing.T) {
|
||||
t.Errorf("storage %v", st["storage"])
|
||||
}
|
||||
}
|
||||
|
||||
// /stats/server reports the statistics database's size and the query log.
|
||||
func TestStatsServerDatabase(t *testing.T) {
|
||||
ss, err := stats.OpenStore(filepath.Join(t.TempDir(), "stats.db"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ss.Close()
|
||||
b := &stats.Batch{}
|
||||
b.AddQueries([]stats.QueryRow{{Time: time.Now().Unix(), Client: "10.0.0.7", View: "internal", Name: "www.example.com", Type: "A", RCode: "NOERROR"}})
|
||||
if err := ss.Commit(context.Background(), b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
opts := stats.Options{QueryLog: true, StatisticsChannel: "127.0.0.1:1", TopN: 10, RetainMinute: time.Hour, RetainHour: 24 * time.Hour, RetainDay: 24 * time.Hour, RetainQueries: 7 * 24 * time.Hour}
|
||||
e := newAPIEnv(t, stats.NewCollector(ss, opts, nil, slog.New(slog.NewTextHandler(io.Discard, nil))))
|
||||
sv := e.do("GET", "/stats/server", e.token, nil, 200)
|
||||
db, ok := sv["database"].(map[string]any)
|
||||
if !ok || db["bytes"].(float64) <= 0 || db["query_log_rows"].(float64) != 1 {
|
||||
t.Errorf("database %v", sv["database"])
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user