feat: add monitoring retention controls

This commit is contained in:
sagitchu
2026-04-28 11:41:07 +08:00
parent e8d5687419
commit 023be27287
15 changed files with 430 additions and 13 deletions
+3 -2
View File
@@ -65,7 +65,8 @@ type TunnelQuality = model.TunnelQuality
// ─── Repository ──────────────────────────────────────────────────────
type Repository struct {
db *gorm.DB
db *gorm.DB
dbPath string
}
type FlowUploadCounterDelta struct {
@@ -198,7 +199,7 @@ func Open(path string) (*Repository, error) {
return nil, err
}
return &Repository{db: db}, nil
return &Repository{db: db, dbPath: path}, nil
}
func OpenPostgres(dsn string) (*Repository, error) {
@@ -0,0 +1,72 @@
package repo
import (
"errors"
"fmt"
"os"
)
type DatabaseStorageSummary struct {
DBType string `json:"dbType"`
DatabaseSizeBytes int64 `json:"databaseSizeBytes"`
DatabaseSizeText string `json:"databaseSizeText"`
}
func (r *Repository) DatabaseStorageSummary() (DatabaseStorageSummary, error) {
if r == nil || r.db == nil {
return DatabaseStorageSummary{}, errors.New("repository not initialized")
}
switch r.db.Dialector.Name() {
case "sqlite":
size, err := sqliteDatabaseFileSize(r.dbPath)
if err != nil {
return DatabaseStorageSummary{}, err
}
return DatabaseStorageSummary{DBType: "sqlite", DatabaseSizeBytes: size, DatabaseSizeText: formatDatabaseSize(size)}, nil
case "postgres":
var size int64
if err := r.db.Raw("SELECT pg_database_size(current_database())").Scan(&size).Error; err != nil {
return DatabaseStorageSummary{}, err
}
return DatabaseStorageSummary{DBType: "postgres", DatabaseSizeBytes: size, DatabaseSizeText: formatDatabaseSize(size)}, nil
default:
return DatabaseStorageSummary{}, fmt.Errorf("unsupported database dialect %q", r.db.Dialector.Name())
}
}
func sqliteDatabaseFileSize(path string) (int64, error) {
if path == "" || path == ":memory:" {
return 0, nil
}
var total int64
for _, candidate := range []string{path, path + "-wal", path + "-shm"} {
info, err := os.Stat(candidate)
if err != nil {
if os.IsNotExist(err) {
continue
}
return 0, err
}
if !info.IsDir() {
total += info.Size()
}
}
return total, nil
}
func formatDatabaseSize(bytes int64) string {
if bytes < 1024 {
return fmt.Sprintf("%d B", bytes)
}
units := []string{"KB", "MB", "GB", "TB"}
value := float64(bytes) / 1024
for _, unit := range units {
if value < 1024 || unit == "TB" {
return fmt.Sprintf("%.1f %s", value, unit)
}
value /= 1024
}
return fmt.Sprintf("%d B", bytes)
}
@@ -0,0 +1,52 @@
package repo
import (
"path/filepath"
"testing"
"go-backend/internal/store/model"
)
func TestDatabaseStorageSummarySQLiteIncludesSize(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "storage.db")
r, err := Open(dbPath)
if err != nil {
t.Fatalf("open repo: %v", err)
}
defer r.Close()
if err := r.InsertNodeMetric(&model.NodeMetric{NodeID: 1, Timestamp: 123, CPUUsage: 1}); err != nil {
t.Fatalf("insert metric: %v", err)
}
summary, err := r.DatabaseStorageSummary()
if err != nil {
t.Fatalf("storage summary: %v", err)
}
if summary.DBType != "sqlite" {
t.Fatalf("expected sqlite db type, got %q", summary.DBType)
}
if summary.DatabaseSizeBytes <= 0 {
t.Fatalf("expected database size > 0, got %d", summary.DatabaseSizeBytes)
}
if summary.DatabaseSizeText == "" {
t.Fatalf("expected formatted size")
}
}
func TestFormatDatabaseSize(t *testing.T) {
tests := []struct {
bytes int64
want string
}{
{bytes: 0, want: "0 B"},
{bytes: 512, want: "512 B"},
{bytes: 1024, want: "1.0 KB"},
{bytes: 1024 * 1024, want: "1.0 MB"},
}
for _, tc := range tests {
if got := formatDatabaseSize(tc.bytes); got != tc.want {
t.Fatalf("formatDatabaseSize(%d) = %q, want %q", tc.bytes, got, tc.want)
}
}
}