mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-30 00:02:24 -05:00
230 lines
7.2 KiB
Go
230 lines
7.2 KiB
Go
|
|
// 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
|
||
|
|
}
|