mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-28 18:42:25 -05:00
- 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>
200 lines
6 KiB
Go
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
|
|
}
|