Skip to content

Commit 666c2e2

Browse files
committed
prepare for migration to new runtime metrics
1 parent 9ddea00 commit 666c2e2

File tree

9 files changed

+456
-268
lines changed

9 files changed

+456
-268
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1212

1313
- The `go.opentelemetry.io/contrib/config` add support to configure periodic reader interval and timeout. (#5661)
1414
- Add support to configure views when creating MeterProvider using the config package. (#5654)
15+
- Add support for disabling the old runtime metrics using the `OTEL_GO_X_DEPRECATED_RUNTIME_METRICS=false` environment variable. (#5747)
1516

1617
### Fixed
1718

instrumentation/runtime/go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@ module go.opentelemetry.io/contrib/instrumentation/runtime
33
go 1.21
44

55
require (
6+
github.com/stretchr/testify v1.9.0
67
go.opentelemetry.io/otel v1.27.0
78
go.opentelemetry.io/otel/metric v1.27.0
89
)
910

1011
require (
12+
github.com/davecgh/go-spew v1.1.1 // indirect
1113
github.com/go-logr/logr v1.4.2 // indirect
1214
github.com/go-logr/stdr v1.2.2 // indirect
15+
github.com/pmezard/go-difflib v1.0.0 // indirect
1316
go.opentelemetry.io/otel/trace v1.27.0 // indirect
17+
gopkg.in/yaml.v3 v3.0.1 // indirect
1418
)

instrumentation/runtime/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,7 @@ go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0
1717
go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
1818
go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw=
1919
go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
20+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
21+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
2022
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
2123
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Package deprecatedruntime implements the deprecated runtime metrics for OpenTelemetry.
5+
//
6+
// The metric events produced are:
7+
//
8+
// runtime.go.cgo.calls - Number of cgo calls made by the current process
9+
// runtime.go.gc.count - Number of completed garbage collection cycles
10+
// runtime.go.gc.pause_ns (ns) Amount of nanoseconds in GC stop-the-world pauses
11+
// runtime.go.gc.pause_total_ns (ns) Cumulative nanoseconds in GC stop-the-world pauses since the program started
12+
// runtime.go.goroutines - Number of goroutines that currently exist
13+
// runtime.go.lookups - Number of pointer lookups performed by the runtime
14+
// runtime.go.mem.heap_alloc (bytes) Bytes of allocated heap objects
15+
// runtime.go.mem.heap_idle (bytes) Bytes in idle (unused) spans
16+
// runtime.go.mem.heap_inuse (bytes) Bytes in in-use spans
17+
// runtime.go.mem.heap_objects - Number of allocated heap objects
18+
// runtime.go.mem.heap_released (bytes) Bytes of idle spans whose physical memory has been returned to the OS
19+
// runtime.go.mem.heap_sys (bytes) Bytes of heap memory obtained from the OS
20+
// runtime.go.mem.live_objects - Number of live objects is the number of cumulative Mallocs - Frees
21+
// runtime.uptime (ms) Milliseconds since application was initialized
22+
package deprecatedruntime // import "go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime"
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package deprecatedruntime // import "go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime"
5+
6+
import (
7+
"context"
8+
goruntime "runtime"
9+
"sync"
10+
"time"
11+
12+
"go.opentelemetry.io/otel/metric"
13+
)
14+
15+
// Runtime reports the work-in-progress conventional runtime metrics specified by OpenTelemetry.
16+
type runtime struct {
17+
minimumReadMemStatsInterval time.Duration
18+
meter metric.Meter
19+
}
20+
21+
// Start initializes reporting of runtime metrics using the supplied config.
22+
func Start(meter metric.Meter, minimumReadMemStatsInterval time.Duration) error {
23+
r := &runtime{
24+
meter: meter,
25+
minimumReadMemStatsInterval: minimumReadMemStatsInterval,
26+
}
27+
return r.register()
28+
}
29+
30+
func (r *runtime) register() error {
31+
startTime := time.Now()
32+
uptime, err := r.meter.Int64ObservableCounter(
33+
"runtime.uptime",
34+
metric.WithUnit("ms"),
35+
metric.WithDescription("Milliseconds since application was initialized"),
36+
)
37+
if err != nil {
38+
return err
39+
}
40+
41+
goroutines, err := r.meter.Int64ObservableUpDownCounter(
42+
"process.runtime.go.goroutines",
43+
metric.WithDescription("Number of goroutines that currently exist"),
44+
)
45+
if err != nil {
46+
return err
47+
}
48+
49+
cgoCalls, err := r.meter.Int64ObservableUpDownCounter(
50+
"process.runtime.go.cgo.calls",
51+
metric.WithDescription("Number of cgo calls made by the current process"),
52+
)
53+
if err != nil {
54+
return err
55+
}
56+
57+
_, err = r.meter.RegisterCallback(
58+
func(ctx context.Context, o metric.Observer) error {
59+
o.ObserveInt64(uptime, time.Since(startTime).Milliseconds())
60+
o.ObserveInt64(goroutines, int64(goruntime.NumGoroutine()))
61+
o.ObserveInt64(cgoCalls, goruntime.NumCgoCall())
62+
return nil
63+
},
64+
uptime,
65+
goroutines,
66+
cgoCalls,
67+
)
68+
if err != nil {
69+
return err
70+
}
71+
72+
return r.registerMemStats()
73+
}
74+
75+
func (r *runtime) registerMemStats() error {
76+
var (
77+
err error
78+
79+
heapAlloc metric.Int64ObservableUpDownCounter
80+
heapIdle metric.Int64ObservableUpDownCounter
81+
heapInuse metric.Int64ObservableUpDownCounter
82+
heapObjects metric.Int64ObservableUpDownCounter
83+
heapReleased metric.Int64ObservableUpDownCounter
84+
heapSys metric.Int64ObservableUpDownCounter
85+
liveObjects metric.Int64ObservableUpDownCounter
86+
87+
// TODO: is ptrLookups useful? I've not seen a value
88+
// other than zero.
89+
ptrLookups metric.Int64ObservableCounter
90+
91+
gcCount metric.Int64ObservableCounter
92+
pauseTotalNs metric.Int64ObservableCounter
93+
gcPauseNs metric.Int64Histogram
94+
95+
lastNumGC uint32
96+
lastMemStats time.Time
97+
memStats goruntime.MemStats
98+
99+
// lock prevents a race between batch observer and instrument registration.
100+
lock sync.Mutex
101+
)
102+
103+
lock.Lock()
104+
defer lock.Unlock()
105+
106+
if heapAlloc, err = r.meter.Int64ObservableUpDownCounter(
107+
"process.runtime.go.mem.heap_alloc",
108+
metric.WithUnit("By"),
109+
metric.WithDescription("Bytes of allocated heap objects"),
110+
); err != nil {
111+
return err
112+
}
113+
114+
if heapIdle, err = r.meter.Int64ObservableUpDownCounter(
115+
"process.runtime.go.mem.heap_idle",
116+
metric.WithUnit("By"),
117+
metric.WithDescription("Bytes in idle (unused) spans"),
118+
); err != nil {
119+
return err
120+
}
121+
122+
if heapInuse, err = r.meter.Int64ObservableUpDownCounter(
123+
"process.runtime.go.mem.heap_inuse",
124+
metric.WithUnit("By"),
125+
metric.WithDescription("Bytes in in-use spans"),
126+
); err != nil {
127+
return err
128+
}
129+
130+
if heapObjects, err = r.meter.Int64ObservableUpDownCounter(
131+
"process.runtime.go.mem.heap_objects",
132+
metric.WithDescription("Number of allocated heap objects"),
133+
); err != nil {
134+
return err
135+
}
136+
137+
// FYI see https://github.com/golang/go/issues/32284 to help
138+
// understand the meaning of this value.
139+
if heapReleased, err = r.meter.Int64ObservableUpDownCounter(
140+
"process.runtime.go.mem.heap_released",
141+
metric.WithUnit("By"),
142+
metric.WithDescription("Bytes of idle spans whose physical memory has been returned to the OS"),
143+
); err != nil {
144+
return err
145+
}
146+
147+
if heapSys, err = r.meter.Int64ObservableUpDownCounter(
148+
"process.runtime.go.mem.heap_sys",
149+
metric.WithUnit("By"),
150+
metric.WithDescription("Bytes of heap memory obtained from the OS"),
151+
); err != nil {
152+
return err
153+
}
154+
155+
if ptrLookups, err = r.meter.Int64ObservableCounter(
156+
"process.runtime.go.mem.lookups",
157+
metric.WithDescription("Number of pointer lookups performed by the runtime"),
158+
); err != nil {
159+
return err
160+
}
161+
162+
if liveObjects, err = r.meter.Int64ObservableUpDownCounter(
163+
"process.runtime.go.mem.live_objects",
164+
metric.WithDescription("Number of live objects is the number of cumulative Mallocs - Frees"),
165+
); err != nil {
166+
return err
167+
}
168+
169+
if gcCount, err = r.meter.Int64ObservableCounter(
170+
"process.runtime.go.gc.count",
171+
metric.WithDescription("Number of completed garbage collection cycles"),
172+
); err != nil {
173+
return err
174+
}
175+
176+
// Note that the following could be derived as a sum of
177+
// individual pauses, but we may lose individual pauses if the
178+
// observation interval is too slow.
179+
if pauseTotalNs, err = r.meter.Int64ObservableCounter(
180+
"process.runtime.go.gc.pause_total_ns",
181+
// TODO: nanoseconds units
182+
metric.WithDescription("Cumulative nanoseconds in GC stop-the-world pauses since the program started"),
183+
); err != nil {
184+
return err
185+
}
186+
187+
if gcPauseNs, err = r.meter.Int64Histogram(
188+
"process.runtime.go.gc.pause_ns",
189+
// TODO: nanoseconds units
190+
metric.WithDescription("Amount of nanoseconds in GC stop-the-world pauses"),
191+
); err != nil {
192+
return err
193+
}
194+
195+
_, err = r.meter.RegisterCallback(
196+
func(ctx context.Context, o metric.Observer) error {
197+
lock.Lock()
198+
defer lock.Unlock()
199+
200+
now := time.Now()
201+
if now.Sub(lastMemStats) >= r.minimumReadMemStatsInterval {
202+
goruntime.ReadMemStats(&memStats)
203+
lastMemStats = now
204+
}
205+
206+
o.ObserveInt64(heapAlloc, int64(memStats.HeapAlloc))
207+
o.ObserveInt64(heapIdle, int64(memStats.HeapIdle))
208+
o.ObserveInt64(heapInuse, int64(memStats.HeapInuse))
209+
o.ObserveInt64(heapObjects, int64(memStats.HeapObjects))
210+
o.ObserveInt64(heapReleased, int64(memStats.HeapReleased))
211+
o.ObserveInt64(heapSys, int64(memStats.HeapSys))
212+
o.ObserveInt64(liveObjects, int64(memStats.Mallocs-memStats.Frees))
213+
o.ObserveInt64(ptrLookups, int64(memStats.Lookups))
214+
o.ObserveInt64(gcCount, int64(memStats.NumGC))
215+
o.ObserveInt64(pauseTotalNs, int64(memStats.PauseTotalNs))
216+
217+
computeGCPauses(ctx, gcPauseNs, memStats.PauseNs[:], lastNumGC, memStats.NumGC)
218+
219+
lastNumGC = memStats.NumGC
220+
221+
return nil
222+
},
223+
heapAlloc,
224+
heapIdle,
225+
heapInuse,
226+
heapObjects,
227+
heapReleased,
228+
heapSys,
229+
liveObjects,
230+
231+
ptrLookups,
232+
233+
gcCount,
234+
pauseTotalNs,
235+
)
236+
if err != nil {
237+
return err
238+
}
239+
return nil
240+
}
241+
242+
func computeGCPauses(
243+
ctx context.Context,
244+
recorder metric.Int64Histogram,
245+
circular []uint64,
246+
lastNumGC, currentNumGC uint32,
247+
) {
248+
delta := int(int64(currentNumGC) - int64(lastNumGC))
249+
250+
if delta == 0 {
251+
return
252+
}
253+
254+
if delta >= len(circular) {
255+
// There were > 256 collections, some may have been lost.
256+
recordGCPauses(ctx, recorder, circular)
257+
return
258+
}
259+
260+
length := uint32(len(circular))
261+
262+
i := lastNumGC % length
263+
j := currentNumGC % length
264+
265+
if j < i { // wrap around the circular buffer
266+
recordGCPauses(ctx, recorder, circular[i:])
267+
recordGCPauses(ctx, recorder, circular[:j])
268+
return
269+
}
270+
271+
recordGCPauses(ctx, recorder, circular[i:j])
272+
}
273+
274+
func recordGCPauses(
275+
ctx context.Context,
276+
recorder metric.Int64Histogram,
277+
pauses []uint64,
278+
) {
279+
for _, pause := range pauses {
280+
recorder.Record(ctx, int64(pause))
281+
}
282+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Feature Gates
2+
3+
The runtime package contains a feature gate used to ease the migration
4+
from the [previous runtime metrics conventions] to the new [OpenTelemetry Go
5+
Runtime conventions].
6+
7+
Note that the new runtime metrics conventions are still experimental, and may
8+
change in backwards incompatible ways as feedback is applied.
9+
10+
## Features
11+
12+
- [Include Deprecated Metrics](#include-deprecated-metrics)
13+
14+
### Include Deprecated Metrics
15+
16+
Once new experimental runtime metrics are added, they will be produced
17+
**in addition to** the existing runtime metrics. Users that migrate right away
18+
can disable the old runtime metrics:
19+
20+
```console
21+
export OTEL_GO_X_DEPRECATED_RUNTIME_METRICS=false
22+
```
23+
24+
In a later release, the deprecated runtime metrics will stop being produced by
25+
default. To temporarily re-enable the deprecated metrics:
26+
27+
```console
28+
export OTEL_GO_X_DEPRECATED_RUNTIME_METRICS=true
29+
```
30+
31+
After two additional releases, the deprecated runtime metrics will be removed,
32+
and setting the environment variable will no longer have any effect.
33+
34+
The value set must be the case-insensitive string of `"true"` to enable the
35+
feature, and `"false"` to disable the feature. All other values are ignored.
36+
37+
[previous runtime metrics conventions]: go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime
38+
[OpenTelemetry Go Runtime conventions]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/runtime/go-metrics.md

0 commit comments

Comments
 (0)