diff options
Diffstat (limited to 'vendor/go.opentelemetry.io/contrib/instrumentation/runtime/runtime.go')
| -rw-r--r-- | vendor/go.opentelemetry.io/contrib/instrumentation/runtime/runtime.go | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/runtime.go b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/runtime.go new file mode 100644 index 000000000..d0ffe2764 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/runtime.go @@ -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 +} |
