diff --git a/.github/workflows/protect-released-changelog.yml b/.github/workflows/protect-released-changelog.yml new file mode 100644 index 00000000000..5634b78b813 --- /dev/null +++ b/.github/workflows/protect-released-changelog.yml @@ -0,0 +1,20 @@ +# This action against that any PR targeting the main branch touches released +# sections in CHANGELOG file. If change to released CHANGELOG is required, like +# doing a release, add the \"Unlock Released Changelog\" label to disable this action. + +name: Protect released changelog + +on: + pull_request: + types: [opened, synchronize, reopened, labeled, unlabeled] +jobs: + protect-released-changelog: + runs-on: ubuntu-latest + if: ${{ !contains(github.event.pull_request.labels.*.name, 'Unlock Released Changelog')}} + + steps: + - uses: actions/checkout@v4 + + - name: Protect the released changelog + run: | + ./tools/verify_released_changelog.sh ${{ github.base_ref }} diff --git a/CHANGELOG.md b/CHANGELOG.md index f6ae2ffb08c..c7a01b70bb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,19 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Support for stdoutlog exporter in `go.opentelemetry.io/contrib/config`. (#5850) - Add macOS ARM64 platform to the compatibility testing suite. (#5868) +- 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) +- Support for the `OTEL_HTTP_CLIENT_COMPATIBILITY_MODE=http/dup` environment variable in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` to emit attributes for both the v1.20.0 and v1.24.0 semantic conventions. (#5401) ### Removed - The deprecated `go.opentelemetry.io/contrib/processors/baggagecopy` package is removed. (#5853) + + + ## [1.28.0/0.53.0/0.22.0/0.8.0/0.3.0/0.1.0] - 2024-07-02 ### Added @@ -1132,6 +1140,8 @@ First official tagged release of `contrib` repository. [0.7.0]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.7.0 [0.6.1]: https://github.com/open-telemetry/opentelemetry-go-contrib/releases/tag/v0.6.1 + + [Go 1.22]: https://go.dev/doc/go1.22 [Go 1.21]: https://go.dev/doc/go1.21 [Go 1.20]: https://go.dev/doc/go1.20 diff --git a/CODEOWNERS b/CODEOWNERS index 077f34e7d6c..d4abc2b9d36 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -29,6 +29,7 @@ bridges/otelslog @open-te bridges/otellogrus/ @open-telemetry/go-approvers @dmathieu @pellared bridges/prometheus/ @open-telemetry/go-approvers @dashpole bridges/otelzap/ @open-telemetry/go-approvers @pellared @khushijain21 +bridges/otelzerolog/ @open-telemetry/go-approvers @dmathieu @AkhigbeEromo config/ @open-telemetry/go-approvers @MadVikingGod @pellared @codeboten diff --git a/RELEASING.md b/RELEASING.md index b2b5700a147..a345df17546 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -108,6 +108,9 @@ since the last release tag. git --no-pager log --pretty=oneline "..HEAD" ``` +Make sure the new released section is under the comment for released section, +like ``, so it is protected from being overwritten in the future. + Be sure to update all the appropriate links at the bottom of the file. Finally, commit this change to your release branch. diff --git a/config/metric.go b/config/metric.go index 72f21cf3850..6c6b34e9f59 100644 --- a/config/metric.go +++ b/config/metric.go @@ -230,6 +230,22 @@ func prometheusReader(ctx context.Context, prometheusConfig *Prometheus) (sdkmet if prometheusConfig.WithoutUnits != nil && *prometheusConfig.WithoutUnits { opts = append(opts, otelprom.WithoutUnits()) } + if prometheusConfig.WithResourceConstantLabels != nil { + if prometheusConfig.WithResourceConstantLabels.Included != nil { + var keys []attribute.Key + for _, val := range prometheusConfig.WithResourceConstantLabels.Included { + keys = append(keys, attribute.Key(val)) + } + otelprom.WithResourceAsConstantLabels(attribute.NewAllowKeysFilter(keys...)) + } + if prometheusConfig.WithResourceConstantLabels.Excluded != nil { + var keys []attribute.Key + for _, val := range prometheusConfig.WithResourceConstantLabels.Included { + keys = append(keys, attribute.Key(val)) + } + otelprom.WithResourceAsConstantLabels(attribute.NewDenyKeysFilter(keys...)) + } + } reg := prometheus.NewRegistry() opts = append(opts, otelprom.WithRegisterer(reg)) @@ -246,9 +262,6 @@ func prometheusReader(ctx context.Context, prometheusConfig *Prometheus) (sdkmet } addr := fmt.Sprintf("%s:%d", *prometheusConfig.Host, *prometheusConfig.Port) - // TODO: add support for constant label filter - // otelprom.WithResourceAsConstantLabels(attribute.NewDenyKeysFilter()), - // ) reader, err := otelprom.New(opts...) if err != nil { return nil, fmt.Errorf("error creating otel prometheus exporter: %w", err) diff --git a/config/metric_test.go b/config/metric_test.go index 83aa7a8beb0..34ead1fddb9 100644 --- a/config/metric_test.go +++ b/config/metric_test.go @@ -148,8 +148,15 @@ func TestReader(t *testing.T) { Pull: &PullMetricReader{ Exporter: MetricExporter{ Prometheus: &Prometheus{ - Host: ptr("localhost"), - Port: ptr(8888), + Host: ptr("localhost"), + Port: ptr(8888), + WithoutScopeInfo: ptr(true), + WithoutUnits: ptr(true), + WithoutTypeSuffix: ptr(true), + WithResourceConstantLabels: &IncludeExclude{ + Included: []string{"include"}, + Excluded: []string{"exclude"}, + }, }, }, }, diff --git a/detectors/aws/ec2/go.mod b/detectors/aws/ec2/go.mod index 62f98b59b8d..7ce24217885 100644 --- a/detectors/aws/ec2/go.mod +++ b/detectors/aws/ec2/go.mod @@ -3,7 +3,7 @@ module go.opentelemetry.io/contrib/detectors/aws/ec2 go 1.21 require ( - github.com/aws/aws-sdk-go v1.54.18 + github.com/aws/aws-sdk-go v1.54.19 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.28.0 go.opentelemetry.io/otel/sdk v1.28.0 diff --git a/detectors/aws/ec2/go.sum b/detectors/aws/ec2/go.sum index 787c7f3cdbe..64b14681ad9 100644 --- a/detectors/aws/ec2/go.sum +++ b/detectors/aws/ec2/go.sum @@ -1,5 +1,5 @@ -github.com/aws/aws-sdk-go v1.54.18 h1:t8DGtN8A2wEiazoJxeDbfPsbxCKtjoRLuO7jBSgJzo4= -github.com/aws/aws-sdk-go v1.54.18/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go v1.54.19 h1:tyWV+07jagrNiCcGRzRhdtVjQs7Vy41NwsuOcl0IbVI= +github.com/aws/aws-sdk-go v1.54.19/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/instrumentation/runtime/doc.go b/instrumentation/runtime/doc.go index 5cbcf1e4589..2b5e78686d4 100644 --- a/instrumentation/runtime/doc.go +++ b/instrumentation/runtime/doc.go @@ -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" diff --git a/instrumentation/runtime/options.go b/instrumentation/runtime/options.go new file mode 100644 index 00000000000..30046ab3509 --- /dev/null +++ b/instrumentation/runtime/options.go @@ -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 +} diff --git a/instrumentation/runtime/options_test.go b/instrumentation/runtime/options_test.go new file mode 100644 index 00000000000..3c32a740938 --- /dev/null +++ b/instrumentation/runtime/options_test.go @@ -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 +} diff --git a/instrumentation/runtime/runtime.go b/instrumentation/runtime/runtime.go index 3c520a49933..be7911bb450 100644 --- a/instrumentation/runtime/runtime.go +++ b/instrumentation/runtime/runtime.go @@ -4,9 +4,14 @@ package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" import ( + "context" + "math" + "runtime/metrics" + "sync" "time" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime" @@ -16,70 +21,18 @@ import ( // ScopeName is the instrumentation scope name. const ScopeName = "go.opentelemetry.io/contrib/instrumentation/runtime" -// 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 -} +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" +) // Start initializes reporting of runtime metrics using the supplied config. func Start(opts ...Option) error { @@ -97,6 +50,175 @@ func Start(opts ...Option) error { if x.DeprecatedRuntimeMetrics.Enabled() { return deprecatedruntime.Start(meter, c.MinimumReadMemStatsInterval) } - // TODO (#5655) Implement new runtime conventions + 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) + var lock sync.Mutex + _, err = meter.RegisterCallback( + func(ctx context.Context, o metric.Observer) error { + lock.Lock() + defer lock.Unlock() + collector.refresh() + stackMemory := collector.get(goHeapMemory) + o.ObserveInt64(memoryUsedInstrument, stackMemory, stackMemoryOpt) + totalMemory := collector.get(goTotalMemory) - collector.get(goMemoryReleased) + otherMemory := totalMemory - stackMemory + o.ObserveInt64(memoryUsedInstrument, otherMemory, otherMemoryOpt) + // Only observe the limit metric if a limit exists + if limit := collector.get(goMemoryLimit); limit != math.MaxInt64 { + o.ObserveInt64(memoryLimitInstrument, limit) + } + o.ObserveInt64(memoryAllocatedInstrument, collector.get(goMemoryAllocated)) + o.ObserveInt64(memoryAllocationsInstrument, collector.get(goMemoryAllocations)) + o.ObserveInt64(memoryGCGoalInstrument, collector.get(goMemoryGoal)) + o.ObserveInt64(goroutineCountInstrument, collector.get(goGoroutines)) + o.ObserveInt64(processorLimitInstrument, collector.get(goMaxProcs)) + o.ObserveInt64(gogcConfigInstrument, collector.get(goConfigGC)) + return nil + }, + memoryUsedInstrument, + memoryLimitInstrument, + memoryAllocatedInstrument, + memoryAllocationsInstrument, + memoryGCGoalInstrument, + goroutineCountInstrument, + processorLimitInstrument, + gogcConfigInstrument, + ) + if err != nil { + return err + } + // TODO (#5655) support go.schedule.duration 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) *goCollector { + g := &goCollector{ + sampleBuffer: make([]metrics.Sample, 0, len(runtimeMetrics)), + sampleMap: make(map[string]*metrics.Sample, len(runtimeMetrics)), + minimumInterval: minimumInterval, + now: time.Now, + } + for _, runtimeMetric := range runtimeMetrics { + g.sampleBuffer = append(g.sampleBuffer, metrics.Sample{Name: runtimeMetric}) + // 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[runtimeMetric] = &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) get(name string) int64 { + if s, ok := g.sampleMap[name]; ok && s.Value.Kind() == metrics.KindUint64 { + return int64(s.Value.Uint64()) + } + return 0 +} diff --git a/instrumentation/runtime/runtime_test.go b/instrumentation/runtime/runtime_test.go index 78d89a3c391..8b27e347d27 100644 --- a/instrumentation/runtime/runtime_test.go +++ b/instrumentation/runtime/runtime_test.go @@ -1,12 +1,53 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package runtime_test - -// TODO(#2757): Add integration tests for the runtime instrumentation. These -// tests depend on -// https://github.com/open-telemetry/opentelemetry-go/issues/3031 being -// resolved. -// -// The added tests will depend on the metric SDK. Therefore, they should be -// added to a sub-directory called "test" instead of this file. +package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestRefreshGoCollector(t *testing.T) { + // buffer for allocating memory + var buffer [][]byte + collector := newCollector(10 * time.Second) + testClock := newClock() + collector.now = testClock.now + // before the first refresh, all counters are zero + assert.Zero(t, collector.get(goMemoryAllocations)) + // after the first refresh, counters are non-zero + buffer = allocateMemory(buffer) + collector.refresh() + initialAllocations := collector.get(goMemoryAllocations) + assert.NotZero(t, initialAllocations) + // if less than the refresh time has elapsed, the value is not updated + // on refresh. + testClock.increment(9 * time.Second) + collector.refresh() + buffer = allocateMemory(buffer) + assert.Equal(t, initialAllocations, collector.get(goMemoryAllocations)) + // if greater than the refresh time has elapsed, the value changes. + testClock.increment(2 * time.Second) + collector.refresh() + _ = allocateMemory(buffer) + assert.NotEqual(t, initialAllocations, collector.get(goMemoryAllocations)) +} + +func allocateMemory(buffer [][]byte) [][]byte { + return append(buffer, make([]byte, 1000000)) +} + +func newClock() *clock { + return &clock{current: time.Now()} +} + +type clock struct { + current time.Time +} + +func (c *clock) now() time.Time { return c.current } + +func (c *clock) increment(d time.Duration) { c.current = c.current.Add(d) } diff --git a/instrumentation/runtime/test/go.mod b/instrumentation/runtime/test/go.mod new file mode 100644 index 00000000000..916e4bcabe3 --- /dev/null +++ b/instrumentation/runtime/test/go.mod @@ -0,0 +1,25 @@ +module go.opentelemetry.io/contrib/instrumentation/runtime/test + +go 1.21 + +require ( + github.com/stretchr/testify v1.9.0 + go.opentelemetry.io/contrib/instrumentation/runtime v0.53.0 + go.opentelemetry.io/otel v1.28.0 + go.opentelemetry.io/otel/sdk v1.28.0 + go.opentelemetry.io/otel/sdk/metric v1.28.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/google/uuid v1.6.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + golang.org/x/sys v0.22.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace go.opentelemetry.io/contrib/instrumentation/runtime => ../ diff --git a/instrumentation/runtime/test/go.sum b/instrumentation/runtime/test/go.sum new file mode 100644 index 00000000000..53e2a04097c --- /dev/null +++ b/instrumentation/runtime/test/go.sum @@ -0,0 +1,31 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= +go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +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/test/runtime_test.go b/instrumentation/runtime/test/runtime_test.go new file mode 100644 index 00000000000..936df2d54f5 --- /dev/null +++ b/instrumentation/runtime/test/runtime_test.go @@ -0,0 +1,157 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime/test" + +import ( + "context" + "fmt" + "math" + "runtime/debug" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/contrib/instrumentation/runtime" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/instrumentation" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" +) + +func TestRuntimeWithLimit(t *testing.T) { + // buffer for allocating memory + var buffer [][]byte + _ = allocateMemory(buffer) + t.Setenv("OTEL_GO_X_DEPRECATED_RUNTIME_METRICS", "false") + debug.SetMemoryLimit(1234567890) + // reset to default + defer debug.SetMemoryLimit(math.MaxInt64) + + reader := metric.NewManualReader() + mp := metric.NewMeterProvider(metric.WithReader(reader)) + err := runtime.Start(runtime.WithMeterProvider(mp)) + assert.NoError(t, err) + rm := metricdata.ResourceMetrics{} + err = reader.Collect(context.Background(), &rm) + assert.NoError(t, err) + require.Len(t, rm.ScopeMetrics, 1) + require.Len(t, rm.ScopeMetrics[0].Metrics, 8) + + expectedScopeMetric := metricdata.ScopeMetrics{ + Scope: instrumentation.Scope{ + Name: "go.opentelemetry.io/contrib/instrumentation/runtime", + Version: runtime.Version(), + }, + Metrics: []metricdata.Metrics{ + { + Name: "go.memory.used", + Description: "Memory used by the Go runtime.", + Unit: "By", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: false, + DataPoints: []metricdata.DataPoint[int64]{ + { + Attributes: attribute.NewSet(attribute.String("go.memory.type", "stack")), + }, + { + Attributes: attribute.NewSet(attribute.String("go.memory.type", "other")), + }, + }, + }, + }, + { + Name: "go.memory.limit", + Description: "Go runtime memory limit configured by the user, if a limit exists.", + Unit: "By", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: false, + DataPoints: []metricdata.DataPoint[int64]{{}}, + }, + }, + { + Name: "go.memory.allocated", + Description: "Memory allocated to the heap by the application.", + Unit: "By", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: []metricdata.DataPoint[int64]{{}}, + }, + }, + { + Name: "go.memory.allocations", + Description: "Count of allocations to the heap by the application.", + Unit: "{allocation}", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: []metricdata.DataPoint[int64]{{}}, + }, + }, + { + Name: "go.memory.gc.goal", + Description: "Heap size target for the end of the GC cycle.", + Unit: "By", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: false, + DataPoints: []metricdata.DataPoint[int64]{{}}, + }, + }, + { + Name: "go.goroutine.count", + Description: "Count of live goroutines.", + Unit: "{goroutine}", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: false, + DataPoints: []metricdata.DataPoint[int64]{{}}, + }, + }, + { + Name: "go.processor.limit", + Description: "The number of OS threads that can execute user-level Go code simultaneously.", + Unit: "{thread}", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: false, + DataPoints: []metricdata.DataPoint[int64]{{}}, + }, + }, + { + Name: "go.config.gogc", + Description: "Heap size target percentage configured by the user, otherwise 100.", + Unit: "%", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: false, + DataPoints: []metricdata.DataPoint[int64]{{}}, + }, + }, + }, + } + metricdatatest.AssertEqual(t, expectedScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) + assertNonZeroValues(t, rm.ScopeMetrics[0]) +} + +func assertNonZeroValues(t *testing.T, sm metricdata.ScopeMetrics) { + for _, m := range sm.Metrics { + switch a := m.Data.(type) { + case metricdata.Sum[int64]: + for _, dp := range a.DataPoints { + assert.True(t, dp.Value > 0, fmt.Sprintf("Metric %q should have a non-zero value for point with attributes %+v", m.Name, dp.Attributes)) + } + default: + t.Fatalf("unexpected data type %v", a) + } + } +} + +func allocateMemory(buffer [][]byte) [][]byte { + return append(buffer, make([]byte, 1000000)) +} diff --git a/tools/go.mod b/tools/go.mod index 09a2f1e5e98..2e322884a55 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -13,7 +13,7 @@ require ( go.opentelemetry.io/build-tools/crosslink v0.14.0 go.opentelemetry.io/build-tools/gotmpl v0.14.0 go.opentelemetry.io/build-tools/multimod v0.14.0 - golang.org/x/exp v0.0.0-20240707233637-46b078467d37 + golang.org/x/exp v0.0.0-20240716160929-1d5bc16f04a8 golang.org/x/tools v0.23.0 golang.org/x/vuln v1.1.2 ) diff --git a/tools/go.sum b/tools/go.sum index 6ec2c2b3612..8e085f70aa0 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -508,8 +508,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= -golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20240716160929-1d5bc16f04a8 h1:Z+vTUQyBb738QmIhbJx3z4htsxDeI+rd0EHvNm8jHkg= +golang.org/x/exp v0.0.0-20240716160929-1d5bc16f04a8/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= diff --git a/tools/verify_released_changelog.sh b/tools/verify_released_changelog.sh new file mode 100755 index 00000000000..c9b7cdbbfef --- /dev/null +++ b/tools/verify_released_changelog.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +TARGET="${1:?Must provide target ref}" + +FILE="CHANGELOG.md" +TEMP_DIR=$(mktemp -d) +echo "Temp folder: $TEMP_DIR" + +# Only the latest commit of the feature branch is available +# automatically. To diff with the base branch, we need to +# fetch that too (and we only need its latest commit). +git fetch origin "${TARGET}" --depth=1 + +# Checkout the previous version on the base branch of the changelog to tmpfolder +git --work-tree="$TEMP_DIR" checkout FETCH_HEAD $FILE + +PREVIOUS_FILE="$TEMP_DIR/$FILE" +CURRENT_FILE="$FILE" +PREVIOUS_LOCKED_FILE="$TEMP_DIR/previous_locked_section.md" +CURRENT_LOCKED_FILE="$TEMP_DIR/current_locked_section.md" + +# Extract released sections from the previous version +awk '/^/ {flag=1} /^/ {flag=0} flag' "$PREVIOUS_FILE" > "$PREVIOUS_LOCKED_FILE" + +# Extract released sections from the current version +awk '/^/ {flag=1} /^/ {flag=0} flag' "$CURRENT_FILE" > "$CURRENT_LOCKED_FILE" + +# Compare the released sections +if ! diff -q "$PREVIOUS_LOCKED_FILE" "$CURRENT_LOCKED_FILE"; then + echo "Error: The released sections of the changelog file have been modified." + diff "$PREVIOUS_LOCKED_FILE" "$CURRENT_LOCKED_FILE" + rm -rf "$TEMP_DIR" + false +fi + +rm -rf "$TEMP_DIR" +echo "The released sections remain unchanged." diff --git a/versions.yaml b/versions.yaml index e24308efac4..a66ee6e247c 100644 --- a/versions.yaml +++ b/versions.yaml @@ -78,6 +78,7 @@ module-sets: modules: - go.opentelemetry.io/contrib/bridges/otelslog - go.opentelemetry.io/contrib/bridges/otellogrus + - go.opentelemetry.io/contrib/bridges/otelzap experimental-processors: version: v0.1.0 modules: @@ -89,7 +90,6 @@ module-sets: - go.opentelemetry.io/contrib/detectors/azure/azurevm excluded-modules: - go.opentelemetry.io/contrib/bridges/otelzerolog - - go.opentelemetry.io/contrib/bridges/otelzap - go.opentelemetry.io/contrib/instrgen - go.opentelemetry.io/contrib/instrgen/driver - go.opentelemetry.io/contrib/instrgen/testdata/interface