internal/aggergate: make HLL state path configurable
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I8ff8ef25ad945aae918bea97ee39d7ea6a6a6964
This commit is contained in:
parent
f988174145
commit
2ef2dabf93
5 changed files with 167 additions and 63 deletions
|
|
@ -98,3 +98,6 @@ server:
|
|||
metrics_path: "/metrics"
|
||||
# Event ingestion endpoint
|
||||
ingestion_path: "/api/event"
|
||||
# Path to persist HyperLogLog state (default: /var/lib/watchdog/hll.state)
|
||||
# Ensures unique visitor counts survive restarts
|
||||
state_path: "/var/lib/watchdog/hll.state"
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ func (m *MetricsAggregator) Shutdown(ctx context.Context) error {
|
|||
m.Stop()
|
||||
// Persist HLL state if configured
|
||||
if m.cfg.Site.SaltRotation != "" {
|
||||
return m.estimator.Save("/tmp/watchdog-hll.state")
|
||||
return m.estimator.Save(m.cfg.Server.StatePath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package aggregate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -190,3 +192,89 @@ func TestMetricsAggregator_MustRegister(t *testing.T) {
|
|||
t.Errorf("expected at least 2 metric families, got %d", len(metrics))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsAggregator_Shutdown_ConfigurableStatePath(t *testing.T) {
|
||||
registry := NewPathRegistry(100)
|
||||
|
||||
// Create temp directory for test
|
||||
tmpDir := t.TempDir()
|
||||
statePath := tmpDir + "/custom-hll.state"
|
||||
|
||||
cfg := config.Config{
|
||||
Site: config.SiteConfig{
|
||||
SaltRotation: "daily",
|
||||
Collect: config.CollectConfig{
|
||||
Pageviews: true,
|
||||
},
|
||||
},
|
||||
Server: config.ServerConfig{
|
||||
StatePath: statePath,
|
||||
},
|
||||
}
|
||||
|
||||
agg := NewMetricsAggregator(registry, NewCustomEventRegistry(100), &cfg)
|
||||
|
||||
// Add some unique visitors so there's state to save
|
||||
agg.AddUnique("192.168.1.1", "Mozilla/5.0")
|
||||
|
||||
// Shutdown should save to configured path
|
||||
ctx := context.Background()
|
||||
if err := agg.Shutdown(ctx); err != nil {
|
||||
t.Fatalf("Shutdown failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify file was created at configured path
|
||||
if _, err := os.Stat(statePath); os.IsNotExist(err) {
|
||||
t.Errorf("state file was not created at configured path: %s", statePath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsAggregator_Shutdown_DefaultStatePath(t *testing.T) {
|
||||
registry := NewPathRegistry(100)
|
||||
|
||||
cfg := config.Config{
|
||||
Site: config.SiteConfig{
|
||||
Domains: []string{"example.com"}, // Required for validation
|
||||
SaltRotation: "daily",
|
||||
Collect: config.CollectConfig{
|
||||
Pageviews: true,
|
||||
},
|
||||
},
|
||||
Limits: config.LimitsConfig{
|
||||
MaxPaths: 1000,
|
||||
MaxSources: 500,
|
||||
},
|
||||
Server: config.ServerConfig{
|
||||
// StatePath not set - validation should set default
|
||||
StatePath: "",
|
||||
},
|
||||
}
|
||||
|
||||
// Validate to apply defaults
|
||||
if err := cfg.Validate(); err != nil {
|
||||
t.Fatalf("config validation failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify default was applied
|
||||
expectedDefault := "/var/lib/watchdog/hll.state"
|
||||
if cfg.Server.StatePath != expectedDefault {
|
||||
t.Errorf(
|
||||
"expected default StatePath %q, got %q",
|
||||
expectedDefault,
|
||||
cfg.Server.StatePath,
|
||||
)
|
||||
}
|
||||
|
||||
agg := NewMetricsAggregator(registry, NewCustomEventRegistry(100), &cfg)
|
||||
|
||||
// Add some unique visitors
|
||||
agg.AddUnique("192.168.1.1", "Mozilla/5.0")
|
||||
|
||||
// Shutdown should save to default path
|
||||
ctx := context.Background()
|
||||
if err := agg.Shutdown(ctx); err != nil {
|
||||
// Might fail due to permissions on /var/lib, which is OK for this test
|
||||
// We're just verifying the code path works
|
||||
t.Logf("Shutdown returned error (might be expected): %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ type ServerConfig struct {
|
|||
ListenAddr string `yaml:"listen_addr"`
|
||||
MetricsPath string `yaml:"metrics_path"`
|
||||
IngestionPath string `yaml:"ingestion_path"`
|
||||
StatePath string `yaml:"state_path"` // path to persist HLL state
|
||||
}
|
||||
|
||||
// YAML configuration file
|
||||
|
|
@ -170,6 +171,9 @@ func (c *Config) Validate() error {
|
|||
if c.Server.IngestionPath == "" {
|
||||
c.Server.IngestionPath = "/api/event"
|
||||
}
|
||||
if c.Server.StatePath == "" {
|
||||
c.Server.StatePath = "/var/lib/watchdog/hll.state"
|
||||
}
|
||||
|
||||
if c.Security.MetricsAuth.Enabled {
|
||||
if c.Security.MetricsAuth.Username == "" || c.Security.MetricsAuth.Password == "" {
|
||||
|
|
|
|||
|
|
@ -1,69 +1,78 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Watchdog Beacon Test</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
max-width: 600px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
button {
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
margin: 8px 4px;
|
||||
}
|
||||
button:hover {
|
||||
background: #45a049;
|
||||
}
|
||||
.info {
|
||||
background: #f0f0f0;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
code {
|
||||
background: #e8e8e8;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Watchdog Analytics Beacon Test</h1>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Watchdog Beacon Test</title>
|
||||
<style>
|
||||
body {
|
||||
font-family:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
sans-serif;
|
||||
max-width: 600px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
button {
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
margin: 8px 4px;
|
||||
}
|
||||
button:hover {
|
||||
background: #45a049;
|
||||
}
|
||||
.info {
|
||||
background: #f0f0f0;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
code {
|
||||
background: #e8e8e8;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: "Courier New", monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Watchdog Analytics Beacon Test</h1>
|
||||
|
||||
<div class="info">
|
||||
<p><strong>Instructions:</strong>
|
||||
This page automatically tracks a pageview on load.
|
||||
Click the buttons below to test custom event tracking.</p>
|
||||
</div>
|
||||
<div class="info">
|
||||
<p>
|
||||
<strong>Instructions:</strong> This page automatically tracks a pageview
|
||||
on load. Click the buttons below to test custom event tracking.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>Custom Events</h2>
|
||||
<button onclick="watchdog.track('signup')">Track Signup</button>
|
||||
<button onclick="watchdog.track('purchase')">Track Purchase</button>
|
||||
<button onclick="watchdog.track('download')">Track Download</button>
|
||||
<button onclick="watchdog.track('newsletter_subscribe')">Track Newsletter</button>
|
||||
<h2>Custom Events</h2>
|
||||
<button onclick="watchdog.track('signup')">Track Signup</button>
|
||||
<button onclick="watchdog.track('purchase')">Track Purchase</button>
|
||||
<button onclick="watchdog.track('download')">Track Download</button>
|
||||
<button onclick="watchdog.track('newsletter_subscribe')">
|
||||
Track Newsletter
|
||||
</button>
|
||||
|
||||
<div class="info">
|
||||
<p><strong>API Usage:</strong></p>
|
||||
<p>Track custom events: <code>watchdog.track('event_name')</code></p>
|
||||
<p>Manual pageview: <code>watchdog.trackPageview()</code></p>
|
||||
</div>
|
||||
<div class="info">
|
||||
<p><strong>API Usage:</strong></p>
|
||||
<p>Track custom events: <code>watchdog.track('event_name')</code></p>
|
||||
<p>Manual pageview: <code>watchdog.trackPageview()</code></p>
|
||||
</div>
|
||||
|
||||
<h2>Console</h2>
|
||||
<p>Open your browser's developer console (F12) and network tab to see beacon requests.</p>
|
||||
<h2>Console</h2>
|
||||
<p>
|
||||
Open your browser's developer console (F12) and network tab to see beacon
|
||||
requests.
|
||||
</p>
|
||||
|
||||
<!-- Load the beacon script -->
|
||||
<script src="/web/beacon.js"></script>
|
||||
</body>
|
||||
<!-- Load the beacon script -->
|
||||
<script src="/web/beacon.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue