From ae2a4f0031e4a20e7fc1f23f04a19285a2ba5ebe Mon Sep 17 00:00:00 2001 From: David Ashpole Date: Thu, 13 Jun 2024 15:46:41 -0400 Subject: [PATCH] Prepare for migration to new runtime metrics (#5747) Part of #5655 This is a refactoring to prepare for the implementation of the new runtime metrics. It: * Adds support for `OTEL_GO_X_DEPRECATED_RUNTIME_METRICS`, which can be set to `true` or `false` to enable/disable the existing runtime metrics. It initially defaults to `true` while the new metrics are under development. * Moves the existing runtime metrics implementation to its own internal package, deprecatedruntime, to clearly separate it from the new metrics being added, and to make the eventual removal easier. This does not change any of the metrics generated, or the public API surface of the runtime metrics package. --- CHANGELOG.md | 1 + instrumentation/runtime/go.mod | 4 + instrumentation/runtime/go.sum | 2 + .../runtime/internal/deprecatedruntime/doc.go | 22 ++ .../internal/deprecatedruntime/runtime.go | 282 ++++++++++++++++++ instrumentation/runtime/internal/x/README.md | 38 +++ instrumentation/runtime/internal/x/x.go | 53 ++++ instrumentation/runtime/internal/x/x_test.go | 45 +++ instrumentation/runtime/runtime.go | 277 +---------------- 9 files changed, 456 insertions(+), 268 deletions(-) create mode 100644 instrumentation/runtime/internal/deprecatedruntime/doc.go create mode 100644 instrumentation/runtime/internal/deprecatedruntime/runtime.go create mode 100644 instrumentation/runtime/internal/x/README.md create mode 100644 instrumentation/runtime/internal/x/x.go create mode 100644 instrumentation/runtime/internal/x/x_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index b53abcd0939..9d1a5eb2b07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - The `go.opentelemetry.io/contrib/config` add support to configure periodic reader interval and timeout. (#5661) - Add support to configure views when creating MeterProvider using the config package. (#5654) - Add log support for the autoexport package. (#5733) +- Add support for disabling the old runtime metrics using the `OTEL_GO_X_DEPRECATED_RUNTIME_METRICS=false` environment variable. (#5747) ### Fixed diff --git a/instrumentation/runtime/go.mod b/instrumentation/runtime/go.mod index bf1f0dda058..9e0541ddd08 100644 --- a/instrumentation/runtime/go.mod +++ b/instrumentation/runtime/go.mod @@ -3,12 +3,16 @@ module go.opentelemetry.io/contrib/instrumentation/runtime go 1.21 require ( + github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.27.0 go.opentelemetry.io/otel/metric v1.27.0 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel/trace v1.27.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/instrumentation/runtime/go.sum b/instrumentation/runtime/go.sum index 7a15ad5fe25..a660d3bd8e0 100644 --- a/instrumentation/runtime/go.sum +++ b/instrumentation/runtime/go.sum @@ -17,5 +17,7 @@ go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0 go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/instrumentation/runtime/internal/deprecatedruntime/doc.go b/instrumentation/runtime/internal/deprecatedruntime/doc.go new file mode 100644 index 00000000000..9fb44efa8d0 --- /dev/null +++ b/instrumentation/runtime/internal/deprecatedruntime/doc.go @@ -0,0 +1,22 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package deprecatedruntime implements the deprecated runtime metrics for OpenTelemetry. +// +// The metric events produced are: +// +// runtime.go.cgo.calls - Number of cgo calls made by the current process +// runtime.go.gc.count - Number of completed garbage collection cycles +// runtime.go.gc.pause_ns (ns) Amount of nanoseconds in GC stop-the-world pauses +// runtime.go.gc.pause_total_ns (ns) Cumulative nanoseconds in GC stop-the-world pauses since the program started +// runtime.go.goroutines - Number of goroutines that currently exist +// runtime.go.lookups - Number of pointer lookups performed by the runtime +// runtime.go.mem.heap_alloc (bytes) Bytes of allocated heap objects +// runtime.go.mem.heap_idle (bytes) Bytes in idle (unused) spans +// runtime.go.mem.heap_inuse (bytes) Bytes in in-use spans +// runtime.go.mem.heap_objects - Number of allocated heap objects +// runtime.go.mem.heap_released (bytes) Bytes of idle spans whose physical memory has been returned to the OS +// runtime.go.mem.heap_sys (bytes) Bytes of heap memory obtained from the OS +// runtime.go.mem.live_objects - Number of live objects is the number of cumulative Mallocs - Frees +// runtime.uptime (ms) Milliseconds since application was initialized +package deprecatedruntime // import "go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime" diff --git a/instrumentation/runtime/internal/deprecatedruntime/runtime.go b/instrumentation/runtime/internal/deprecatedruntime/runtime.go new file mode 100644 index 00000000000..487072e3bdc --- /dev/null +++ b/instrumentation/runtime/internal/deprecatedruntime/runtime.go @@ -0,0 +1,282 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package deprecatedruntime // import "go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime" + +import ( + "context" + goruntime "runtime" + "sync" + "time" + + "go.opentelemetry.io/otel/metric" +) + +// Runtime reports the work-in-progress conventional runtime metrics specified by OpenTelemetry. +type runtime struct { + minimumReadMemStatsInterval time.Duration + meter metric.Meter +} + +// Start initializes reporting of runtime metrics using the supplied config. +func Start(meter metric.Meter, minimumReadMemStatsInterval time.Duration) error { + r := &runtime{ + meter: meter, + minimumReadMemStatsInterval: minimumReadMemStatsInterval, + } + return r.register() +} + +func (r *runtime) register() error { + startTime := time.Now() + uptime, err := r.meter.Int64ObservableCounter( + "runtime.uptime", + metric.WithUnit("ms"), + metric.WithDescription("Milliseconds since application was initialized"), + ) + if err != nil { + return err + } + + goroutines, err := r.meter.Int64ObservableUpDownCounter( + "process.runtime.go.goroutines", + metric.WithDescription("Number of goroutines that currently exist"), + ) + if err != nil { + return err + } + + cgoCalls, err := r.meter.Int64ObservableUpDownCounter( + "process.runtime.go.cgo.calls", + metric.WithDescription("Number of cgo calls made by the current process"), + ) + if err != nil { + return err + } + + _, err = r.meter.RegisterCallback( + func(ctx context.Context, o metric.Observer) error { + o.ObserveInt64(uptime, time.Since(startTime).Milliseconds()) + o.ObserveInt64(goroutines, int64(goruntime.NumGoroutine())) + o.ObserveInt64(cgoCalls, goruntime.NumCgoCall()) + return nil + }, + uptime, + goroutines, + cgoCalls, + ) + if err != nil { + return err + } + + return r.registerMemStats() +} + +func (r *runtime) registerMemStats() error { + var ( + err error + + heapAlloc metric.Int64ObservableUpDownCounter + heapIdle metric.Int64ObservableUpDownCounter + heapInuse metric.Int64ObservableUpDownCounter + heapObjects metric.Int64ObservableUpDownCounter + heapReleased metric.Int64ObservableUpDownCounter + heapSys metric.Int64ObservableUpDownCounter + liveObjects metric.Int64ObservableUpDownCounter + + // TODO: is ptrLookups useful? I've not seen a value + // other than zero. + ptrLookups metric.Int64ObservableCounter + + gcCount metric.Int64ObservableCounter + pauseTotalNs metric.Int64ObservableCounter + gcPauseNs metric.Int64Histogram + + lastNumGC uint32 + lastMemStats time.Time + memStats goruntime.MemStats + + // lock prevents a race between batch observer and instrument registration. + lock sync.Mutex + ) + + lock.Lock() + defer lock.Unlock() + + if heapAlloc, err = r.meter.Int64ObservableUpDownCounter( + "process.runtime.go.mem.heap_alloc", + metric.WithUnit("By"), + metric.WithDescription("Bytes of allocated heap objects"), + ); err != nil { + return err + } + + if heapIdle, err = r.meter.Int64ObservableUpDownCounter( + "process.runtime.go.mem.heap_idle", + metric.WithUnit("By"), + metric.WithDescription("Bytes in idle (unused) spans"), + ); err != nil { + return err + } + + if heapInuse, err = r.meter.Int64ObservableUpDownCounter( + "process.runtime.go.mem.heap_inuse", + metric.WithUnit("By"), + metric.WithDescription("Bytes in in-use spans"), + ); err != nil { + return err + } + + if heapObjects, err = r.meter.Int64ObservableUpDownCounter( + "process.runtime.go.mem.heap_objects", + metric.WithDescription("Number of allocated heap objects"), + ); err != nil { + return err + } + + // FYI see https://github.com/golang/go/issues/32284 to help + // understand the meaning of this value. + if heapReleased, err = r.meter.Int64ObservableUpDownCounter( + "process.runtime.go.mem.heap_released", + metric.WithUnit("By"), + metric.WithDescription("Bytes of idle spans whose physical memory has been returned to the OS"), + ); err != nil { + return err + } + + if heapSys, err = r.meter.Int64ObservableUpDownCounter( + "process.runtime.go.mem.heap_sys", + metric.WithUnit("By"), + metric.WithDescription("Bytes of heap memory obtained from the OS"), + ); err != nil { + return err + } + + if ptrLookups, err = r.meter.Int64ObservableCounter( + "process.runtime.go.mem.lookups", + metric.WithDescription("Number of pointer lookups performed by the runtime"), + ); err != nil { + return err + } + + if liveObjects, err = r.meter.Int64ObservableUpDownCounter( + "process.runtime.go.mem.live_objects", + metric.WithDescription("Number of live objects is the number of cumulative Mallocs - Frees"), + ); err != nil { + return err + } + + if gcCount, err = r.meter.Int64ObservableCounter( + "process.runtime.go.gc.count", + metric.WithDescription("Number of completed garbage collection cycles"), + ); err != nil { + return err + } + + // Note that the following could be derived as a sum of + // individual pauses, but we may lose individual pauses if the + // observation interval is too slow. + if pauseTotalNs, err = r.meter.Int64ObservableCounter( + "process.runtime.go.gc.pause_total_ns", + // TODO: nanoseconds units + metric.WithDescription("Cumulative nanoseconds in GC stop-the-world pauses since the program started"), + ); err != nil { + return err + } + + if gcPauseNs, err = r.meter.Int64Histogram( + "process.runtime.go.gc.pause_ns", + // TODO: nanoseconds units + metric.WithDescription("Amount of nanoseconds in GC stop-the-world pauses"), + ); err != nil { + return err + } + + _, err = r.meter.RegisterCallback( + func(ctx context.Context, o metric.Observer) error { + lock.Lock() + defer lock.Unlock() + + now := time.Now() + if now.Sub(lastMemStats) >= r.minimumReadMemStatsInterval { + goruntime.ReadMemStats(&memStats) + lastMemStats = now + } + + o.ObserveInt64(heapAlloc, int64(memStats.HeapAlloc)) + o.ObserveInt64(heapIdle, int64(memStats.HeapIdle)) + o.ObserveInt64(heapInuse, int64(memStats.HeapInuse)) + o.ObserveInt64(heapObjects, int64(memStats.HeapObjects)) + o.ObserveInt64(heapReleased, int64(memStats.HeapReleased)) + o.ObserveInt64(heapSys, int64(memStats.HeapSys)) + o.ObserveInt64(liveObjects, int64(memStats.Mallocs-memStats.Frees)) + o.ObserveInt64(ptrLookups, int64(memStats.Lookups)) + o.ObserveInt64(gcCount, int64(memStats.NumGC)) + o.ObserveInt64(pauseTotalNs, int64(memStats.PauseTotalNs)) + + computeGCPauses(ctx, gcPauseNs, memStats.PauseNs[:], lastNumGC, memStats.NumGC) + + lastNumGC = memStats.NumGC + + return nil + }, + heapAlloc, + heapIdle, + heapInuse, + heapObjects, + heapReleased, + heapSys, + liveObjects, + + ptrLookups, + + gcCount, + pauseTotalNs, + ) + if err != nil { + return err + } + return nil +} + +func computeGCPauses( + ctx context.Context, + recorder metric.Int64Histogram, + circular []uint64, + lastNumGC, currentNumGC uint32, +) { + delta := int(int64(currentNumGC) - int64(lastNumGC)) + + if delta == 0 { + return + } + + if delta >= len(circular) { + // There were > 256 collections, some may have been lost. + recordGCPauses(ctx, recorder, circular) + return + } + + length := uint32(len(circular)) + + i := lastNumGC % length + j := currentNumGC % length + + if j < i { // wrap around the circular buffer + recordGCPauses(ctx, recorder, circular[i:]) + recordGCPauses(ctx, recorder, circular[:j]) + return + } + + recordGCPauses(ctx, recorder, circular[i:j]) +} + +func recordGCPauses( + ctx context.Context, + recorder metric.Int64Histogram, + pauses []uint64, +) { + for _, pause := range pauses { + recorder.Record(ctx, int64(pause)) + } +} diff --git a/instrumentation/runtime/internal/x/README.md b/instrumentation/runtime/internal/x/README.md new file mode 100644 index 00000000000..241297dc9e4 --- /dev/null +++ b/instrumentation/runtime/internal/x/README.md @@ -0,0 +1,38 @@ +# Feature Gates + +The runtime package contains a feature gate used to ease the migration +from the [previous runtime metrics conventions] to the new [OpenTelemetry Go +Runtime conventions]. + +Note that the new runtime metrics conventions are still experimental, and may +change in backwards incompatible ways as feedback is applied. + +## Features + +- [Include Deprecated Metrics](#include-deprecated-metrics) + +### Include Deprecated Metrics + +Once new experimental runtime metrics are added, they will be produced +**in addition to** the existing runtime metrics. Users that migrate right away +can disable the old runtime metrics: + +```console +export OTEL_GO_X_DEPRECATED_RUNTIME_METRICS=false +``` + +In a later release, the deprecated runtime metrics will stop being produced by +default. To temporarily re-enable the deprecated metrics: + +```console +export OTEL_GO_X_DEPRECATED_RUNTIME_METRICS=true +``` + +After two additional releases, the deprecated runtime metrics will be removed, +and setting the environment variable will no longer have any effect. + +The value set must be the case-insensitive string of `"true"` to enable the +feature, and `"false"` to disable the feature. All other values are ignored. + +[previous runtime metrics conventions]: go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime +[OpenTelemetry Go Runtime conventions]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/runtime/go-metrics.md diff --git a/instrumentation/runtime/internal/x/x.go b/instrumentation/runtime/internal/x/x.go new file mode 100644 index 00000000000..2ecb52c0d89 --- /dev/null +++ b/instrumentation/runtime/internal/x/x.go @@ -0,0 +1,53 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package x contains support for OTel runtime instrumentaiton experimental features. +// +// This package should only be used for features defined in the specification. +// It should not be used for experiments or new project ideas. +package x // import "go.opentelemetry.io/contrib/instrumentation/runtime/internal/x" + +import ( + "os" + "strings" +) + +// DeprecatedRuntimeMetrics is an experimental feature flag that defines if the deprecated +// runtime metrics should be produced. During development of the new +// conventions, it is enabled by default. +// +// To disable this feature set the OTEL_GO_X_DEPRECATED_RUNTIME_METRICS environment variable +// to the case-insensitive string value of "false" (i.e. "False" and "FALSE" +// will also enable this). +var DeprecatedRuntimeMetrics = newFeature("DEPRECATED_RUNTIME_METRICS", true) + +// BoolFeature is an experimental feature control flag. It provides a uniform way +// to interact with these feature flags and parse their values. +type BoolFeature struct { + key string + defaultVal bool +} + +func newFeature(suffix string, defaultVal bool) BoolFeature { + const envKeyRoot = "OTEL_GO_X_" + return BoolFeature{ + key: envKeyRoot + suffix, + defaultVal: defaultVal, + } +} + +// Key returns the environment variable key that needs to be set to enable the +// feature. +func (f BoolFeature) Key() string { return f.key } + +// Enabled returns if the feature is enabled. +func (f BoolFeature) Enabled() bool { + v := os.Getenv(f.key) + if strings.ToLower(v) == "false" { + return false + } + if strings.ToLower(v) == "true" { + return true + } + return f.defaultVal +} diff --git a/instrumentation/runtime/internal/x/x_test.go b/instrumentation/runtime/internal/x/x_test.go new file mode 100644 index 00000000000..86db0d845f0 --- /dev/null +++ b/instrumentation/runtime/internal/x/x_test.go @@ -0,0 +1,45 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package x + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDeprecatedRuntimeMetrics(t *testing.T) { + const key = "OTEL_GO_X_DEPRECATED_RUNTIME_METRICS" + require.Equal(t, key, DeprecatedRuntimeMetrics.Key()) + + t.Run("true", run(setenv(key, "true"), assertEnabled(DeprecatedRuntimeMetrics, true))) + t.Run("True", run(setenv(key, "True"), assertEnabled(DeprecatedRuntimeMetrics, true))) + t.Run("TRUE", run(setenv(key, "TRUE"), assertEnabled(DeprecatedRuntimeMetrics, true))) + t.Run("false", run(setenv(key, "false"), assertEnabled(DeprecatedRuntimeMetrics, false))) + t.Run("False", run(setenv(key, "False"), assertEnabled(DeprecatedRuntimeMetrics, false))) + t.Run("FALSE", run(setenv(key, "FALSE"), assertEnabled(DeprecatedRuntimeMetrics, false))) + t.Run("1", run(setenv(key, "1"), assertEnabled(DeprecatedRuntimeMetrics, true))) + t.Run("empty", run(assertEnabled(DeprecatedRuntimeMetrics, true))) +} + +func run(steps ...func(*testing.T)) func(*testing.T) { + return func(t *testing.T) { + t.Helper() + for _, step := range steps { + step(t) + } + } +} + +func setenv(k, v string) func(t *testing.T) { + return func(t *testing.T) { t.Setenv(k, v) } +} + +func assertEnabled(f BoolFeature, enabled bool) func(*testing.T) { + return func(t *testing.T) { + t.Helper() + assert.Equal(t, enabled, f.Enabled(), "not enabled") + } +} diff --git a/instrumentation/runtime/runtime.go b/instrumentation/runtime/runtime.go index 45d387dbb50..3c520a49933 100644 --- a/instrumentation/runtime/runtime.go +++ b/instrumentation/runtime/runtime.go @@ -4,24 +4,18 @@ package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" import ( - "context" - goruntime "runtime" - "sync" "time" "go.opentelemetry.io/otel" "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" -// Runtime reports the work-in-progress conventional runtime metrics specified by OpenTelemetry. -type runtime struct { - config config - meter metric.Meter -} - // config contains optional settings for reporting runtime metrics. type config struct { // MinimumReadMemStatsInterval sets the minimum interval @@ -96,266 +90,13 @@ func Start(opts ...Option) error { if c.MeterProvider == nil { c.MeterProvider = otel.GetMeterProvider() } - r := &runtime{ - meter: c.MeterProvider.Meter( - ScopeName, - metric.WithInstrumentationVersion(Version()), - ), - config: c, - } - return r.register() -} - -func (r *runtime) register() error { - startTime := time.Now() - uptime, err := r.meter.Int64ObservableCounter( - "runtime.uptime", - metric.WithUnit("ms"), - metric.WithDescription("Milliseconds since application was initialized"), - ) - if err != nil { - return err - } - - goroutines, err := r.meter.Int64ObservableUpDownCounter( - "process.runtime.go.goroutines", - metric.WithDescription("Number of goroutines that currently exist"), - ) - if err != nil { - return err - } - - cgoCalls, err := r.meter.Int64ObservableUpDownCounter( - "process.runtime.go.cgo.calls", - metric.WithDescription("Number of cgo calls made by the current process"), - ) - if err != nil { - return err - } - - _, err = r.meter.RegisterCallback( - func(ctx context.Context, o metric.Observer) error { - o.ObserveInt64(uptime, time.Since(startTime).Milliseconds()) - o.ObserveInt64(goroutines, int64(goruntime.NumGoroutine())) - o.ObserveInt64(cgoCalls, goruntime.NumCgoCall()) - return nil - }, - uptime, - goroutines, - cgoCalls, - ) - if err != nil { - return err - } - - return r.registerMemStats() -} - -func (r *runtime) registerMemStats() error { - var ( - err error - - heapAlloc metric.Int64ObservableUpDownCounter - heapIdle metric.Int64ObservableUpDownCounter - heapInuse metric.Int64ObservableUpDownCounter - heapObjects metric.Int64ObservableUpDownCounter - heapReleased metric.Int64ObservableUpDownCounter - heapSys metric.Int64ObservableUpDownCounter - liveObjects metric.Int64ObservableUpDownCounter - - // TODO: is ptrLookups useful? I've not seen a value - // other than zero. - ptrLookups metric.Int64ObservableCounter - - gcCount metric.Int64ObservableCounter - pauseTotalNs metric.Int64ObservableCounter - gcPauseNs metric.Int64Histogram - - lastNumGC uint32 - lastMemStats time.Time - memStats goruntime.MemStats - - // lock prevents a race between batch observer and instrument registration. - lock sync.Mutex + meter := c.MeterProvider.Meter( + ScopeName, + metric.WithInstrumentationVersion(Version()), ) - - lock.Lock() - defer lock.Unlock() - - if heapAlloc, err = r.meter.Int64ObservableUpDownCounter( - "process.runtime.go.mem.heap_alloc", - metric.WithUnit("By"), - metric.WithDescription("Bytes of allocated heap objects"), - ); err != nil { - return err - } - - if heapIdle, err = r.meter.Int64ObservableUpDownCounter( - "process.runtime.go.mem.heap_idle", - metric.WithUnit("By"), - metric.WithDescription("Bytes in idle (unused) spans"), - ); err != nil { - return err - } - - if heapInuse, err = r.meter.Int64ObservableUpDownCounter( - "process.runtime.go.mem.heap_inuse", - metric.WithUnit("By"), - metric.WithDescription("Bytes in in-use spans"), - ); err != nil { - return err - } - - if heapObjects, err = r.meter.Int64ObservableUpDownCounter( - "process.runtime.go.mem.heap_objects", - metric.WithDescription("Number of allocated heap objects"), - ); err != nil { - return err - } - - // FYI see https://github.com/golang/go/issues/32284 to help - // understand the meaning of this value. - if heapReleased, err = r.meter.Int64ObservableUpDownCounter( - "process.runtime.go.mem.heap_released", - metric.WithUnit("By"), - metric.WithDescription("Bytes of idle spans whose physical memory has been returned to the OS"), - ); err != nil { - return err - } - - if heapSys, err = r.meter.Int64ObservableUpDownCounter( - "process.runtime.go.mem.heap_sys", - metric.WithUnit("By"), - metric.WithDescription("Bytes of heap memory obtained from the OS"), - ); err != nil { - return err - } - - if ptrLookups, err = r.meter.Int64ObservableCounter( - "process.runtime.go.mem.lookups", - metric.WithDescription("Number of pointer lookups performed by the runtime"), - ); err != nil { - return err - } - - if liveObjects, err = r.meter.Int64ObservableUpDownCounter( - "process.runtime.go.mem.live_objects", - metric.WithDescription("Number of live objects is the number of cumulative Mallocs - Frees"), - ); err != nil { - return err - } - - if gcCount, err = r.meter.Int64ObservableCounter( - "process.runtime.go.gc.count", - metric.WithDescription("Number of completed garbage collection cycles"), - ); err != nil { - return err - } - - // Note that the following could be derived as a sum of - // individual pauses, but we may lose individual pauses if the - // observation interval is too slow. - if pauseTotalNs, err = r.meter.Int64ObservableCounter( - "process.runtime.go.gc.pause_total_ns", - // TODO: nanoseconds units - metric.WithDescription("Cumulative nanoseconds in GC stop-the-world pauses since the program started"), - ); err != nil { - return err - } - - if gcPauseNs, err = r.meter.Int64Histogram( - "process.runtime.go.gc.pause_ns", - // TODO: nanoseconds units - metric.WithDescription("Amount of nanoseconds in GC stop-the-world pauses"), - ); err != nil { - return err - } - - _, err = r.meter.RegisterCallback( - func(ctx context.Context, o metric.Observer) error { - lock.Lock() - defer lock.Unlock() - - now := time.Now() - if now.Sub(lastMemStats) >= r.config.MinimumReadMemStatsInterval { - goruntime.ReadMemStats(&memStats) - lastMemStats = now - } - - o.ObserveInt64(heapAlloc, int64(memStats.HeapAlloc)) - o.ObserveInt64(heapIdle, int64(memStats.HeapIdle)) - o.ObserveInt64(heapInuse, int64(memStats.HeapInuse)) - o.ObserveInt64(heapObjects, int64(memStats.HeapObjects)) - o.ObserveInt64(heapReleased, int64(memStats.HeapReleased)) - o.ObserveInt64(heapSys, int64(memStats.HeapSys)) - o.ObserveInt64(liveObjects, int64(memStats.Mallocs-memStats.Frees)) - o.ObserveInt64(ptrLookups, int64(memStats.Lookups)) - o.ObserveInt64(gcCount, int64(memStats.NumGC)) - o.ObserveInt64(pauseTotalNs, int64(memStats.PauseTotalNs)) - - computeGCPauses(ctx, gcPauseNs, memStats.PauseNs[:], lastNumGC, memStats.NumGC) - - lastNumGC = memStats.NumGC - - return nil - }, - heapAlloc, - heapIdle, - heapInuse, - heapObjects, - heapReleased, - heapSys, - liveObjects, - - ptrLookups, - - gcCount, - pauseTotalNs, - ) - if err != nil { - return err + if x.DeprecatedRuntimeMetrics.Enabled() { + return deprecatedruntime.Start(meter, c.MinimumReadMemStatsInterval) } + // TODO (#5655) Implement new runtime conventions return nil } - -func computeGCPauses( - ctx context.Context, - recorder metric.Int64Histogram, - circular []uint64, - lastNumGC, currentNumGC uint32, -) { - delta := int(int64(currentNumGC) - int64(lastNumGC)) - - if delta == 0 { - return - } - - if delta >= len(circular) { - // There were > 256 collections, some may have been lost. - recordGCPauses(ctx, recorder, circular) - return - } - - length := uint32(len(circular)) - - i := lastNumGC % length - j := currentNumGC % length - - if j < i { // wrap around the circular buffer - recordGCPauses(ctx, recorder, circular[i:]) - recordGCPauses(ctx, recorder, circular[:j]) - return - } - - recordGCPauses(ctx, recorder, circular[i:j]) -} - -func recordGCPauses( - ctx context.Context, - recorder metric.Int64Histogram, - pauses []uint64, -) { - for _, pause := range pauses { - recorder.Record(ctx, int64(pause)) - } -}