Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion pkg/edge/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,26 @@ type Config struct {

// Cloud holds the upstream (L2/L3) connection settings.
Cloud CloudConfig `json:"cloud"`

// StatusInterval is how often status snapshots are sent to the Fleet Manager.
// Defaults to 5 minutes if zero.
StatusInterval time.Duration `json:"status_interval"`

// MaxRetries is the maximum number of delivery retries for failed HTTP calls.
// Defaults to 3 if zero.
MaxRetries int `json:"max_retries"`

// RetryBaseDelay is the initial backoff duration for retries.
// Each retry doubles this with jitter. Defaults to 1s if zero.
RetryBaseDelay time.Duration `json:"retry_base_delay"`

// EventQueueSize is the capacity of the buffered event queue for async reporting.
// Defaults to 256 if zero.
EventQueueSize int `json:"event_queue_size"`

// EventWorkers is the number of goroutines draining the async event queue.
// Defaults to 1 if zero.
EventWorkers int `json:"event_workers"`
}

// CloudConfig holds the upstream cloud/gateway connection settings.
Expand All @@ -46,5 +66,10 @@ func DefaultConfig() Config {
Endpoint: "http://localhost:8080",
HeartbeatInterval: 30 * time.Second,
},
StatusInterval: 5 * time.Minute,
MaxRetries: 3,
RetryBaseDelay: 1 * time.Second,
EventQueueSize: 256,
EventWorkers: 1,
}
}
}
72 changes: 72 additions & 0 deletions pkg/edge/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package edge

import (
"expvar"
"sync"
"sync/atomic"
"time"
)

// ReporterMetrics holds live counters and gauges for the edge reporter.
// Exported via expvar under "edgereporter" so operators can scrape them
// alongside any Prometheus /debug/vars exporter.
type ReporterMetrics struct {
mu sync.RWMutex

// Counters (cumulative)
RegistrationsTotal atomic.Int64 `json:"registrations_total"`
HeartbeatsTotal atomic.Int64 `json:"heartbeats_total"`
StatusReportsTotal atomic.Int64 `json:"status_reports_total"`
EventsTotal atomic.Int64 `json:"events_total"`
QueuedEventsTotal atomic.Int64 `json:"queued_events_total"`
DeliveriesTotal atomic.Int64 `json:"deliveries_total"` // all HTTP POST attempts
DeliveriesOK atomic.Int64 `json:"deliveries_ok"` // 2xx responses
DeliveriesFailed atomic.Int64 `json:"deliveries_failed"` // non-2xx or network errors
RetriesTotal atomic.Int64 `json:"retries_total"` // retry attempts due to failure
DroppedEventsTotal atomic.Int64 `json:"dropped_events_total"` // events dropped when queue full

// Gauges (snapshots)
QueueDepth atomic.Int64 `json:"queue_depth"`
LastDeliveryOK atomic.Bool `json:"last_delivery_ok"`

// Latency buckets (ms) for the most recent deliveries (ring buffer)
latencyMu sync.Mutex
latencyRing [64]time.Duration
latencyIdx int
}

var metrics = &ReporterMetrics{}

func init() {
expvar.Publish("edgereporter", expvar.Func(func() any {
return metrics.Snapshot()
}))
}

// Snapshot returns a consistent read of all metrics.
func (m *ReporterMetrics) Snapshot() map[string]any {
m.mu.RLock()
defer m.mu.RUnlock()

return map[string]any{
"registrations_total": m.RegistrationsTotal.Load(),
"heartbeats_total": m.HeartbeatsTotal.Load(),
"status_reports_total": m.StatusReportsTotal.Load(),
"events_total": m.EventsTotal.Load(),
"queued_events_total": m.QueuedEventsTotal.Load(),
"deliveries_total": m.DeliveriesTotal.Load(),
"deliveries_ok": m.DeliveriesOK.Load(),
"deliveries_failed": m.DeliveriesFailed.Load(),
"retries_total": m.RetriesTotal.Load(),
"dropped_events_total": m.DroppedEventsTotal.Load(),
"queue_depth": m.QueueDepth.Load(),
"last_delivery_ok": m.LastDeliveryOK.Load(),
}
}

func (m *ReporterMetrics) recordLatency(d time.Duration) {
m.latencyMu.Lock()
m.latencyRing[m.latencyIdx%64] = d
m.latencyIdx++
m.latencyMu.Unlock()
}
Loading