gotosocial/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/runtime.go
kim 78defcd916 [chore] bump dependencies (#4406)
- codeberg.org/gruf/go-ffmpreg: v0.6.9 -> v0.6.10
- github.com/ncruces/go-sqlite3: v0.27.1 -> v0.28.0
- github.com/stretchr/testify: v1.10.0 -> v1.11.1
- github.com/tdewolff/minify/v2 v2.23.11 -> v2.24.2
- go.opentelemetry.io/otel{,/*}: v1.37.0 -> v1.38.0
- go.opentelemetry.io/contrib/*: v0.62.0 -> v0.63.0

Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4406
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
2025-09-04 15:29:27 +02:00

200 lines
6 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/otel/semconv/v1.37.0/goconv"
"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() {
if err := deprecatedruntime.Start(meter, c.MinimumReadMemStatsInterval); err != nil {
return err
}
}
memoryUsed, err := goconv.NewMemoryUsed(meter)
if err != nil {
return err
}
memoryLimit, err := goconv.NewMemoryLimit(meter)
if err != nil {
return err
}
memoryAllocated, err := goconv.NewMemoryAllocated(meter)
if err != nil {
return err
}
memoryAllocations, err := goconv.NewMemoryAllocations(meter)
if err != nil {
return err
}
memoryGCGoal, err := goconv.NewMemoryGCGoal(meter)
if err != nil {
return err
}
goroutineCount, err := goconv.NewGoroutineCount(meter)
if err != nil {
return err
}
processorLimit, err := goconv.NewProcessorLimit(meter)
if err != nil {
return err
}
configGogc, err := goconv.NewConfigGogc(meter)
if err != nil {
return err
}
otherMemoryOpt := metric.WithAttributeSet(
attribute.NewSet(memoryUsed.AttrMemoryType(goconv.MemoryTypeOther)),
)
stackMemoryOpt := metric.WithAttributeSet(
attribute.NewSet(memoryUsed.AttrMemoryType(goconv.MemoryTypeStack)),
)
collector := newCollector(c.MinimumReadMemStatsInterval, runtimeMetrics)
var lock sync.Mutex
_, err = meter.RegisterCallback(
func(_ context.Context, o metric.Observer) error {
lock.Lock()
defer lock.Unlock()
collector.refresh()
stackMemory := collector.getInt(goHeapMemory)
o.ObserveInt64(memoryUsed.Inst(), stackMemory, stackMemoryOpt)
totalMemory := collector.getInt(goTotalMemory) - collector.getInt(goMemoryReleased)
otherMemory := totalMemory - stackMemory
o.ObserveInt64(memoryUsed.Inst(), otherMemory, otherMemoryOpt)
// Only observe the limit metric if a limit exists
if limit := collector.getInt(goMemoryLimit); limit != math.MaxInt64 {
o.ObserveInt64(memoryLimit.Inst(), limit)
}
o.ObserveInt64(memoryAllocated.Inst(), collector.getInt(goMemoryAllocated))
o.ObserveInt64(memoryAllocations.Inst(), collector.getInt(goMemoryAllocations))
o.ObserveInt64(memoryGCGoal.Inst(), collector.getInt(goMemoryGoal))
o.ObserveInt64(goroutineCount.Inst(), collector.getInt(goGoroutines))
o.ObserveInt64(processorLimit.Inst(), collector.getInt(goMaxProcs))
o.ObserveInt64(configGogc.Inst(), collector.getInt(goConfigGC))
return nil
},
memoryUsed.Inst(),
memoryLimit.Inst(),
memoryAllocated.Inst(),
memoryAllocations.Inst(),
memoryGCGoal.Inst(),
goroutineCount.Inst(),
processorLimit.Inst(),
configGogc.Inst(),
)
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)
}
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
}