From 6593a7be763434fde61d0e08bc51b8c2301d3406 Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 28 Jul 2025 00:27:54 +0200 Subject: [PATCH] Add database file size and reorganize status page - Add database file size tracking to Stats struct and GetStats() - Move routing table metrics to separate 'Routing Table' status box - Add IPv4/IPv6 updates per second to routing table metrics - Database box now shows: ASNs, prefixes, peerings, and database size - Routing table box shows: live routes, IPv4/IPv6 counts, and update rates --- internal/database/database.go | 13 +++- internal/database/interface.go | 11 +-- internal/routewatch/cli.go | 7 ++ internal/server/server.go | 124 ++++++++++++++++++--------------- internal/templates/status.html | 19 +++++ 5 files changed, 112 insertions(+), 62 deletions(-) diff --git a/internal/database/database.go b/internal/database/database.go index 7d4bb8f..422d17a 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -25,6 +25,7 @@ const dirPermissions = 0750 // rwxr-x--- type Database struct { db *sql.DB logger *slog.Logger + path string } // Config holds database configuration @@ -112,7 +113,7 @@ func NewWithConfig(config Config, logger *slog.Logger) (*Database, error) { db.SetMaxIdleConns(1) db.SetConnMaxLifetime(0) - database := &Database{db: db, logger: logger} + database := &Database{db: db, logger: logger, path: config.Path} if err := database.Initialize(); err != nil { return nil, fmt.Errorf("failed to initialize database: %w", err) @@ -437,6 +438,16 @@ func (d *Database) GetStats() (Stats, error) { return stats, err } + // Get database file size + d.logger.Info("Getting database file size") + fileInfo, err := os.Stat(d.path) + if err != nil { + d.logger.Warn("Failed to get database file size", "error", err) + stats.FileSizeBytes = 0 + } else { + stats.FileSizeBytes = fileInfo.Size() + } + d.logger.Info("Stats collection complete") return stats, nil diff --git a/internal/database/interface.go b/internal/database/interface.go index bd1117f..8a7c33e 100644 --- a/internal/database/interface.go +++ b/internal/database/interface.go @@ -6,11 +6,12 @@ import ( // Stats contains database statistics type Stats struct { - ASNs int - Prefixes int - IPv4Prefixes int - IPv6Prefixes int - Peerings int + ASNs int + Prefixes int + IPv4Prefixes int + IPv6Prefixes int + Peerings int + FileSizeBytes int64 } // Store defines the interface for database operations diff --git a/internal/routewatch/cli.go b/internal/routewatch/cli.go index 67ca45f..06abbba 100644 --- a/internal/routewatch/cli.go +++ b/internal/routewatch/cli.go @@ -6,14 +6,21 @@ import ( "os" "os/signal" "syscall" + "time" "go.uber.org/fx" ) +const ( + // shutdownTimeout is the maximum time allowed for graceful shutdown + shutdownTimeout = 60 * time.Second +) + // CLIEntry is the main entry point for the CLI func CLIEntry() { app := fx.New( getModule(), + fx.StopTimeout(shutdownTimeout), // Allow 60 seconds for graceful shutdown fx.Invoke(func(lc fx.Lifecycle, rw *RouteWatch, logger *slog.Logger) { lc.Append(fx.Hook{ OnStart: func(_ context.Context) error { diff --git a/internal/server/server.go b/internal/server/server.go index 1bd360c..99b5ac3 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -114,20 +114,23 @@ func (s *Server) handleRoot() http.HandlerFunc { func (s *Server) handleStatusJSON() http.HandlerFunc { // Stats represents the statistics response type Stats struct { - Uptime string `json:"uptime"` - TotalMessages uint64 `json:"total_messages"` - TotalBytes uint64 `json:"total_bytes"` - MessagesPerSec float64 `json:"messages_per_sec"` - MbitsPerSec float64 `json:"mbits_per_sec"` - Connected bool `json:"connected"` - ASNs int `json:"asns"` - Prefixes int `json:"prefixes"` - IPv4Prefixes int `json:"ipv4_prefixes"` - IPv6Prefixes int `json:"ipv6_prefixes"` - Peerings int `json:"peerings"` - LiveRoutes int `json:"live_routes"` - IPv4Routes int `json:"ipv4_routes"` - IPv6Routes int `json:"ipv6_routes"` + Uptime string `json:"uptime"` + TotalMessages uint64 `json:"total_messages"` + TotalBytes uint64 `json:"total_bytes"` + MessagesPerSec float64 `json:"messages_per_sec"` + MbitsPerSec float64 `json:"mbits_per_sec"` + Connected bool `json:"connected"` + ASNs int `json:"asns"` + Prefixes int `json:"prefixes"` + IPv4Prefixes int `json:"ipv4_prefixes"` + IPv6Prefixes int `json:"ipv6_prefixes"` + Peerings int `json:"peerings"` + DatabaseSizeBytes int64 `json:"database_size_bytes"` + LiveRoutes int `json:"live_routes"` + IPv4Routes int `json:"ipv4_routes"` + IPv6Routes int `json:"ipv6_routes"` + IPv4UpdatesPerSec float64 `json:"ipv4_updates_per_sec"` + IPv6UpdatesPerSec float64 `json:"ipv6_updates_per_sec"` } return func(w http.ResponseWriter, r *http.Request) { @@ -197,20 +200,23 @@ func (s *Server) handleStatusJSON() http.HandlerFunc { rtStats := s.routingTable.GetDetailedStats() stats := Stats{ - Uptime: uptime, - TotalMessages: metrics.TotalMessages, - TotalBytes: metrics.TotalBytes, - MessagesPerSec: metrics.MessagesPerSec, - MbitsPerSec: metrics.BitsPerSec / bitsPerMegabit, - Connected: metrics.Connected, - ASNs: dbStats.ASNs, - Prefixes: dbStats.Prefixes, - IPv4Prefixes: dbStats.IPv4Prefixes, - IPv6Prefixes: dbStats.IPv6Prefixes, - Peerings: dbStats.Peerings, - LiveRoutes: rtStats.TotalRoutes, - IPv4Routes: rtStats.IPv4Routes, - IPv6Routes: rtStats.IPv6Routes, + Uptime: uptime, + TotalMessages: metrics.TotalMessages, + TotalBytes: metrics.TotalBytes, + MessagesPerSec: metrics.MessagesPerSec, + MbitsPerSec: metrics.BitsPerSec / bitsPerMegabit, + Connected: metrics.Connected, + ASNs: dbStats.ASNs, + Prefixes: dbStats.Prefixes, + IPv4Prefixes: dbStats.IPv4Prefixes, + IPv6Prefixes: dbStats.IPv6Prefixes, + Peerings: dbStats.Peerings, + DatabaseSizeBytes: dbStats.FileSizeBytes, + LiveRoutes: rtStats.TotalRoutes, + IPv4Routes: rtStats.IPv4Routes, + IPv6Routes: rtStats.IPv6Routes, + IPv4UpdatesPerSec: rtStats.IPv4UpdatesRate, + IPv6UpdatesPerSec: rtStats.IPv6UpdatesRate, } w.Header().Set("Content-Type", "application/json") @@ -228,20 +234,23 @@ func (s *Server) handleStatusJSON() http.HandlerFunc { func (s *Server) handleStats() http.HandlerFunc { // StatsResponse represents the API statistics response type StatsResponse struct { - Uptime string `json:"uptime"` - TotalMessages uint64 `json:"total_messages"` - TotalBytes uint64 `json:"total_bytes"` - MessagesPerSec float64 `json:"messages_per_sec"` - MbitsPerSec float64 `json:"mbits_per_sec"` - Connected bool `json:"connected"` - ASNs int `json:"asns"` - Prefixes int `json:"prefixes"` - IPv4Prefixes int `json:"ipv4_prefixes"` - IPv6Prefixes int `json:"ipv6_prefixes"` - Peerings int `json:"peerings"` - LiveRoutes int `json:"live_routes"` - IPv4Routes int `json:"ipv4_routes"` - IPv6Routes int `json:"ipv6_routes"` + Uptime string `json:"uptime"` + TotalMessages uint64 `json:"total_messages"` + TotalBytes uint64 `json:"total_bytes"` + MessagesPerSec float64 `json:"messages_per_sec"` + MbitsPerSec float64 `json:"mbits_per_sec"` + Connected bool `json:"connected"` + ASNs int `json:"asns"` + Prefixes int `json:"prefixes"` + IPv4Prefixes int `json:"ipv4_prefixes"` + IPv6Prefixes int `json:"ipv6_prefixes"` + Peerings int `json:"peerings"` + DatabaseSizeBytes int64 `json:"database_size_bytes"` + LiveRoutes int `json:"live_routes"` + IPv4Routes int `json:"ipv4_routes"` + IPv6Routes int `json:"ipv6_routes"` + IPv4UpdatesPerSec float64 `json:"ipv4_updates_per_sec"` + IPv6UpdatesPerSec float64 `json:"ipv6_updates_per_sec"` } return func(w http.ResponseWriter, r *http.Request) { @@ -304,20 +313,23 @@ func (s *Server) handleStats() http.HandlerFunc { rtStats := s.routingTable.GetDetailedStats() stats := StatsResponse{ - Uptime: uptime, - TotalMessages: metrics.TotalMessages, - TotalBytes: metrics.TotalBytes, - MessagesPerSec: metrics.MessagesPerSec, - MbitsPerSec: metrics.BitsPerSec / bitsPerMegabit, - Connected: metrics.Connected, - ASNs: dbStats.ASNs, - Prefixes: dbStats.Prefixes, - IPv4Prefixes: dbStats.IPv4Prefixes, - IPv6Prefixes: dbStats.IPv6Prefixes, - Peerings: dbStats.Peerings, - LiveRoutes: rtStats.TotalRoutes, - IPv4Routes: rtStats.IPv4Routes, - IPv6Routes: rtStats.IPv6Routes, + Uptime: uptime, + TotalMessages: metrics.TotalMessages, + TotalBytes: metrics.TotalBytes, + MessagesPerSec: metrics.MessagesPerSec, + MbitsPerSec: metrics.BitsPerSec / bitsPerMegabit, + Connected: metrics.Connected, + ASNs: dbStats.ASNs, + Prefixes: dbStats.Prefixes, + IPv4Prefixes: dbStats.IPv4Prefixes, + IPv6Prefixes: dbStats.IPv6Prefixes, + Peerings: dbStats.Peerings, + DatabaseSizeBytes: dbStats.FileSizeBytes, + LiveRoutes: rtStats.TotalRoutes, + IPv4Routes: rtStats.IPv4Routes, + IPv6Routes: rtStats.IPv6Routes, + IPv4UpdatesPerSec: rtStats.IPv4UpdatesRate, + IPv6UpdatesPerSec: rtStats.IPv6UpdatesRate, } w.Header().Set("Content-Type", "application/json") diff --git a/internal/templates/status.html b/internal/templates/status.html index 8175a4d..33163c0 100644 --- a/internal/templates/status.html +++ b/internal/templates/status.html @@ -122,6 +122,14 @@ Peerings - +
+ Database Size + - +
+ + +
+

Routing Table

Live Routes - @@ -134,6 +142,14 @@ IPv6 Routes -
+
+ IPv4 Updates/sec + - +
+
+ IPv6 Updates/sec + - +
@@ -180,9 +196,12 @@ document.getElementById('ipv4_prefixes').textContent = formatNumber(data.ipv4_prefixes); document.getElementById('ipv6_prefixes').textContent = formatNumber(data.ipv6_prefixes); document.getElementById('peerings').textContent = formatNumber(data.peerings); + document.getElementById('database_size').textContent = formatBytes(data.database_size_bytes); document.getElementById('live_routes').textContent = formatNumber(data.live_routes); document.getElementById('ipv4_routes').textContent = formatNumber(data.ipv4_routes); document.getElementById('ipv6_routes').textContent = formatNumber(data.ipv6_routes); + document.getElementById('ipv4_updates_per_sec').textContent = data.ipv4_updates_per_sec.toFixed(1); + document.getElementById('ipv6_updates_per_sec').textContent = data.ipv6_updates_per_sec.toFixed(1); // Clear any errors document.getElementById('error').style.display = 'none';