Skip to content

Commit

Permalink
Implement new runtime metrics (#5780)
Browse files Browse the repository at this point in the history
Part of
#5655

Changes:

* Move the configuration options to `options.go` without modification.
* Implements the metrics defined here:
https://github.com/open-telemetry/semantic-conventions/blob/main/docs/runtime/go-metrics.md.
These are still disabled by default while it is under development.
* Add unit tests

Notes:

It doesn't implement `go.schedule.duration`, as the histogram will need
some additional work to figure out.

Based on
prometheus/client_golang#955 (comment),
using go's runtime metrics should is more efficient than reading
memstats.

---------

Co-authored-by: Sam Xie <sam@samxie.me>
  • Loading branch information
dashpole and XSAM authored Jul 15, 2024
1 parent 879727b commit 7b8afe3
Show file tree
Hide file tree
Showing 9 changed files with 580 additions and 74 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- The `go.opentelemetry.io/contrib/bridges/otelzap` module.
This module provides an OpenTelemetry logging bridge for `go.uber.org/zap`. (#5191)
- The `go.opentelemetry.io/contrib/config` package supports configuring `with_resource_constant_labels` for the prometheus exporter. (#5890)
- Add new runtime metrics to `go.opentelemetry.io/contrib/instrumentation/runtime`, which are still disabled by default. (#5870)

### Removed

Expand Down
12 changes: 12 additions & 0 deletions instrumentation/runtime/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,16 @@
// 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
//
// When the OTEL_GO_X_DEPRECATED_RUNTIME_METRICS environment variable is set to
// false, the metrics produced are:
//
// go.memory.used By Memory used by the Go runtime.
// go.memory.limit By Go runtime memory limit configured by the user, if a limit exists.
// go.memory.allocated By Memory allocated to the heap by the application.
// go.memory.allocations {allocation} Count of allocations to the heap by the application.
// go.memory.gc.goal By Heap size target for the end of the GC cycle.
// go.goroutine.count {goroutine} Count of live goroutines.
// go.processor.limit {thread} The number of OS threads that can execute user-level Go code simultaneously.
// go.config.gogc % Heap size target percentage configured by the user, otherwise 100.
package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime"
76 changes: 76 additions & 0 deletions instrumentation/runtime/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime"

import (
"time"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric"
)

// config contains optional settings for reporting runtime metrics.
type config struct {
// MinimumReadMemStatsInterval sets the minimum interval
// between calls to runtime.ReadMemStats(). Negative values
// are ignored.
MinimumReadMemStatsInterval time.Duration

// MeterProvider sets the metric.MeterProvider. If nil, the global
// Provider will be used.
MeterProvider metric.MeterProvider
}

// Option supports configuring optional settings for runtime metrics.
type Option interface {
apply(*config)
}

// DefaultMinimumReadMemStatsInterval is the default minimum interval
// between calls to runtime.ReadMemStats(). Use the
// WithMinimumReadMemStatsInterval() option to modify this setting in
// Start().
const DefaultMinimumReadMemStatsInterval time.Duration = 15 * time.Second

// WithMinimumReadMemStatsInterval sets a minimum interval between calls to
// runtime.ReadMemStats(), which is a relatively expensive call to make
// frequently. This setting is ignored when `d` is negative.
func WithMinimumReadMemStatsInterval(d time.Duration) Option {
return minimumReadMemStatsIntervalOption(d)
}

type minimumReadMemStatsIntervalOption time.Duration

func (o minimumReadMemStatsIntervalOption) apply(c *config) {
if o >= 0 {
c.MinimumReadMemStatsInterval = time.Duration(o)
}
}

// WithMeterProvider sets the Metric implementation to use for
// reporting. If this option is not used, the global metric.MeterProvider
// will be used. `provider` must be non-nil.
func WithMeterProvider(provider metric.MeterProvider) Option {
return metricProviderOption{provider}
}

type metricProviderOption struct{ metric.MeterProvider }

func (o metricProviderOption) apply(c *config) {
if o.MeterProvider != nil {
c.MeterProvider = o.MeterProvider
}
}

// newConfig computes a config from the supplied Options.
func newConfig(opts ...Option) config {
c := config{
MeterProvider: otel.GetMeterProvider(),
MinimumReadMemStatsInterval: DefaultMinimumReadMemStatsInterval,
}
for _, opt := range opts {
opt.apply(&c)
}
return c
}
44 changes: 44 additions & 0 deletions instrumentation/runtime/options_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime"

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestNewConfig(t *testing.T) {
for _, tt := range []struct {
name string
opts []Option
expect config
}{
{
name: "default",
expect: config{MinimumReadMemStatsInterval: 15 * time.Second},
},
{
name: "negative MinimumReadMemStatsInterval ignored",
opts: []Option{WithMinimumReadMemStatsInterval(-1 * time.Second)},
expect: config{MinimumReadMemStatsInterval: 15 * time.Second},
},
{
name: "set MinimumReadMemStatsInterval",
opts: []Option{WithMinimumReadMemStatsInterval(10 * time.Second)},
expect: config{MinimumReadMemStatsInterval: 10 * time.Second},
},
} {
t.Run(tt.name, func(t *testing.T) {
got := newConfig(tt.opts...)
assert.True(t, configEqual(got, tt.expect))
})
}
}

func configEqual(a, b config) bool {
// ignore MeterProvider
return a.MinimumReadMemStatsInterval == b.MinimumReadMemStatsInterval
}
Loading

0 comments on commit 7b8afe3

Please sign in to comment.