Compare commits
No commits in common. "c116b035bd5d1e541d6f02f4bcae2eee7e6d0249" and "9043cf9bc0af84b842b6948dfe419dbde6690a64" have entirely different histories.
c116b035bd
...
9043cf9bc0
8
Makefile
8
Makefile
@ -1,11 +1,5 @@
|
|||||||
export DEBUG = routewatch
|
export DEBUG = routewatch
|
||||||
|
|
||||||
# Git revision for version embedding
|
|
||||||
GIT_REVISION := $(shell git rev-parse HEAD 2>/dev/null || echo "unknown")
|
|
||||||
GIT_REVISION_SHORT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
|
||||||
VERSION_PKG := git.eeqj.de/sneak/routewatch/internal/version
|
|
||||||
LDFLAGS := -X $(VERSION_PKG).GitRevision=$(GIT_REVISION) -X $(VERSION_PKG).GitRevisionShort=$(GIT_REVISION_SHORT)
|
|
||||||
|
|
||||||
.PHONY: test fmt lint build clean run asupdate
|
.PHONY: test fmt lint build clean run asupdate
|
||||||
|
|
||||||
all: test
|
all: test
|
||||||
@ -21,7 +15,7 @@ lint:
|
|||||||
golangci-lint run
|
golangci-lint run
|
||||||
|
|
||||||
build:
|
build:
|
||||||
CGO_ENABLED=1 go build -ldflags "$(LDFLAGS)" -o bin/routewatch cmd/routewatch/main.go
|
CGO_ENABLED=1 go build -o bin/routewatch cmd/routewatch/main.go
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin/
|
rm -rf bin/
|
||||||
|
|||||||
@ -30,14 +30,6 @@ type Tracker struct {
|
|||||||
// Route update metrics
|
// Route update metrics
|
||||||
ipv4UpdateRate metrics.Meter
|
ipv4UpdateRate metrics.Meter
|
||||||
ipv6UpdateRate metrics.Meter
|
ipv6UpdateRate metrics.Meter
|
||||||
|
|
||||||
// Announcement/withdrawal metrics
|
|
||||||
announcementCounter metrics.Counter
|
|
||||||
withdrawalCounter metrics.Counter
|
|
||||||
churnRate metrics.Meter // combined announcements + withdrawals per second
|
|
||||||
|
|
||||||
// BGP peer tracking
|
|
||||||
bgpPeerCount atomic.Int32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new metrics tracker
|
// New creates a new metrics tracker
|
||||||
@ -45,18 +37,15 @@ func New() *Tracker {
|
|||||||
registry := metrics.NewRegistry()
|
registry := metrics.NewRegistry()
|
||||||
|
|
||||||
return &Tracker{
|
return &Tracker{
|
||||||
registry: registry,
|
registry: registry,
|
||||||
messageCounter: metrics.NewCounter(),
|
messageCounter: metrics.NewCounter(),
|
||||||
byteCounter: metrics.NewCounter(),
|
byteCounter: metrics.NewCounter(),
|
||||||
messageRate: metrics.NewMeter(),
|
messageRate: metrics.NewMeter(),
|
||||||
byteRate: metrics.NewMeter(),
|
byteRate: metrics.NewMeter(),
|
||||||
wireByteCounter: metrics.NewCounter(),
|
wireByteCounter: metrics.NewCounter(),
|
||||||
wireByteRate: metrics.NewMeter(),
|
wireByteRate: metrics.NewMeter(),
|
||||||
ipv4UpdateRate: metrics.NewMeter(),
|
ipv4UpdateRate: metrics.NewMeter(),
|
||||||
ipv6UpdateRate: metrics.NewMeter(),
|
ipv6UpdateRate: metrics.NewMeter(),
|
||||||
announcementCounter: metrics.NewCounter(),
|
|
||||||
withdrawalCounter: metrics.NewCounter(),
|
|
||||||
churnRate: metrics.NewMeter(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,56 +134,6 @@ func (t *Tracker) RecordIPv6Update() {
|
|||||||
t.ipv6UpdateRate.Mark(1)
|
t.ipv6UpdateRate.Mark(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordAnnouncement records a route announcement
|
|
||||||
func (t *Tracker) RecordAnnouncement() {
|
|
||||||
t.announcementCounter.Inc(1)
|
|
||||||
t.churnRate.Mark(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordWithdrawal records a route withdrawal
|
|
||||||
func (t *Tracker) RecordWithdrawal() {
|
|
||||||
t.withdrawalCounter.Inc(1)
|
|
||||||
t.churnRate.Mark(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBGPPeerCount updates the current BGP peer count
|
|
||||||
func (t *Tracker) SetBGPPeerCount(count int) {
|
|
||||||
// BGP peer count is always small (< 1000), so int32 is safe
|
|
||||||
if count > 0 && count < 1<<31 {
|
|
||||||
t.bgpPeerCount.Store(int32(count)) //nolint:gosec // count is validated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBGPPeerCount returns the current BGP peer count
|
|
||||||
func (t *Tracker) GetBGPPeerCount() int {
|
|
||||||
return int(t.bgpPeerCount.Load())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAnnouncementCount returns the total announcement count
|
|
||||||
func (t *Tracker) GetAnnouncementCount() uint64 {
|
|
||||||
count := t.announcementCounter.Count()
|
|
||||||
if count < 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return uint64(count)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetWithdrawalCount returns the total withdrawal count
|
|
||||||
func (t *Tracker) GetWithdrawalCount() uint64 {
|
|
||||||
count := t.withdrawalCounter.Count()
|
|
||||||
if count < 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return uint64(count)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetChurnRate returns the route churn rate per second
|
|
||||||
func (t *Tracker) GetChurnRate() float64 {
|
|
||||||
return t.churnRate.Rate1()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRouteMetrics returns current route update metrics
|
// GetRouteMetrics returns current route update metrics
|
||||||
func (t *Tracker) GetRouteMetrics() RouteMetrics {
|
func (t *Tracker) GetRouteMetrics() RouteMetrics {
|
||||||
return RouteMetrics{
|
return RouteMetrics{
|
||||||
|
|||||||
@ -113,10 +113,6 @@ func (h *PrefixHandler) HandleMessage(msg *ristypes.RISMessage) {
|
|||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
path: msg.Path,
|
path: msg.Path,
|
||||||
})
|
})
|
||||||
// Record announcement in metrics
|
|
||||||
if h.metrics != nil {
|
|
||||||
h.metrics.RecordAnnouncement()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,10 +126,6 @@ func (h *PrefixHandler) HandleMessage(msg *ristypes.RISMessage) {
|
|||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
path: msg.Path,
|
path: msg.Path,
|
||||||
})
|
})
|
||||||
// Record withdrawal in metrics
|
|
||||||
if h.metrics != nil {
|
|
||||||
h.metrics.RecordWithdrawal()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we need to flush
|
// Check if we need to flush
|
||||||
|
|||||||
@ -320,23 +320,6 @@ func (s *Server) handleStats() http.HandlerFunc {
|
|||||||
MaxProcessTimeMs float64 `json:"max_process_time_ms"`
|
MaxProcessTimeMs float64 `json:"max_process_time_ms"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GCStats represents garbage collection statistics
|
|
||||||
type GCStats struct {
|
|
||||||
NumGC uint32 `json:"num_gc"`
|
|
||||||
TotalPauseMs uint64 `json:"total_pause_ms"`
|
|
||||||
LastPauseMs float64 `json:"last_pause_ms"`
|
|
||||||
HeapAllocBytes uint64 `json:"heap_alloc_bytes"`
|
|
||||||
HeapSysBytes uint64 `json:"heap_sys_bytes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// StreamStats represents stream statistics including announcements/withdrawals
|
|
||||||
type StreamStats struct {
|
|
||||||
Announcements uint64 `json:"announcements"`
|
|
||||||
Withdrawals uint64 `json:"withdrawals"`
|
|
||||||
RouteChurnPerSec float64 `json:"route_churn_per_sec"`
|
|
||||||
BGPPeerCount int `json:"bgp_peer_count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// StatsResponse represents the API statistics response
|
// StatsResponse represents the API statistics response
|
||||||
type StatsResponse struct {
|
type StatsResponse struct {
|
||||||
Uptime string `json:"uptime"`
|
Uptime string `json:"uptime"`
|
||||||
@ -352,8 +335,6 @@ func (s *Server) handleStats() http.HandlerFunc {
|
|||||||
GoVersion string `json:"go_version"`
|
GoVersion string `json:"go_version"`
|
||||||
Goroutines int `json:"goroutines"`
|
Goroutines int `json:"goroutines"`
|
||||||
MemoryUsage string `json:"memory_usage"`
|
MemoryUsage string `json:"memory_usage"`
|
||||||
GC GCStats `json:"gc"`
|
|
||||||
Stream StreamStats `json:"stream"`
|
|
||||||
ASNs int `json:"asns"`
|
ASNs int `json:"asns"`
|
||||||
Prefixes int `json:"prefixes"`
|
Prefixes int `json:"prefixes"`
|
||||||
IPv4Prefixes int `json:"ipv4_prefixes"`
|
IPv4Prefixes int `json:"ipv4_prefixes"`
|
||||||
@ -469,25 +450,6 @@ func (s *Server) handleStats() http.HandlerFunc {
|
|||||||
connectionDuration = time.Since(metrics.ConnectedSince).Truncate(time.Second).String()
|
connectionDuration = time.Since(metrics.ConnectedSince).Truncate(time.Second).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get announcement/withdrawal stats from metrics tracker
|
|
||||||
metricsTracker := s.streamer.GetMetricsTracker()
|
|
||||||
announcements := metricsTracker.GetAnnouncementCount()
|
|
||||||
withdrawals := metricsTracker.GetWithdrawalCount()
|
|
||||||
churnRate := metricsTracker.GetChurnRate()
|
|
||||||
bgpPeerCount := metricsTracker.GetBGPPeerCount()
|
|
||||||
|
|
||||||
// Calculate last GC pause
|
|
||||||
const (
|
|
||||||
nanosecondsPerMillisecond = 1e6
|
|
||||||
gcPauseHistorySize = 256 // Size of runtime.MemStats.PauseNs circular buffer
|
|
||||||
)
|
|
||||||
var lastPauseMs float64
|
|
||||||
if memStats.NumGC > 0 {
|
|
||||||
// PauseNs is a circular buffer, get the most recent pause
|
|
||||||
lastPauseIdx := (memStats.NumGC + gcPauseHistorySize - 1) % gcPauseHistorySize
|
|
||||||
lastPauseMs = float64(memStats.PauseNs[lastPauseIdx]) / nanosecondsPerMillisecond
|
|
||||||
}
|
|
||||||
|
|
||||||
stats := StatsResponse{
|
stats := StatsResponse{
|
||||||
Uptime: uptime,
|
Uptime: uptime,
|
||||||
TotalMessages: metrics.TotalMessages,
|
TotalMessages: metrics.TotalMessages,
|
||||||
@ -502,19 +464,6 @@ func (s *Server) handleStats() http.HandlerFunc {
|
|||||||
GoVersion: runtime.Version(),
|
GoVersion: runtime.Version(),
|
||||||
Goroutines: runtime.NumGoroutine(),
|
Goroutines: runtime.NumGoroutine(),
|
||||||
MemoryUsage: humanize.Bytes(memStats.Alloc),
|
MemoryUsage: humanize.Bytes(memStats.Alloc),
|
||||||
GC: GCStats{
|
|
||||||
NumGC: memStats.NumGC,
|
|
||||||
TotalPauseMs: memStats.PauseTotalNs / uint64(nanosecondsPerMillisecond),
|
|
||||||
LastPauseMs: lastPauseMs,
|
|
||||||
HeapAllocBytes: memStats.HeapAlloc,
|
|
||||||
HeapSysBytes: memStats.HeapSys,
|
|
||||||
},
|
|
||||||
Stream: StreamStats{
|
|
||||||
Announcements: announcements,
|
|
||||||
Withdrawals: withdrawals,
|
|
||||||
RouteChurnPerSec: churnRate,
|
|
||||||
BGPPeerCount: bgpPeerCount,
|
|
||||||
},
|
|
||||||
ASNs: dbStats.ASNs,
|
ASNs: dbStats.ASNs,
|
||||||
Prefixes: dbStats.Prefixes,
|
Prefixes: dbStats.Prefixes,
|
||||||
IPv4Prefixes: dbStats.IPv4Prefixes,
|
IPv4Prefixes: dbStats.IPv4Prefixes,
|
||||||
@ -736,8 +685,7 @@ func (s *Server) handleASDetailJSON() http.HandlerFunc {
|
|||||||
// handlePrefixDetailJSON returns prefix details as JSON
|
// handlePrefixDetailJSON returns prefix details as JSON
|
||||||
func (s *Server) handlePrefixDetailJSON() http.HandlerFunc {
|
func (s *Server) handlePrefixDetailJSON() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Get wildcard parameter (everything after /prefix/)
|
prefixParam := chi.URLParam(r, "prefix")
|
||||||
prefixParam := chi.URLParam(r, "*")
|
|
||||||
if prefixParam == "" {
|
if prefixParam == "" {
|
||||||
writeJSONError(w, http.StatusBadRequest, "Prefix parameter is required")
|
writeJSONError(w, http.StatusBadRequest, "Prefix parameter is required")
|
||||||
|
|
||||||
@ -903,8 +851,7 @@ func (s *Server) handleASDetail() http.HandlerFunc {
|
|||||||
// handlePrefixDetail returns a handler that serves the prefix detail HTML page
|
// handlePrefixDetail returns a handler that serves the prefix detail HTML page
|
||||||
func (s *Server) handlePrefixDetail() http.HandlerFunc {
|
func (s *Server) handlePrefixDetail() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Get wildcard parameter (everything after /prefix/)
|
prefixParam := chi.URLParam(r, "prefix")
|
||||||
prefixParam := chi.URLParam(r, "*")
|
|
||||||
if prefixParam == "" {
|
if prefixParam == "" {
|
||||||
http.Error(w, "Prefix parameter is required", http.StatusBadRequest)
|
http.Error(w, "Prefix parameter is required", http.StatusBadRequest)
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ func (s *Server) setupRoutes() {
|
|||||||
|
|
||||||
// AS and prefix detail pages
|
// AS and prefix detail pages
|
||||||
r.Get("/as/{asn}", s.handleASDetail())
|
r.Get("/as/{asn}", s.handleASDetail())
|
||||||
r.Get("/prefix/*", s.handlePrefixDetail())
|
r.Get("/prefix/{prefix}", s.handlePrefixDetail())
|
||||||
r.Get("/prefixlength/{length}", s.handlePrefixLength())
|
r.Get("/prefixlength/{length}", s.handlePrefixLength())
|
||||||
r.Get("/prefixlength6/{length}", s.handlePrefixLength6())
|
r.Get("/prefixlength6/{length}", s.handlePrefixLength6())
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ func (s *Server) setupRoutes() {
|
|||||||
r.Get("/stats", s.handleStats())
|
r.Get("/stats", s.handleStats())
|
||||||
r.Get("/ip/{ip}", s.handleIPLookup())
|
r.Get("/ip/{ip}", s.handleIPLookup())
|
||||||
r.Get("/as/{asn}", s.handleASDetailJSON())
|
r.Get("/as/{asn}", s.handleASDetailJSON())
|
||||||
r.Get("/prefix/*", s.handlePrefixDetailJSON())
|
r.Get("/prefix/{prefix}", s.handlePrefixDetailJSON())
|
||||||
})
|
})
|
||||||
|
|
||||||
s.router = r
|
s.router = r
|
||||||
|
|||||||
@ -114,8 +114,6 @@ type Streamer struct {
|
|||||||
metrics *metrics.Tracker
|
metrics *metrics.Tracker
|
||||||
totalDropped uint64 // Total dropped messages across all handlers
|
totalDropped uint64 // Total dropped messages across all handlers
|
||||||
random *rand.Rand // Random number generator for backpressure drops
|
random *rand.Rand // Random number generator for backpressure drops
|
||||||
bgpPeers map[string]bool // Track active BGP peers by peer IP
|
|
||||||
bgpPeersMu sync.RWMutex // Protects bgpPeers map
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Streamer instance configured to connect to the RIS Live API.
|
// New creates a new Streamer instance configured to connect to the RIS Live API.
|
||||||
@ -134,8 +132,7 @@ func New(logger *logger.Logger, metrics *metrics.Tracker) *Streamer {
|
|||||||
handlers: make([]*handlerInfo, 0),
|
handlers: make([]*handlerInfo, 0),
|
||||||
metrics: metrics,
|
metrics: metrics,
|
||||||
//nolint:gosec // Non-cryptographic randomness is fine for backpressure
|
//nolint:gosec // Non-cryptographic randomness is fine for backpressure
|
||||||
random: rand.New(rand.NewSource(time.Now().UnixNano())),
|
random: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
bgpPeers: make(map[string]bool),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -611,32 +608,18 @@ func (s *Streamer) stream(ctx context.Context) error {
|
|||||||
// BGP keepalive messages - silently process
|
// BGP keepalive messages - silently process
|
||||||
continue
|
continue
|
||||||
case "OPEN":
|
case "OPEN":
|
||||||
// BGP open messages - track peer as active
|
// BGP open messages
|
||||||
s.bgpPeersMu.Lock()
|
|
||||||
s.bgpPeers[msg.Peer] = true
|
|
||||||
peerCount := len(s.bgpPeers)
|
|
||||||
s.bgpPeersMu.Unlock()
|
|
||||||
s.metrics.SetBGPPeerCount(peerCount)
|
|
||||||
|
|
||||||
s.logger.Info("BGP session opened",
|
s.logger.Info("BGP session opened",
|
||||||
"peer", msg.Peer,
|
"peer", msg.Peer,
|
||||||
"peer_asn", msg.PeerASN,
|
"peer_asn", msg.PeerASN,
|
||||||
"total_peers", peerCount,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
case "NOTIFICATION":
|
case "NOTIFICATION":
|
||||||
// BGP notification messages (session closed)
|
// BGP notification messages (errors)
|
||||||
s.bgpPeersMu.Lock()
|
|
||||||
delete(s.bgpPeers, msg.Peer)
|
|
||||||
peerCount := len(s.bgpPeers)
|
|
||||||
s.bgpPeersMu.Unlock()
|
|
||||||
s.metrics.SetBGPPeerCount(peerCount)
|
|
||||||
|
|
||||||
s.logger.Warn("BGP notification",
|
s.logger.Warn("BGP notification",
|
||||||
"peer", msg.Peer,
|
"peer", msg.Peer,
|
||||||
"peer_asn", msg.PeerASN,
|
"peer_asn", msg.PeerASN,
|
||||||
"total_peers", peerCount,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|||||||
@ -72,27 +72,6 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
.footer {
|
|
||||||
margin-top: 40px;
|
|
||||||
padding: 20px;
|
|
||||||
background: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
|
|
||||||
text-align: center;
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.footer a {
|
|
||||||
color: #0066cc;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.footer a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.footer .separator {
|
|
||||||
margin: 0 10px;
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -133,10 +112,6 @@
|
|||||||
<span class="metric-label">Reconnections</span>
|
<span class="metric-label">Reconnections</span>
|
||||||
<span class="metric-value" id="reconnect_count">-</span>
|
<span class="metric-value" id="reconnect_count">-</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="metric">
|
|
||||||
<span class="metric-label">BGP Peers</span>
|
|
||||||
<span class="metric-value" id="bgp_peer_count">-</span>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<span class="metric-label">Total Messages</span>
|
<span class="metric-label">Total Messages</span>
|
||||||
<span class="metric-value" id="total_messages">-</span>
|
<span class="metric-value" id="total_messages">-</span>
|
||||||
@ -145,18 +120,6 @@
|
|||||||
<span class="metric-label">Messages/sec</span>
|
<span class="metric-label">Messages/sec</span>
|
||||||
<span class="metric-value" id="messages_per_sec">-</span>
|
<span class="metric-value" id="messages_per_sec">-</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="metric">
|
|
||||||
<span class="metric-label">Announcements</span>
|
|
||||||
<span class="metric-value" id="announcements">-</span>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
|
||||||
<span class="metric-label">Withdrawals</span>
|
|
||||||
<span class="metric-value" id="withdrawals">-</span>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
|
||||||
<span class="metric-label">Route Churn/sec</span>
|
|
||||||
<span class="metric-value" id="route_churn_per_sec">-</span>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<span class="metric-label">Total Data</span>
|
<span class="metric-label">Total Data</span>
|
||||||
<span class="metric-value" id="total_wire_bytes">-</span>
|
<span class="metric-value" id="total_wire_bytes">-</span>
|
||||||
@ -167,30 +130,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status-card">
|
|
||||||
<h2>GC Statistics</h2>
|
|
||||||
<div class="metric">
|
|
||||||
<span class="metric-label">GC Runs</span>
|
|
||||||
<span class="metric-value" id="gc_num">-</span>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
|
||||||
<span class="metric-label">Total Pause</span>
|
|
||||||
<span class="metric-value" id="gc_total_pause">-</span>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
|
||||||
<span class="metric-label">Last Pause</span>
|
|
||||||
<span class="metric-value" id="gc_last_pause">-</span>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
|
||||||
<span class="metric-label">Heap Alloc</span>
|
|
||||||
<span class="metric-value" id="gc_heap_alloc">-</span>
|
|
||||||
</div>
|
|
||||||
<div class="metric">
|
|
||||||
<span class="metric-label">Heap Sys</span>
|
|
||||||
<span class="metric-value" id="gc_heap_sys">-</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="status-card">
|
<div class="status-card">
|
||||||
<h2>Database Statistics</h2>
|
<h2>Database Statistics</h2>
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
@ -405,19 +344,10 @@
|
|||||||
document.getElementById('memory_usage').textContent = '-';
|
document.getElementById('memory_usage').textContent = '-';
|
||||||
document.getElementById('connection_duration').textContent = '-';
|
document.getElementById('connection_duration').textContent = '-';
|
||||||
document.getElementById('reconnect_count').textContent = '-';
|
document.getElementById('reconnect_count').textContent = '-';
|
||||||
document.getElementById('bgp_peer_count').textContent = '-';
|
|
||||||
document.getElementById('total_messages').textContent = '-';
|
document.getElementById('total_messages').textContent = '-';
|
||||||
document.getElementById('messages_per_sec').textContent = '-';
|
document.getElementById('messages_per_sec').textContent = '-';
|
||||||
document.getElementById('announcements').textContent = '-';
|
|
||||||
document.getElementById('withdrawals').textContent = '-';
|
|
||||||
document.getElementById('route_churn_per_sec').textContent = '-';
|
|
||||||
document.getElementById('total_wire_bytes').textContent = '-';
|
document.getElementById('total_wire_bytes').textContent = '-';
|
||||||
document.getElementById('wire_mbits_per_sec').textContent = '-';
|
document.getElementById('wire_mbits_per_sec').textContent = '-';
|
||||||
document.getElementById('gc_num').textContent = '-';
|
|
||||||
document.getElementById('gc_total_pause').textContent = '-';
|
|
||||||
document.getElementById('gc_last_pause').textContent = '-';
|
|
||||||
document.getElementById('gc_heap_alloc').textContent = '-';
|
|
||||||
document.getElementById('gc_heap_sys').textContent = '-';
|
|
||||||
document.getElementById('asns').textContent = '-';
|
document.getElementById('asns').textContent = '-';
|
||||||
document.getElementById('prefixes').textContent = '-';
|
document.getElementById('prefixes').textContent = '-';
|
||||||
document.getElementById('ipv4_prefixes').textContent = '-';
|
document.getElementById('ipv4_prefixes').textContent = '-';
|
||||||
@ -491,23 +421,6 @@
|
|||||||
document.getElementById('ipv4_updates_per_sec').textContent = data.ipv4_updates_per_sec.toFixed(1);
|
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);
|
document.getElementById('ipv6_updates_per_sec').textContent = data.ipv6_updates_per_sec.toFixed(1);
|
||||||
|
|
||||||
// Update stream stats
|
|
||||||
if (data.stream) {
|
|
||||||
document.getElementById('bgp_peer_count').textContent = formatNumber(data.stream.bgp_peer_count);
|
|
||||||
document.getElementById('announcements').textContent = formatNumber(data.stream.announcements);
|
|
||||||
document.getElementById('withdrawals').textContent = formatNumber(data.stream.withdrawals);
|
|
||||||
document.getElementById('route_churn_per_sec').textContent = data.stream.route_churn_per_sec.toFixed(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update GC stats
|
|
||||||
if (data.gc) {
|
|
||||||
document.getElementById('gc_num').textContent = formatNumber(data.gc.num_gc);
|
|
||||||
document.getElementById('gc_total_pause').textContent = data.gc.total_pause_ms + ' ms';
|
|
||||||
document.getElementById('gc_last_pause').textContent = data.gc.last_pause_ms.toFixed(3) + ' ms';
|
|
||||||
document.getElementById('gc_heap_alloc').textContent = formatBytes(data.gc.heap_alloc_bytes);
|
|
||||||
document.getElementById('gc_heap_sys').textContent = formatBytes(data.gc.heap_sys_bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update WHOIS stats
|
// Update WHOIS stats
|
||||||
if (data.whois_stats) {
|
if (data.whois_stats) {
|
||||||
document.getElementById('whois_fresh').textContent = formatNumber(data.whois_stats.fresh_asns);
|
document.getElementById('whois_fresh').textContent = formatNumber(data.whois_stats.fresh_asns);
|
||||||
@ -542,13 +455,5 @@
|
|||||||
updateStatus();
|
updateStatus();
|
||||||
setInterval(updateStatus, 2000);
|
setInterval(updateStatus, 2000);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<footer class="footer">
|
|
||||||
<span><a href="{{appRepoURL}}">{{appName}}</a> by <a href="{{appAuthorURL}}">{{appAuthor}}</a></span>
|
|
||||||
<span class="separator">|</span>
|
|
||||||
<span>{{appLicense}}</span>
|
|
||||||
<span class="separator">|</span>
|
|
||||||
<span><a href="{{appGitCommitURL}}">{{appGitRevision}}</a></span>
|
|
||||||
</footer>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -7,8 +7,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.eeqj.de/sneak/routewatch/internal/version"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed status.html
|
//go:embed status.html
|
||||||
@ -88,19 +86,12 @@ func initTemplates() {
|
|||||||
|
|
||||||
// Create common template functions
|
// Create common template functions
|
||||||
funcs := template.FuncMap{
|
funcs := template.FuncMap{
|
||||||
"timeSince": timeSince,
|
"timeSince": timeSince,
|
||||||
"urlEncode": url.QueryEscape,
|
"urlEncode": url.QueryEscape,
|
||||||
"appName": func() string { return version.Name },
|
|
||||||
"appAuthor": func() string { return version.Author },
|
|
||||||
"appAuthorURL": func() string { return version.AuthorURL },
|
|
||||||
"appLicense": func() string { return version.License },
|
|
||||||
"appRepoURL": func() string { return version.RepoURL },
|
|
||||||
"appGitRevision": func() string { return version.GitRevisionShort },
|
|
||||||
"appGitCommitURL": func() string { return version.CommitURL() },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse status template
|
// Parse status template
|
||||||
defaultTemplates.Status, err = template.New("status").Funcs(funcs).Parse(statusHTML)
|
defaultTemplates.Status, err = template.New("status").Parse(statusHTML)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("failed to parse status template: " + err.Error())
|
panic("failed to parse status template: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
// Package version provides build version information
|
|
||||||
package version
|
|
||||||
|
|
||||||
// Build-time variables set via ldflags
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // These must be variables to allow ldflags injection at build time
|
|
||||||
var (
|
|
||||||
// GitRevision is the git commit hash
|
|
||||||
GitRevision = "unknown"
|
|
||||||
// GitRevisionShort is the short git commit hash (7 chars)
|
|
||||||
GitRevisionShort = "unknown"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Name is the program name
|
|
||||||
Name = "routewatch"
|
|
||||||
// Author is the program author
|
|
||||||
Author = "@sneak"
|
|
||||||
// AuthorURL is the author's website
|
|
||||||
AuthorURL = "https://sneak.berlin"
|
|
||||||
// License is the program license
|
|
||||||
License = "WTFPL"
|
|
||||||
// RepoURL is the git repository URL
|
|
||||||
RepoURL = "https://git.eeqj.de/sneak/routewatch"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CommitURL returns the URL to view the current commit
|
|
||||||
func CommitURL() string {
|
|
||||||
if GitRevision == "unknown" {
|
|
||||||
return RepoURL
|
|
||||||
}
|
|
||||||
|
|
||||||
return RepoURL + "/commit/" + GitRevision
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user