Add handler queue metrics to status page
- Add GetHandlerStats() method to streamer to expose handler metrics - Include queue length/capacity, processed/dropped counts, timing stats - Update API to include handler_stats in response - Add dynamic handler stats display to status page HTML - Shows separate status box for each handler with all metrics
This commit is contained in:
parent
6593a7be76
commit
1a0622efaa
@ -232,25 +232,38 @@ func (s *Server) handleStatusJSON() http.HandlerFunc {
|
|||||||
|
|
||||||
// handleStats returns a handler that serves API v1 statistics
|
// handleStats returns a handler that serves API v1 statistics
|
||||||
func (s *Server) handleStats() http.HandlerFunc {
|
func (s *Server) handleStats() http.HandlerFunc {
|
||||||
|
// HandlerStatsInfo represents handler statistics in the API response
|
||||||
|
type HandlerStatsInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
QueueLength int `json:"queue_length"`
|
||||||
|
QueueCapacity int `json:"queue_capacity"`
|
||||||
|
ProcessedCount uint64 `json:"processed_count"`
|
||||||
|
DroppedCount uint64 `json:"dropped_count"`
|
||||||
|
AvgProcessTimeMs float64 `json:"avg_process_time_ms"`
|
||||||
|
MinProcessTimeMs float64 `json:"min_process_time_ms"`
|
||||||
|
MaxProcessTimeMs float64 `json:"max_process_time_ms"`
|
||||||
|
}
|
||||||
|
|
||||||
// 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"`
|
||||||
TotalMessages uint64 `json:"total_messages"`
|
TotalMessages uint64 `json:"total_messages"`
|
||||||
TotalBytes uint64 `json:"total_bytes"`
|
TotalBytes uint64 `json:"total_bytes"`
|
||||||
MessagesPerSec float64 `json:"messages_per_sec"`
|
MessagesPerSec float64 `json:"messages_per_sec"`
|
||||||
MbitsPerSec float64 `json:"mbits_per_sec"`
|
MbitsPerSec float64 `json:"mbits_per_sec"`
|
||||||
Connected bool `json:"connected"`
|
Connected bool `json:"connected"`
|
||||||
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"`
|
||||||
IPv6Prefixes int `json:"ipv6_prefixes"`
|
IPv6Prefixes int `json:"ipv6_prefixes"`
|
||||||
Peerings int `json:"peerings"`
|
Peerings int `json:"peerings"`
|
||||||
DatabaseSizeBytes int64 `json:"database_size_bytes"`
|
DatabaseSizeBytes int64 `json:"database_size_bytes"`
|
||||||
LiveRoutes int `json:"live_routes"`
|
LiveRoutes int `json:"live_routes"`
|
||||||
IPv4Routes int `json:"ipv4_routes"`
|
IPv4Routes int `json:"ipv4_routes"`
|
||||||
IPv6Routes int `json:"ipv6_routes"`
|
IPv6Routes int `json:"ipv6_routes"`
|
||||||
IPv4UpdatesPerSec float64 `json:"ipv4_updates_per_sec"`
|
IPv4UpdatesPerSec float64 `json:"ipv4_updates_per_sec"`
|
||||||
IPv6UpdatesPerSec float64 `json:"ipv6_updates_per_sec"`
|
IPv6UpdatesPerSec float64 `json:"ipv6_updates_per_sec"`
|
||||||
|
HandlerStats []HandlerStatsInfo `json:"handler_stats"`
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -312,6 +325,23 @@ func (s *Server) handleStats() http.HandlerFunc {
|
|||||||
// Get detailed routing table stats
|
// Get detailed routing table stats
|
||||||
rtStats := s.routingTable.GetDetailedStats()
|
rtStats := s.routingTable.GetDetailedStats()
|
||||||
|
|
||||||
|
// Get handler stats
|
||||||
|
handlerStats := s.streamer.GetHandlerStats()
|
||||||
|
handlerStatsInfo := make([]HandlerStatsInfo, 0, len(handlerStats))
|
||||||
|
const microsecondsPerMillisecond = 1000.0
|
||||||
|
for _, hs := range handlerStats {
|
||||||
|
handlerStatsInfo = append(handlerStatsInfo, HandlerStatsInfo{
|
||||||
|
Name: hs.Name,
|
||||||
|
QueueLength: hs.QueueLength,
|
||||||
|
QueueCapacity: hs.QueueCapacity,
|
||||||
|
ProcessedCount: hs.ProcessedCount,
|
||||||
|
DroppedCount: hs.DroppedCount,
|
||||||
|
AvgProcessTimeMs: float64(hs.AvgProcessTime.Microseconds()) / microsecondsPerMillisecond,
|
||||||
|
MinProcessTimeMs: float64(hs.MinProcessTime.Microseconds()) / microsecondsPerMillisecond,
|
||||||
|
MaxProcessTimeMs: float64(hs.MaxProcessTime.Microseconds()) / microsecondsPerMillisecond,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
stats := StatsResponse{
|
stats := StatsResponse{
|
||||||
Uptime: uptime,
|
Uptime: uptime,
|
||||||
TotalMessages: metrics.TotalMessages,
|
TotalMessages: metrics.TotalMessages,
|
||||||
@ -330,6 +360,7 @@ func (s *Server) handleStats() http.HandlerFunc {
|
|||||||
IPv6Routes: rtStats.IPv6Routes,
|
IPv6Routes: rtStats.IPv6Routes,
|
||||||
IPv4UpdatesPerSec: rtStats.IPv4UpdatesRate,
|
IPv4UpdatesPerSec: rtStats.IPv4UpdatesRate,
|
||||||
IPv6UpdatesPerSec: rtStats.IPv6UpdatesRate,
|
IPv6UpdatesPerSec: rtStats.IPv6UpdatesRate,
|
||||||
|
HandlerStats: handlerStatsInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
@ -195,6 +195,57 @@ func (s *Streamer) GetMetrics() metrics.StreamMetrics {
|
|||||||
return s.metrics.GetStreamMetrics()
|
return s.metrics.GetStreamMetrics()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandlerStats represents metrics for a single handler
|
||||||
|
type HandlerStats struct {
|
||||||
|
Name string
|
||||||
|
QueueLength int
|
||||||
|
QueueCapacity int
|
||||||
|
ProcessedCount uint64
|
||||||
|
DroppedCount uint64
|
||||||
|
AvgProcessTime time.Duration
|
||||||
|
MinProcessTime time.Duration
|
||||||
|
MaxProcessTime time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHandlerStats returns current handler statistics
|
||||||
|
func (s *Streamer) GetHandlerStats() []HandlerStats {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
|
||||||
|
stats := make([]HandlerStats, 0, len(s.handlers))
|
||||||
|
|
||||||
|
for _, info := range s.handlers {
|
||||||
|
info.metrics.mu.Lock()
|
||||||
|
|
||||||
|
hs := HandlerStats{
|
||||||
|
Name: fmt.Sprintf("%T", info.handler),
|
||||||
|
QueueLength: len(info.queue),
|
||||||
|
QueueCapacity: cap(info.queue),
|
||||||
|
ProcessedCount: info.metrics.processedCount,
|
||||||
|
DroppedCount: info.metrics.droppedCount,
|
||||||
|
MinProcessTime: info.metrics.minTime,
|
||||||
|
MaxProcessTime: info.metrics.maxTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate average time
|
||||||
|
if info.metrics.processedCount > 0 {
|
||||||
|
processedCount := info.metrics.processedCount
|
||||||
|
const maxInt64 = 1<<63 - 1
|
||||||
|
if processedCount > maxInt64 {
|
||||||
|
processedCount = maxInt64
|
||||||
|
}
|
||||||
|
//nolint:gosec // processedCount is explicitly bounded above
|
||||||
|
hs.AvgProcessTime = info.metrics.totalTime / time.Duration(processedCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
info.metrics.mu.Unlock()
|
||||||
|
|
||||||
|
stats = append(stats, hs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats
|
||||||
|
}
|
||||||
|
|
||||||
// GetDroppedMessages returns the total number of dropped messages
|
// GetDroppedMessages returns the total number of dropped messages
|
||||||
func (s *Streamer) GetDroppedMessages() uint64 {
|
func (s *Streamer) GetDroppedMessages() uint64 {
|
||||||
return atomic.LoadUint64(&s.totalDropped)
|
return atomic.LoadUint64(&s.totalDropped)
|
||||||
|
@ -153,6 +153,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="handler-stats-container" class="status-grid">
|
||||||
|
<!-- Handler stats will be dynamically added here -->
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function formatBytes(bytes) {
|
function formatBytes(bytes) {
|
||||||
if (bytes === 0) return '0 B';
|
if (bytes === 0) return '0 B';
|
||||||
@ -166,6 +170,45 @@
|
|||||||
return num.toLocaleString();
|
return num.toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateHandlerStats(handlerStats) {
|
||||||
|
const container = document.getElementById('handler-stats-container');
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
handlerStats.forEach(handler => {
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'status-card';
|
||||||
|
|
||||||
|
// Extract handler name (remove package prefix)
|
||||||
|
const handlerName = handler.name.split('.').pop();
|
||||||
|
|
||||||
|
card.innerHTML = `
|
||||||
|
<h2>${handlerName}</h2>
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">Queue</span>
|
||||||
|
<span class="metric-value">${handler.queue_length}/${handler.queue_capacity}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">Processed</span>
|
||||||
|
<span class="metric-value">${formatNumber(handler.processed_count)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">Dropped</span>
|
||||||
|
<span class="metric-value ${handler.dropped_count > 0 ? 'disconnected' : ''}">${formatNumber(handler.dropped_count)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">Avg Time</span>
|
||||||
|
<span class="metric-value">${handler.avg_process_time_ms.toFixed(2)} ms</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<span class="metric-label">Min/Max Time</span>
|
||||||
|
<span class="metric-value">${handler.min_process_time_ms.toFixed(2)} / ${handler.max_process_time_ms.toFixed(2)} ms</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
container.appendChild(card);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function updateStatus() {
|
function updateStatus() {
|
||||||
fetch('/api/v1/stats')
|
fetch('/api/v1/stats')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
@ -203,6 +246,9 @@
|
|||||||
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 handler stats
|
||||||
|
updateHandlerStats(data.handler_stats || []);
|
||||||
|
|
||||||
// Clear any errors
|
// Clear any errors
|
||||||
document.getElementById('error').style.display = 'none';
|
document.getElementById('error').style.display = 'none';
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user