Merge pull request #51 from hobbyistlabs-coder/feature/opentelemetry-metrics-integration-3940720966979177033
feat: integrate OpenTelemetry metrics for system health visibility
This commit is contained in:
commit
c4aa14f0cb
2 changed files with 81 additions and 1 deletions
5
go.mod
5
go.mod
|
|
@ -30,6 +30,8 @@ require (
|
||||||
github.com/tencent-connect/botgo v0.2.1
|
github.com/tencent-connect/botgo v0.2.1
|
||||||
github.com/traefik/yaegi v0.16.1
|
github.com/traefik/yaegi v0.16.1
|
||||||
go.mau.fi/whatsmeow v0.0.0-20260219150138-7ae702b1eed4
|
go.mau.fi/whatsmeow v0.0.0-20260219150138-7ae702b1eed4
|
||||||
|
go.opentelemetry.io/otel v1.29.0
|
||||||
|
go.opentelemetry.io/otel/metric v1.29.0
|
||||||
golang.org/x/oauth2 v0.35.0
|
golang.org/x/oauth2 v0.35.0
|
||||||
golang.org/x/time v0.14.0
|
golang.org/x/time v0.14.0
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.11
|
||||||
|
|
@ -49,6 +51,8 @@ require (
|
||||||
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
|
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
|
||||||
github.com/gdamore/encoding v1.0.1 // indirect
|
github.com/gdamore/encoding v1.0.1 // indirect
|
||||||
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
|
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-stack/stack v1.8.1 // indirect
|
github.com/go-stack/stack v1.8.1 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
|
@ -68,6 +72,7 @@ require (
|
||||||
github.com/vektah/gqlparser/v2 v2.5.27 // indirect
|
github.com/vektah/gqlparser/v2 v2.5.27 // indirect
|
||||||
go.mau.fi/libsignal v0.2.1 // indirect
|
go.mau.fi/libsignal v0.2.1 // indirect
|
||||||
go.mau.fi/util v0.9.6 // indirect
|
go.mau.fi/util v0.9.6 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.29.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect
|
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect
|
||||||
golang.org/x/term v0.40.0 // indirect
|
golang.org/x/term v0.40.0 // indirect
|
||||||
golang.org/x/text v0.34.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/metric"
|
||||||
"jane/pkg/logger"
|
"jane/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -16,6 +18,12 @@ type ResourceTracker struct {
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
stopCh chan struct{}
|
stopCh chan struct{}
|
||||||
stopOnce sync.Once
|
stopOnce sync.Once
|
||||||
|
|
||||||
|
// OpenTelemetry gauges
|
||||||
|
goroutinesGauge metric.Int64ObservableGauge
|
||||||
|
allocMBGauge metric.Float64ObservableGauge
|
||||||
|
totalMBGauge metric.Float64ObservableGauge
|
||||||
|
sysMBGauge metric.Float64ObservableGauge
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewResourceTracker creates a new ResourceTracker that logs metrics every `interval`.
|
// NewResourceTracker creates a new ResourceTracker that logs metrics every `interval`.
|
||||||
|
|
@ -23,10 +31,77 @@ func NewResourceTracker(interval time.Duration) *ResourceTracker {
|
||||||
if interval == 0 {
|
if interval == 0 {
|
||||||
interval = 60 * time.Second // Default to 1 minute
|
interval = 60 * time.Second // Default to 1 minute
|
||||||
}
|
}
|
||||||
return &ResourceTracker{
|
|
||||||
|
rt := &ResourceTracker{
|
||||||
interval: interval,
|
interval: interval,
|
||||||
stopCh: make(chan struct{}),
|
stopCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
meter := otel.Meter("jane/pkg/health")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
rt.goroutinesGauge, err = meter.Int64ObservableGauge(
|
||||||
|
"sys.goroutines",
|
||||||
|
metric.WithDescription("Number of active goroutines"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.ErrorCF("SystemHealth", "Failed to create goroutines gauge", map[string]any{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
rt.allocMBGauge, err = meter.Float64ObservableGauge(
|
||||||
|
"sys.memory.alloc_mb",
|
||||||
|
metric.WithDescription("Memory allocated and still in use, in MB"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.ErrorCF("SystemHealth", "Failed to create memory alloc gauge", map[string]any{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
rt.totalMBGauge, err = meter.Float64ObservableGauge(
|
||||||
|
"sys.memory.total_alloc_mb",
|
||||||
|
metric.WithDescription("Total memory allocated (even if freed), in MB"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.ErrorCF("SystemHealth", "Failed to create total memory alloc gauge", map[string]any{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
rt.sysMBGauge, err = meter.Float64ObservableGauge(
|
||||||
|
"sys.memory.sys_mb",
|
||||||
|
metric.WithDescription("Total memory obtained from the OS, in MB"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.ErrorCF("SystemHealth", "Failed to create memory sys gauge", map[string]any{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := meter.RegisterCallback(rt.observeMetrics, rt.goroutinesGauge, rt.allocMBGauge, rt.totalMBGauge, rt.sysMBGauge); err != nil {
|
||||||
|
logger.ErrorCF("SystemHealth", "Failed to register metrics callback", map[string]any{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
return rt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *ResourceTracker) observeMetrics(_ context.Context, o metric.Observer) error {
|
||||||
|
var m runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&m)
|
||||||
|
|
||||||
|
goroutines := runtime.NumGoroutine()
|
||||||
|
allocMB := float64(m.Alloc) / 1024 / 1024
|
||||||
|
totalAllocMB := float64(m.TotalAlloc) / 1024 / 1024
|
||||||
|
sysMB := float64(m.Sys) / 1024 / 1024
|
||||||
|
|
||||||
|
if rt.goroutinesGauge != nil {
|
||||||
|
o.ObserveInt64(rt.goroutinesGauge, int64(goroutines))
|
||||||
|
}
|
||||||
|
if rt.allocMBGauge != nil {
|
||||||
|
o.ObserveFloat64(rt.allocMBGauge, allocMB)
|
||||||
|
}
|
||||||
|
if rt.totalMBGauge != nil {
|
||||||
|
o.ObserveFloat64(rt.totalMBGauge, totalAllocMB)
|
||||||
|
}
|
||||||
|
if rt.sysMBGauge != nil {
|
||||||
|
o.ObserveFloat64(rt.sysMBGauge, sysMB)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start begins tracking resources in a background goroutine.
|
// Start begins tracking resources in a background goroutine.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue