summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmd/relay/main.go18
-rw-r--r--internal/metrics/dashboard.html6
-rw-r--r--internal/storage/storage.go36
3 files changed, 58 insertions, 2 deletions
diff --git a/cmd/relay/main.go b/cmd/relay/main.go
index e4fe9bc..8948ebf 100644
--- a/cmd/relay/main.go
+++ b/cmd/relay/main.go
@@ -9,6 +9,7 @@ import (
9 "os" 9 "os"
10 "os/signal" 10 "os/signal"
11 "syscall" 11 "syscall"
12 "time"
12 13
13 "connectrpc.com/connect" 14 "connectrpc.com/connect"
14 "golang.org/x/net/http2" 15 "golang.org/x/net/http2"
@@ -95,6 +96,23 @@ func main() {
95 wsHandler := wshandler.NewHandler(store, subManager) 96 wsHandler := wshandler.NewHandler(store, subManager)
96 if m != nil { 97 if m != nil {
97 wsHandler.SetMetrics(m) 98 wsHandler.SetMetrics(m)
99
100 // Update storage stats periodically
101 go func() {
102 ticker := time.NewTicker(30 * time.Second)
103 defer ticker.Stop()
104
105 // Update immediately on start
106 if stats, err := store.GetStats(context.Background()); err == nil {
107 m.UpdateStorageStats(stats.EventCount, stats.DBSizeBytes)
108 }
109
110 for range ticker.C {
111 if stats, err := store.GetStats(context.Background()); err == nil {
112 m.UpdateStorageStats(stats.EventCount, stats.DBSizeBytes)
113 }
114 }
115 }()
98 } 116 }
99 117
100 var grpcDisplay, httpDisplay, wsDisplay string 118 var grpcDisplay, httpDisplay, wsDisplay string
diff --git a/internal/metrics/dashboard.html b/internal/metrics/dashboard.html
index 363f1f7..63b42ed 100644
--- a/internal/metrics/dashboard.html
+++ b/internal/metrics/dashboard.html
@@ -295,6 +295,12 @@
295 document.getElementById('event_deletions').textContent = 295 document.getElementById('event_deletions').textContent =
296 sumMetric(metrics, `${prefix}_relay_event_deletions_total`); 296 sumMetric(metrics, `${prefix}_relay_event_deletions_total`);
297 297
298 const durationSum = sumMetric(metrics, `${prefix}_relay_request_duration_seconds_sum`);
299 const durationCount = sumMetric(metrics, `${prefix}_relay_request_duration_seconds_count`);
300 const avgLatencyMs = durationCount > 0 ? (durationSum / durationCount * 1000) : 0;
301 document.getElementById('avg_latency').innerHTML =
302 `${avgLatencyMs.toFixed(1)}<span class="metric-unit">ms</span>`;
303
298 const processStart = sumMetric(metrics, 'process_start_time_seconds'); 304 const processStart = sumMetric(metrics, 'process_start_time_seconds');
299 if (processStart > 0) { 305 if (processStart > 0) {
300 if (!processStartTime) processStartTime = processStart; 306 if (!processStartTime) processStartTime = processStart;
diff --git a/internal/storage/storage.go b/internal/storage/storage.go
index 9ef9956..ca9b264 100644
--- a/internal/storage/storage.go
+++ b/internal/storage/storage.go
@@ -4,6 +4,7 @@ import (
4 "context" 4 "context"
5 "database/sql" 5 "database/sql"
6 "fmt" 6 "fmt"
7 "os"
7 8
8 _ "modernc.org/sqlite" // Pure Go SQLite driver 9 _ "modernc.org/sqlite" // Pure Go SQLite driver
9) 10)
@@ -11,7 +12,8 @@ import (
11// Storage provides event persistence using SQLite. 12// Storage provides event persistence using SQLite.
12// Consumers should define their own interface based on their needs. 13// Consumers should define their own interface based on their needs.
13type Storage struct { 14type Storage struct {
14 db *sql.DB 15 db *sql.DB
16 dbPath string
15} 17}
16 18
17// New creates a new Storage instance and initializes the database schema. 19// New creates a new Storage instance and initializes the database schema.
@@ -48,7 +50,10 @@ func New(dbPath string) (*Storage, error) {
48 } 50 }
49 } 51 }
50 52
51 s := &Storage{db: db} 53 s := &Storage{
54 db: db,
55 dbPath: dbPath,
56 }
52 57
53 // Initialize schema 58 // Initialize schema
54 if err := s.initSchema(context.Background()); err != nil { 59 if err := s.initSchema(context.Background()); err != nil {
@@ -142,3 +147,30 @@ func (s *Storage) initSchema(ctx context.Context) error {
142func (s *Storage) DB() *sql.DB { 147func (s *Storage) DB() *sql.DB {
143 return s.db 148 return s.db
144} 149}
150
151type Stats struct {
152 EventCount int64
153 DBSizeBytes int64
154}
155
156func (s *Storage) GetStats(ctx context.Context) (*Stats, error) {
157 var eventCount int64
158 err := s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM events WHERE deleted = 0").Scan(&eventCount)
159 if err != nil {
160 return nil, fmt.Errorf("failed to count events: %w", err)
161 }
162
163 var dbSize int64
164 if s.dbPath != ":memory:" {
165 info, err := os.Stat(s.dbPath)
166 if err != nil {
167 return nil, fmt.Errorf("failed to stat database file: %w", err)
168 }
169 dbSize = info.Size()
170 }
171
172 return &Stats{
173 EventCount: eventCount,
174 DBSizeBytes: dbSize,
175 }, nil
176}