Skip to content

Commit a60647b

Browse files
jmacdMrAlias
andauthored
Create runtime.Config struct with metric.Provider and WithMinimumReadMemStatsInterval() configuration options (#224)
* Pass metric.Provider to runtime.Start; make interval param an Option * Add more tests * Address feedback * More comments * More comments * Apply instrumentation version Co-authored-by: Tyler Yahn <[email protected]>
1 parent 733f3b7 commit a60647b

File tree

6 files changed

+167
-19
lines changed

6 files changed

+167
-19
lines changed

instrumentation/runtime/doc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@
2929
// runtime.go.mem.heap_sys (bytes) Bytes of heap memory obtained from the OS
3030
// runtime.go.mem.live_objects - Number of live objects is the number of cumulative Mallocs - Frees
3131
// runtime.uptime (ms) Milliseconds since application was initialized
32-
package runtime
32+
package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime"

instrumentation/runtime/example/main.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"syscall"
2222
"time"
2323

24-
"go.opentelemetry.io/otel/api/global"
2524
"go.opentelemetry.io/otel/exporters/stdout"
2625
"go.opentelemetry.io/otel/sdk/metric/controller/push"
2726

@@ -42,9 +41,11 @@ func initMeter() *push.Controller {
4241
func main() {
4342
defer initMeter().Stop()
4443

45-
meter := global.Meter("runtime")
46-
47-
if err := runtime.Start(meter, time.Second); err != nil {
44+
if err := runtime.Start(
45+
runtime.Configure(
46+
runtime.WithMinimumReadMemStatsInterval(time.Second),
47+
),
48+
); err != nil {
4849
panic(err)
4950
}
5051

instrumentation/runtime/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ replace go.opentelemetry.io/contrib => ../..
66

77
require (
88
github.com/stretchr/testify v1.6.1
9+
go.opentelemetry.io/contrib v0.10.1
910
go.opentelemetry.io/otel v0.10.0
1011
go.opentelemetry.io/otel/exporters/stdout v0.10.0
1112
go.opentelemetry.io/otel/sdk v0.10.0

instrumentation/runtime/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
9090
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
9191
google.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE=
9292
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
93+
google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI=
94+
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
9395
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
9496
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
9597
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

instrumentation/runtime/runtime.go

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,100 @@ import (
2020
"sync"
2121
"time"
2222

23+
"go.opentelemetry.io/contrib"
24+
"go.opentelemetry.io/otel/api/global"
2325
"go.opentelemetry.io/otel/api/metric"
2426
"go.opentelemetry.io/otel/api/unit"
2527
)
2628

2729
// Runtime reports the work-in-progress conventional runtime metrics specified by OpenTelemetry
2830
type runtime struct {
29-
meter metric.Meter
30-
interval time.Duration
31+
config Config
32+
meter metric.Meter
3133
}
3234

33-
// New returns Runtime, a structure for reporting Go runtime metrics
34-
// interval is used to limit how often to invoke Go runtime.ReadMemStats() to obtain metric data.
35-
// If the metric SDK attempts to observe MemStats-derived instruments more frequently than the
36-
// interval, a cached value will be used.
37-
func Start(meter metric.Meter, interval time.Duration) error {
35+
// Config contains optional settings for reporting runtime metrics.
36+
type Config struct {
37+
// MinimumReadMemStatsInterval sets the mininum interval
38+
// between calls to runtime.ReadMemStats(). Negative values
39+
// are ignored.
40+
MinimumReadMemStatsInterval time.Duration
41+
42+
// MeterProvider sets the metric.Provider. If nil, the global
43+
// Provider will be used.
44+
MeterProvider metric.Provider
45+
}
46+
47+
// Option supports configuring optional settings for runtime metrics.
48+
type Option interface {
49+
// ApplyRuntime updates *Config.
50+
ApplyRuntime(*Config)
51+
}
52+
53+
// DefaultMinimumReadMemStatsInterval is the default minimum interval
54+
// between calls to runtime.ReadMemStats(). Use the
55+
// WithMinimumReadMemStatsInterval() option to modify this setting in
56+
// Start().
57+
const DefaultMinimumReadMemStatsInterval time.Duration = 15 * time.Second
58+
59+
// WithMinimumReadMemStatsInterval sets a minimum interval between calls to
60+
// runtime.ReadMemStats(), which is a relatively expensive call to make
61+
// frequently. This setting is ignored when `d` is negative.
62+
func WithMinimumReadMemStatsInterval(d time.Duration) Option {
63+
return minimumReadMemStatsIntervalOption(d)
64+
}
65+
66+
type minimumReadMemStatsIntervalOption time.Duration
67+
68+
// ApplyRuntime implements Option.
69+
func (o minimumReadMemStatsIntervalOption) ApplyRuntime(c *Config) {
70+
if o >= 0 {
71+
c.MinimumReadMemStatsInterval = time.Duration(o)
72+
}
73+
}
74+
75+
// WithMeterProvider sets the Metric implementation to use for
76+
// reporting. If this option is not used, the global metric.Provider
77+
// will be used. `provider` must be non-nil.
78+
func WithMeterProvider(provider metric.Provider) Option {
79+
return metricProviderOption{provider}
80+
}
81+
82+
type metricProviderOption struct{ metric.Provider }
83+
84+
// ApplyRuntime implements Option.
85+
func (o metricProviderOption) ApplyRuntime(c *Config) {
86+
c.MeterProvider = o.Provider
87+
}
88+
89+
// Configure computes a Config from the supplied Options.
90+
func Configure(opts ...Option) Config {
91+
c := Config{
92+
MeterProvider: global.MeterProvider(),
93+
MinimumReadMemStatsInterval: DefaultMinimumReadMemStatsInterval,
94+
}
95+
for _, opt := range opts {
96+
opt.ApplyRuntime(&c)
97+
}
98+
return c
99+
}
100+
101+
// Start initializes reporting of runtime metrics using the supplied Config.
102+
func Start(c Config) error {
103+
if c.MinimumReadMemStatsInterval < 0 {
104+
c.MinimumReadMemStatsInterval = DefaultMinimumReadMemStatsInterval
105+
}
106+
if c.MeterProvider == nil {
107+
c.MeterProvider = global.MeterProvider()
108+
}
38109
r := &runtime{
39-
meter: meter,
40-
interval: interval,
110+
meter: c.MeterProvider.Meter(
111+
// TODO: should library names be qualified?
112+
// e.g., contrib/runtime?
113+
"runtime",
114+
metric.WithInstrumentationVersion(contrib.SemVersion()),
115+
),
116+
config: c,
41117
}
42118
return r.register()
43119
}
@@ -118,7 +194,7 @@ func (r *runtime) registerMemStats() error {
118194
defer lock.Unlock()
119195

120196
now := time.Now()
121-
if now.Sub(lastMemStats) >= r.interval {
197+
if now.Sub(lastMemStats) >= r.config.MinimumReadMemStatsInterval {
122198
goruntime.ReadMemStats(&memStats)
123199
lastMemStats = now
124200
}

instrumentation/runtime/runtime_test.go

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,87 @@
1515
package runtime_test
1616

1717
import (
18+
goruntime "runtime"
1819
"testing"
1920
"time"
2021

2122
"github.com/stretchr/testify/assert"
23+
"github.com/stretchr/testify/require"
2224

2325
"go.opentelemetry.io/contrib/instrumentation/runtime"
24-
25-
"go.opentelemetry.io/otel/api/global"
26+
"go.opentelemetry.io/contrib/internal/metric"
2627
)
2728

2829
func TestRuntime(t *testing.T) {
29-
meter := global.Meter("test")
30-
err := runtime.Start(meter, time.Second)
30+
err := runtime.Start(
31+
runtime.Configure(
32+
runtime.WithMinimumReadMemStatsInterval(time.Second),
33+
),
34+
)
3135
assert.NoError(t, err)
3236
time.Sleep(time.Second)
3337
}
38+
39+
func getGCCount(impl *metric.MeterImpl) int {
40+
for _, b := range impl.MeasurementBatches {
41+
for _, m := range b.Measurements {
42+
if m.Instrument.Descriptor().Name() == "runtime.go.gc.count" {
43+
return int(m.Number.CoerceToInt64(m.Instrument.Descriptor().NumberKind()))
44+
}
45+
}
46+
}
47+
panic("Could not locate a runtime.go.gc.count metric in test output")
48+
}
49+
50+
func testMinimumInterval(t *testing.T, shouldHappen bool, opts ...runtime.Option) {
51+
goruntime.GC()
52+
53+
var mstats0 goruntime.MemStats
54+
goruntime.ReadMemStats(&mstats0)
55+
baseline := int(mstats0.NumGC)
56+
57+
impl, provider := metric.NewProvider()
58+
59+
err := runtime.Start(
60+
runtime.Configure(
61+
append(
62+
opts,
63+
runtime.WithMeterProvider(provider),
64+
)...,
65+
),
66+
)
67+
assert.NoError(t, err)
68+
69+
goruntime.GC()
70+
71+
impl.RunAsyncInstruments()
72+
73+
require.Equal(t, 1, getGCCount(impl)-baseline)
74+
75+
impl.MeasurementBatches = nil
76+
77+
extra := 0
78+
if shouldHappen {
79+
extra = 3
80+
}
81+
82+
goruntime.GC()
83+
goruntime.GC()
84+
goruntime.GC()
85+
86+
impl.RunAsyncInstruments()
87+
88+
require.Equal(t, 1+extra, getGCCount(impl)-baseline)
89+
}
90+
91+
func TestDefaultMinimumInterval(t *testing.T) {
92+
testMinimumInterval(t, false)
93+
}
94+
95+
func TestNoMinimumInterval(t *testing.T) {
96+
testMinimumInterval(t, true, runtime.WithMinimumReadMemStatsInterval(0))
97+
}
98+
99+
func TestExplicitMinimumInterval(t *testing.T) {
100+
testMinimumInterval(t, false, runtime.WithMinimumReadMemStatsInterval(time.Hour))
101+
}

0 commit comments

Comments
 (0)