mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-30 13:52:26 -05:00
[chore] Add Go runtime and host metrics (#4137)
Daenney is a dummy and forgot to add these when he revamped the OTEL stuff. Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4137 Co-authored-by: Daenney <daenney@noreply.codeberg.org> Co-committed-by: Daenney <daenney@noreply.codeberg.org>
This commit is contained in:
parent
4a6b357501
commit
90a5425fe9
14 changed files with 1124 additions and 0 deletions
229
vendor/go.opentelemetry.io/contrib/instrumentation/runtime/runtime.go
generated
vendored
Normal file
229
vendor/go.opentelemetry.io/contrib/instrumentation/runtime/runtime.go
generated
vendored
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"runtime/metrics"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
|
||||
"go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime"
|
||||
"go.opentelemetry.io/contrib/instrumentation/runtime/internal/x"
|
||||
)
|
||||
|
||||
// ScopeName is the instrumentation scope name.
|
||||
const ScopeName = "go.opentelemetry.io/contrib/instrumentation/runtime"
|
||||
|
||||
const (
|
||||
goTotalMemory = "/memory/classes/total:bytes"
|
||||
goMemoryReleased = "/memory/classes/heap/released:bytes"
|
||||
goHeapMemory = "/memory/classes/heap/stacks:bytes"
|
||||
goMemoryLimit = "/gc/gomemlimit:bytes"
|
||||
goMemoryAllocated = "/gc/heap/allocs:bytes"
|
||||
goMemoryAllocations = "/gc/heap/allocs:objects"
|
||||
goMemoryGoal = "/gc/heap/goal:bytes"
|
||||
goGoroutines = "/sched/goroutines:goroutines"
|
||||
goMaxProcs = "/sched/gomaxprocs:threads"
|
||||
goConfigGC = "/gc/gogc:percent"
|
||||
goSchedLatencies = "/sched/latencies:seconds"
|
||||
)
|
||||
|
||||
// Start initializes reporting of runtime metrics using the supplied config.
|
||||
// For goroutine scheduling metrics, additionally see [NewProducer].
|
||||
func Start(opts ...Option) error {
|
||||
c := newConfig(opts...)
|
||||
meter := c.MeterProvider.Meter(
|
||||
ScopeName,
|
||||
metric.WithInstrumentationVersion(Version()),
|
||||
)
|
||||
if x.DeprecatedRuntimeMetrics.Enabled() {
|
||||
return deprecatedruntime.Start(meter, c.MinimumReadMemStatsInterval)
|
||||
}
|
||||
memoryUsedInstrument, err := meter.Int64ObservableUpDownCounter(
|
||||
"go.memory.used",
|
||||
metric.WithUnit("By"),
|
||||
metric.WithDescription("Memory used by the Go runtime."),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
memoryLimitInstrument, err := meter.Int64ObservableUpDownCounter(
|
||||
"go.memory.limit",
|
||||
metric.WithUnit("By"),
|
||||
metric.WithDescription("Go runtime memory limit configured by the user, if a limit exists."),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
memoryAllocatedInstrument, err := meter.Int64ObservableCounter(
|
||||
"go.memory.allocated",
|
||||
metric.WithUnit("By"),
|
||||
metric.WithDescription("Memory allocated to the heap by the application."),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
memoryAllocationsInstrument, err := meter.Int64ObservableCounter(
|
||||
"go.memory.allocations",
|
||||
metric.WithUnit("{allocation}"),
|
||||
metric.WithDescription("Count of allocations to the heap by the application."),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
memoryGCGoalInstrument, err := meter.Int64ObservableUpDownCounter(
|
||||
"go.memory.gc.goal",
|
||||
metric.WithUnit("By"),
|
||||
metric.WithDescription("Heap size target for the end of the GC cycle."),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
goroutineCountInstrument, err := meter.Int64ObservableUpDownCounter(
|
||||
"go.goroutine.count",
|
||||
metric.WithUnit("{goroutine}"),
|
||||
metric.WithDescription("Count of live goroutines."),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
processorLimitInstrument, err := meter.Int64ObservableUpDownCounter(
|
||||
"go.processor.limit",
|
||||
metric.WithUnit("{thread}"),
|
||||
metric.WithDescription("The number of OS threads that can execute user-level Go code simultaneously."),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gogcConfigInstrument, err := meter.Int64ObservableUpDownCounter(
|
||||
"go.config.gogc",
|
||||
metric.WithUnit("%"),
|
||||
metric.WithDescription("Heap size target percentage configured by the user, otherwise 100."),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
otherMemoryOpt := metric.WithAttributeSet(
|
||||
attribute.NewSet(attribute.String("go.memory.type", "other")),
|
||||
)
|
||||
stackMemoryOpt := metric.WithAttributeSet(
|
||||
attribute.NewSet(attribute.String("go.memory.type", "stack")),
|
||||
)
|
||||
collector := newCollector(c.MinimumReadMemStatsInterval, runtimeMetrics)
|
||||
var lock sync.Mutex
|
||||
_, err = meter.RegisterCallback(
|
||||
func(ctx context.Context, o metric.Observer) error {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
collector.refresh()
|
||||
stackMemory := collector.getInt(goHeapMemory)
|
||||
o.ObserveInt64(memoryUsedInstrument, stackMemory, stackMemoryOpt)
|
||||
totalMemory := collector.getInt(goTotalMemory) - collector.getInt(goMemoryReleased)
|
||||
otherMemory := totalMemory - stackMemory
|
||||
o.ObserveInt64(memoryUsedInstrument, otherMemory, otherMemoryOpt)
|
||||
// Only observe the limit metric if a limit exists
|
||||
if limit := collector.getInt(goMemoryLimit); limit != math.MaxInt64 {
|
||||
o.ObserveInt64(memoryLimitInstrument, limit)
|
||||
}
|
||||
o.ObserveInt64(memoryAllocatedInstrument, collector.getInt(goMemoryAllocated))
|
||||
o.ObserveInt64(memoryAllocationsInstrument, collector.getInt(goMemoryAllocations))
|
||||
o.ObserveInt64(memoryGCGoalInstrument, collector.getInt(goMemoryGoal))
|
||||
o.ObserveInt64(goroutineCountInstrument, collector.getInt(goGoroutines))
|
||||
o.ObserveInt64(processorLimitInstrument, collector.getInt(goMaxProcs))
|
||||
o.ObserveInt64(gogcConfigInstrument, collector.getInt(goConfigGC))
|
||||
return nil
|
||||
},
|
||||
memoryUsedInstrument,
|
||||
memoryLimitInstrument,
|
||||
memoryAllocatedInstrument,
|
||||
memoryAllocationsInstrument,
|
||||
memoryGCGoalInstrument,
|
||||
goroutineCountInstrument,
|
||||
processorLimitInstrument,
|
||||
gogcConfigInstrument,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// These are the metrics we actually fetch from the go runtime.
|
||||
var runtimeMetrics = []string{
|
||||
goTotalMemory,
|
||||
goMemoryReleased,
|
||||
goHeapMemory,
|
||||
goMemoryLimit,
|
||||
goMemoryAllocated,
|
||||
goMemoryAllocations,
|
||||
goMemoryGoal,
|
||||
goGoroutines,
|
||||
goMaxProcs,
|
||||
goConfigGC,
|
||||
}
|
||||
|
||||
type goCollector struct {
|
||||
// now is used to replace the implementation of time.Now for testing
|
||||
now func() time.Time
|
||||
// lastCollect tracks the last time metrics were refreshed
|
||||
lastCollect time.Time
|
||||
// minimumInterval is the minimum amount of time between calls to metrics.Read
|
||||
minimumInterval time.Duration
|
||||
// sampleBuffer is populated by runtime/metrics
|
||||
sampleBuffer []metrics.Sample
|
||||
// sampleMap allows us to easily get the value of a single metric
|
||||
sampleMap map[string]*metrics.Sample
|
||||
}
|
||||
|
||||
func newCollector(minimumInterval time.Duration, metricNames []string) *goCollector {
|
||||
g := &goCollector{
|
||||
sampleBuffer: make([]metrics.Sample, 0, len(metricNames)),
|
||||
sampleMap: make(map[string]*metrics.Sample, len(metricNames)),
|
||||
minimumInterval: minimumInterval,
|
||||
now: time.Now,
|
||||
}
|
||||
for _, metricName := range metricNames {
|
||||
g.sampleBuffer = append(g.sampleBuffer, metrics.Sample{Name: metricName})
|
||||
// sampleMap references a position in the sampleBuffer slice. If an
|
||||
// element is appended to sampleBuffer, it must be added to sampleMap
|
||||
// for the sample to be accessible in sampleMap.
|
||||
g.sampleMap[metricName] = &g.sampleBuffer[len(g.sampleBuffer)-1]
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *goCollector) refresh() {
|
||||
now := g.now()
|
||||
if now.Sub(g.lastCollect) < g.minimumInterval {
|
||||
// refresh was invoked more frequently than allowed by the minimum
|
||||
// interval. Do nothing.
|
||||
return
|
||||
}
|
||||
metrics.Read(g.sampleBuffer)
|
||||
g.lastCollect = now
|
||||
}
|
||||
|
||||
func (g *goCollector) getInt(name string) int64 {
|
||||
if s, ok := g.sampleMap[name]; ok && s.Value.Kind() == metrics.KindUint64 {
|
||||
v := s.Value.Uint64()
|
||||
if v > math.MaxInt64 {
|
||||
return math.MaxInt64
|
||||
}
|
||||
return int64(v) // nolint: gosec // Overflow checked above.
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (g *goCollector) getHistogram(name string) *metrics.Float64Histogram {
|
||||
if s, ok := g.sampleMap[name]; ok && s.Value.Kind() == metrics.KindFloat64Histogram {
|
||||
return s.Value.Float64Histogram()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue