diff --git a/go.mod b/go.mod index 373c127b95..da1f5be78c 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/spf13/pflag v1.0.6 github.com/tsenart/vegeta/v12 v12.12.0 go.opencensus.io v0.24.0 + go.opentelemetry.io/contrib/instrumentation/runtime v0.62.0 go.opentelemetry.io/otel v1.37.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0 diff --git a/go.sum b/go.sum index c4acd6b67c..c82bcae167 100644 --- a/go.sum +++ b/go.sum @@ -363,6 +363,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/runtime v0.62.0 h1:ZIt0ya9/y4WyRIzfLC8hQRRsWg0J9M9GyaGtIMiElZI= +go.opentelemetry.io/contrib/instrumentation/runtime v0.62.0/go.mod h1:F1aJ9VuiKWOlWwKdTYDUp1aoS0HzQxg38/VLxKmhm5U= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A= diff --git a/injection/sharedmain/main.go b/injection/sharedmain/main.go index 86bb1e3409..ae085d0715 100644 --- a/injection/sharedmain/main.go +++ b/injection/sharedmain/main.go @@ -28,6 +28,11 @@ import ( "strings" "time" + "go.opentelemetry.io/contrib/instrumentation/runtime" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/trace" + "go.uber.org/automaxprocs/maxprocs" // automatically set GOMAXPROCS based on cgroups "go.uber.org/zap" "golang.org/x/sync/errgroup" @@ -41,14 +46,18 @@ import ( "k8s.io/client-go/rest" kubeclient "knative.dev/pkg/client/injection/kube/client" + "knative.dev/pkg/configmap" cminformer "knative.dev/pkg/configmap/informer" "knative.dev/pkg/controller" "knative.dev/pkg/injection" "knative.dev/pkg/leaderelection" "knative.dev/pkg/logging" "knative.dev/pkg/logging/logkey" - "knative.dev/pkg/metrics" - "knative.dev/pkg/profiling" + "knative.dev/pkg/observability" + o11yconfigmap "knative.dev/pkg/observability/configmap" + "knative.dev/pkg/observability/metrics" + "knative.dev/pkg/observability/resource" + "knative.dev/pkg/observability/tracing" "knative.dev/pkg/reconciler" "knative.dev/pkg/signals" "knative.dev/pkg/system" @@ -108,19 +117,21 @@ func GetLeaderElectionConfig(ctx context.Context) (*leaderelection.Config, error // 1. provided context, // 2. reading from the API server, // 3. defaults (if not found). -func GetObservabilityConfig(ctx context.Context) (*metrics.ObservabilityConfig, error) { - if cfg := metrics.GetObservabilityConfig(ctx); cfg != nil { +func GetObservabilityConfig(ctx context.Context) (*observability.Config, error) { + if cfg := observability.GetConfig(ctx); cfg != nil { return cfg, nil } - observabilityConfigMap, err := kubeclient.Get(ctx).CoreV1().ConfigMaps(system.Namespace()).Get(ctx, metrics.ConfigMapName(), metav1.GetOptions{}) + client := kubeclient.Get(ctx).CoreV1().ConfigMaps(system.Namespace()) + cm, err := client.Get(ctx, o11yconfigmap.Name(), metav1.GetOptions{}) + if apierrors.IsNotFound(err) { - return metrics.NewObservabilityConfigFromConfigMap(nil) - } - if err != nil { + return observability.DefaultConfig(), nil + } else if err != nil { return nil, err } - return metrics.NewObservabilityConfigFromConfigMap(observabilityConfigMap) + + return o11yconfigmap.Parse(cm) } // EnableInjectionOrDie enables Knative Injection and starts the informers. @@ -229,8 +240,6 @@ func MainWithConfig(ctx context.Context, component string, cfg *rest.Config, cto log.Printf("Registering %d informers", len(injection.Default.GetInformers())) log.Printf("Registering %d controllers", len(ctors)) - metrics.MemStatsOrDie(ctx) - // Respect user provided settings, but if omitted customize the default behavior. if cfg.QPS == 0 { // Adjust our client's rate limits based on the number of controllers we are running. @@ -243,14 +252,14 @@ func MainWithConfig(ctx context.Context, component string, cfg *rest.Config, cto ctx, startInformers := injection.EnableInjectionOrDie(ctx, cfg) logger, atomicLevel := SetupLoggerOrDie(ctx, component) - defer flush(logger) + defer logger.Sync() ctx = logging.WithLogger(ctx, logger) // Override client-go's warning handler to give us nicely printed warnings. rest.SetDefaultWarningHandler(&logging.WarningHandler{Logger: logger}) - profilingHandler := profiling.NewHandler(logger, false) - profilingServer := profiling.NewServer(profilingHandler) + pprof := newProfilingServer(logger.Named("pprof")) + defer pprof.Shutdown(context.Background()) CheckK8sClientMinimumVersionOrDie(ctx, logger) cmw := SetupConfigMapWatchOrDie(ctx, logger) @@ -267,35 +276,37 @@ func MainWithConfig(ctx context.Context, component string, cfg *rest.Config, cto leaderElectionConfig.GetComponentConfig(component)) } - SetupObservabilityOrDie(ctx, component, logger, profilingHandler) + mp, tp := SetupObservabilityOrDie(ctx, component, logger, pprof) + defer func() { + if err := mp.Shutdown(context.Background()); err != nil { + logger.Errorw("Error flushing metrics", zap.Error(err)) + } + }() + defer func() { + if err := tp.Shutdown(context.Background()); err != nil { + logger.Errorw("Error flushing traces", zap.Error(err)) + } + }() controllers, webhooks := ControllersAndWebhooksFromCtors(ctx, cmw, ctors...) WatchLoggingConfigOrDie(ctx, cmw, logger, atomicLevel, component) - WatchObservabilityConfigOrDie(ctx, cmw, profilingHandler, logger, component) + WatchObservabilityConfigOrDie(ctx, cmw, pprof, logger, component) eg, egCtx := errgroup.WithContext(ctx) - eg.Go(profilingServer.ListenAndServe) + eg.Go(pprof.ListenAndServe) // Many of the webhooks rely on configuration, e.g. configurable defaults, feature flags. // So make sure that we have synchronized our configuration state before launching the // webhooks, so that things are properly initialized. - logger.Info("Starting configuration manager...") + logger.Info("Starting configmap watcher...") if err := cmw.Start(ctx.Done()); err != nil { - logger.Fatalw("Failed to start configuration manager", zap.Error(err)) + logger.Fatalw("Failed to start configmap watcher", zap.Error(err)) } // If we have one or more admission controllers, then start the webhook // and pass them in. var wh *webhook.Webhook if len(webhooks) > 0 { - // Register webhook metrics - opts := webhook.GetOptions(ctx) - if opts != nil { - webhook.RegisterMetrics(opts.StatsReporterOptions...) - } else { - webhook.RegisterMetrics() - } - wh, err = webhook.New(ctx, webhooks) if err != nil { logger.Fatalw("Failed to create webhook", zap.Error(err)) @@ -328,7 +339,6 @@ func MainWithConfig(ctx context.Context, component string, cfg *rest.Config, cto // returns an error. <-egCtx.Done() - profilingServer.Shutdown(context.Background()) // Don't forward ErrServerClosed as that indicates we're already shutting down. if err := eg.Wait(); err != nil && !errors.Is(err, http.ErrServerClosed) { logger.Errorw("Error while running server", zap.Error(err)) @@ -346,11 +356,6 @@ func healthProbesDisabled(ctx context.Context) bool { return ctx.Value(healthProbesDisabledKey{}) != nil } -func flush(logger *zap.SugaredLogger) { - logger.Sync() - metrics.FlushExporter() -} - // SetupLoggerOrDie sets up the logger using the config from the given context // and returns a logger and atomic level, or dies by calling log.Fatalf. func SetupLoggerOrDie(ctx context.Context, component string) (*zap.SugaredLogger, zap.AtomicLevel) { @@ -360,10 +365,7 @@ func SetupLoggerOrDie(ctx context.Context, component string) (*zap.SugaredLogger } l, level := logging.NewLoggerFromConfig(loggingConfig, component) - // If PodName is injected into the env vars, set it on the logger. - // This is needed for HA components to distinguish logs from different - // pods. - if pn := os.Getenv("POD_NAME"); pn != "" { + if pn := system.PodName(); pn != "" { l = l.With(zap.String(logkey.Pod, pn)) } @@ -372,14 +374,53 @@ func SetupLoggerOrDie(ctx context.Context, component string) (*zap.SugaredLogger // SetupObservabilityOrDie sets up the observability using the config from the given context // or dies by calling log.Fatalf. -func SetupObservabilityOrDie(ctx context.Context, component string, logger *zap.SugaredLogger, profilingHandler *profiling.Handler) { - observabilityConfig, err := GetObservabilityConfig(ctx) +func SetupObservabilityOrDie( + ctx context.Context, + component string, + logger *zap.SugaredLogger, + pprof *pprofServer, +) (*metrics.MeterProvider, *tracing.TracerProvider) { + cfg, err := GetObservabilityConfig(ctx) if err != nil { logger.Fatal("Error loading observability configuration: ", err) } - observabilityConfigMap := observabilityConfig.GetConfigMap() - metrics.ConfigMapWatcher(ctx, component, SecretFetcher(ctx), logger)(&observabilityConfigMap) - profilingHandler.UpdateFromConfigMap(&observabilityConfigMap) + + pprof.UpdateFromConfig(cfg.Runtime) + + resource := resource.Default(component) + + meterProvider, err := metrics.NewMeterProvider( + ctx, + cfg.Metrics, + metric.WithView(OTelViews(ctx)...), + metric.WithResource(resource), + ) + if err != nil { + logger.Fatalw("Failed to setup meter provider", zap.Error(err)) + } + + otel.SetMeterProvider(meterProvider) + + err = runtime.Start( + runtime.WithMinimumReadMemStatsInterval(cfg.Runtime.ExportInterval), + ) + if err != nil { + logger.Fatalw("Failed to start runtime metrics", zap.Error(err)) + } + + tracerProvider, err := tracing.NewTracerProvider( + ctx, + cfg.Tracing, + trace.WithResource(resource), + ) + if err != nil { + logger.Fatalw("Failed to setup trace provider", zap.Error(err)) + } + + otel.SetTextMapPropagator(tracing.DefaultTextMapPropagator()) + otel.SetTracerProvider(tracerProvider) + + return meterProvider, tracerProvider } // CheckK8sClientMinimumVersionOrDie checks that the hosting Kubernetes cluster @@ -424,30 +465,24 @@ func WatchLoggingConfigOrDie(ctx context.Context, cmw *cminformer.InformedWatche // WatchObservabilityConfigOrDie establishes a watch of the observability config // or dies by calling log.Fatalw. Note, if the config does not exist, it will be // defaulted and this method will not die. -func WatchObservabilityConfigOrDie(ctx context.Context, cmw *cminformer.InformedWatcher, profilingHandler *profiling.Handler, logger *zap.SugaredLogger, component string) { - if _, err := kubeclient.Get(ctx).CoreV1().ConfigMaps(system.Namespace()).Get(ctx, metrics.ConfigMapName(), - metav1.GetOptions{}); err == nil { - cmw.Watch(metrics.ConfigMapName(), - metrics.ConfigMapWatcher(ctx, component, SecretFetcher(ctx), logger), - profilingHandler.UpdateFromConfigMap) - } else if !apierrors.IsNotFound(err) { - logger.Fatalw("Error reading ConfigMap "+metrics.ConfigMapName(), zap.Error(err)) +func WatchObservabilityConfigOrDie( + ctx context.Context, + cmw *cminformer.InformedWatcher, + pprof *pprofServer, + logger *zap.SugaredLogger, + component string, +) { + cmName := o11yconfigmap.Name() + client := kubeclient.Get(ctx).CoreV1().ConfigMaps(system.Namespace()) + + observers := []configmap.Observer{ + pprof.UpdateFromConfigMap, } -} -// SecretFetcher provides a helper function to fetch individual Kubernetes -// Secrets (for example, a key for client-side TLS). Note that this is not -// intended for high-volume usage; the current use is when establishing a -// metrics client connection in WatchObservabilityConfigOrDie. -func SecretFetcher(ctx context.Context) metrics.SecretFetcher { - // NOTE: Do not use secrets.Get(ctx) here to get a SecretLister, as it will register - // a *global* SecretInformer and require cluster-level `secrets.list` permission, - // even if you scope down the Lister to a given namespace after requesting it. Instead, - // we package up a function from kubeclient. - // TODO(evankanderson): If this direct request to the apiserver on each TLS connection - // to the opencensus agent is too much load, switch to a cached Secret. - return func(name string) (*corev1.Secret, error) { - return kubeclient.Get(ctx).CoreV1().Secrets(system.Namespace()).Get(ctx, name, metav1.GetOptions{}) + if _, err := client.Get(ctx, cmName, metav1.GetOptions{}); err == nil { + cmw.Watch(cmName, observers...) + } else if !apierrors.IsNotFound(err) { + logger.Fatalw("Error reading ConfigMap "+cmName, zap.Error(err)) } } @@ -456,13 +491,13 @@ func SecretFetcher(ctx context.Context) metrics.SecretFetcher { func ControllersAndWebhooksFromCtors(ctx context.Context, cmw *cminformer.InformedWatcher, ctors ...injection.ControllerConstructor, -) ([]*controller.Impl, []interface{}) { +) ([]*controller.Impl, []any) { // Check whether the context has been infused with a leader elector builder. // If it has, then every reconciler we plan to start MUST implement LeaderAware. leEnabled := leaderelection.HasLeaderElection(ctx) controllers := make([]*controller.Impl, 0, len(ctors)) - webhooks := make([]interface{}, 0) + webhooks := make([]any, 0) for _, cf := range ctors { ctrl := cf(ctx, cmw) controllers = append(controllers, ctrl) diff --git a/injection/sharedmain/main_test.go b/injection/sharedmain/main_test.go index bc9b54ce66..4e341a4875 100644 --- a/injection/sharedmain/main_test.go +++ b/injection/sharedmain/main_test.go @@ -26,7 +26,7 @@ import ( "knative.dev/pkg/injection" "knative.dev/pkg/leaderelection" "knative.dev/pkg/logging" - "knative.dev/pkg/metrics" + "knative.dev/pkg/observability" ) func TestEnabledControllers(t *testing.T) { @@ -106,17 +106,12 @@ func TestWithLeaderElectionConfig(t *testing.T) { } func TestWithObservabilityConfig(t *testing.T) { - want := &metrics.ObservabilityConfig{ - EnableVarLogCollection: false, - LoggingURLTemplate: "url-template", - RequestLogTemplate: "log-template", - EnableProbeRequestLog: true, - RequestMetricsBackend: "prometheus", - EnableProfiling: true, - EnableRequestLog: false, - MetricsCollectorAddress: "localhost:9090", + want := &observability.Config{ + Tracing: observability.TracingConfig{ + Protocol: "some-protocol", + }, } - ctx := metrics.WithConfig(context.Background(), want) + ctx := observability.WithConfig(context.Background(), want) got, err := GetObservabilityConfig(ctx) if err != nil { diff --git a/observability/metrics/globalviews/views.go b/injection/sharedmain/options.go similarity index 57% rename from observability/metrics/globalviews/views.go rename to injection/sharedmain/options.go index f2d3f60616..2895544ee0 100644 --- a/observability/metrics/globalviews/views.go +++ b/injection/sharedmain/options.go @@ -14,28 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -package globalviews +package sharedmain import ( - "maps" - "slices" + "context" "go.opentelemetry.io/otel/sdk/metric" ) -var globalViews map[string][]metric.View = make(map[string][]metric.View) +type otelviews struct{} -// Register should be called during package init -func Register(pkg string, v ...metric.View) { - views := globalViews[pkg] - globalViews[pkg] = append(views, v...) +func WithOTelViews(ctx context.Context, views ...metric.View) context.Context { + return context.WithValue(ctx, otelviews{}, views) } -func GetPackageViews(pkg string) []metric.View { - return globalViews[pkg] -} - -func GetAllViews() []metric.View { - list := slices.Collect(maps.Values(globalViews)) - return slices.Concat(list...) +func OTelViews(ctx context.Context) []metric.View { + return ctx.Value(otelviews{}).([]metric.View) } diff --git a/injection/sharedmain/pprof.go b/injection/sharedmain/pprof.go new file mode 100644 index 0000000000..cfb13ea192 --- /dev/null +++ b/injection/sharedmain/pprof.go @@ -0,0 +1,53 @@ +/* +Copyright 2025 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sharedmain + +import ( + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + "knative.dev/pkg/observability/runtime" +) + +type pprofServer struct { + *runtime.ProfilingServer + log *zap.SugaredLogger +} + +func newProfilingServer(logger *zap.SugaredLogger) *pprofServer { + s := runtime.NewProfilingServer() + + return &pprofServer{ + ProfilingServer: s, + log: logger, + } +} + +func (s *pprofServer) UpdateFromConfigMap(cm *corev1.ConfigMap) { + cfg, err := runtime.NewFromMap(cm.Data) + if err != nil { + s.log.Errorw("Failed to update the profiling flag", zap.Error(err)) + return + } + s.UpdateFromConfig(cfg) +} + +func (s *pprofServer) UpdateFromConfig(cfg runtime.Config) { + enabled := cfg.ProfilingEnabled() + if s.ProfilingServer.SetEnabled(enabled) != enabled { + s.log.Info("Profiling enabled: ", enabled) + } +} diff --git a/observability/metrics/globalviews/doc.go b/observability/metrics/globalviews/doc.go deleted file mode 100644 index 0a010c3a66..0000000000 --- a/observability/metrics/globalviews/doc.go +++ /dev/null @@ -1,33 +0,0 @@ -/* -Copyright 2025 The Knative Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// OpenTelemetry views allow you to override the default behaviour -// of the SDK. It defines how data should be collected for certain -// instruments. -// -// OpenCensus allowed views to be registered globally to simplify -// setup. In contrast, OpenTelemetry requires views to be provided -// when creating a [MeterProvider] using the [WithView] option. This -// was done to provide flexibility of having multiple metrics pipelines. -// -// To provide a similar UX experience the globalviews provides a similar -// way to register OTel views globally. This allows Knative maintainers -// to author packages with metrics and include views that can be consumed -// by packages such as sharedmain. -// -// [MeterProvider]: https://pkg.go.dev/go.opentelemetry.io/otel/sdk/metric#NewMeterProvider -// [WithView]: https://pkg.go.dev/go.opentelemetry.io/otel/sdk/metric#WithView -package globalviews diff --git a/observability/metrics/globalviews/views_test.go b/observability/metrics/globalviews/views_test.go deleted file mode 100644 index c9cbc4ecb7..0000000000 --- a/observability/metrics/globalviews/views_test.go +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright 2025 The Knative Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package globalviews_test - -import ( - "testing" - - "go.opentelemetry.io/otel/sdk/instrumentation" - "go.opentelemetry.io/otel/sdk/metric" - "knative.dev/pkg/observability/metrics/globalviews" -) - -var testView = metric.NewView( - metric.Instrument{ - Name: "latency", - Scope: instrumentation.Scope{Name: "http"}, - }, - metric.Stream{ - Aggregation: metric.AggregationBase2ExponentialHistogram{ - MaxSize: 160, - MaxScale: 20, - }, - }, -) - -var secondView = metric.NewView( - metric.Instrument{ - Name: "latency", - Scope: instrumentation.Scope{Name: "http"}, - }, - metric.Stream{ - Aggregation: metric.AggregationBase2ExponentialHistogram{ - MaxSize: 160, - MaxScale: 20, - }, - }, -) - -func TestRegistration(t *testing.T) { - testPackage := "com.example.package" - - if len(globalviews.GetAllViews()) != 0 { - t.Fatal("expected zero views") - } - - globalviews.Register(testPackage, testView) - - if count := len(globalviews.GetAllViews()); count != 1 { - t.Fatalf("expected global view count to be 1 got %d", count) - } - - if count := len(globalviews.GetPackageViews(testPackage)); count != 1 { - t.Fatalf("expected a single view for %q got %d", testPackage, count) - } - - if count := len(globalviews.GetPackageViews("com.example.second.package")); count != 0 { - t.Fatalf("expected no views for 'second' package got %d", count) - } - - globalviews.Register(testPackage, secondView) - - if count := len(globalviews.GetAllViews()); count != 2 { - t.Fatalf("expected global view count to be 2 got %d", count) - } - - if count := len(globalviews.GetPackageViews(testPackage)); count != 2 { - t.Fatalf("expected two views for %q got %d", testPackage, count) - } - - if count := len(globalviews.GetPackageViews("com.example.second.package")); count != 0 { - t.Fatalf("expected no views for 'second' package got %d", count) - } -} diff --git a/observability/resource/default.go b/observability/resource/default.go new file mode 100644 index 0000000000..bbdc1d940d --- /dev/null +++ b/observability/resource/default.go @@ -0,0 +1,61 @@ +/* +Copyright 2025 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "knative.dev/pkg/changeset" + "knative.dev/pkg/system" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.34.0" +) + +// Default returns a default OpenTelemetry Resource enriched +// with common Knative/Kubernetes attributes. +// +// It will populate: +// - Namespace using system.Namespace +// - PodName using system.PodName +// - ServiceVersion with changeset.Get +func Default(serviceName string) *resource.Resource { + attrs := []attribute.KeyValue{ + semconv.K8SNamespaceName(system.Namespace()), + semconv.ServiceName(serviceName), + semconv.ServiceVersion(changeset.Get()), + } + + if pn := system.PodName(); pn != "" { + attrs = append(attrs, semconv.K8SPodName(pn)) + } + + // Ignore the error because it complains about semconv + // schema version differences + resource, err := resource.Merge( + resource.NewWithAttributes( + semconv.SchemaURL, + attrs..., + ), + // We merge 'Default' last since this allows overriding + // the service name etc. using env variables + resource.Default(), + ) + if err != nil { + panic(err) + } + return resource +} diff --git a/observability/resource/default_test.go b/observability/resource/default_test.go new file mode 100644 index 0000000000..a4277518fb --- /dev/null +++ b/observability/resource/default_test.go @@ -0,0 +1,31 @@ +/* +Copyright 2025 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "testing" + + _ "knative.dev/pkg/system/testing" +) + +func TestDefault(t *testing.T) { + r := Default("myservice") + + if r == nil { + t.Fatalf("expected resource to not be nil") + } +} diff --git a/system/env.go b/system/env.go index ea1db0f0be..c82cef6b03 100644 --- a/system/env.go +++ b/system/env.go @@ -17,6 +17,7 @@ limitations under the License. package system import ( + "cmp" "fmt" "os" ) @@ -24,8 +25,10 @@ import ( const ( // NamespaceEnvKey is the environment variable that specifies the system namespace. NamespaceEnvKey = "SYSTEM_NAMESPACE" + // ResourceLabelEnvKey is the environment variable that specifies the system resource - // label. + // label. This label should be used to limit the number of configmaps that are watched + // in the system namespace. ResourceLabelEnvKey = "SYSTEM_RESOURCE_LABEL" ) @@ -60,3 +63,28 @@ import ( func ResourceLabel() string { return os.Getenv(ResourceLabelEnvKey) } + +// PodName will read various env vars to determine the name of the running +// pod before falling back +// +// First it will read 'POD_NAME' this is expected to be populated using the +// Kubernetes downward API. +// +// env: +// - name: MY_POD_NAME +// valueFrom: +// fieldRef: +// fieldPath: metadata.name +// +// As a fallback it will read HOSTNAME. This is undocumented +// Kubernetes behaviour that podman, cri-o and containerd have +// inherited from docker. +// +// If none of these env-vars is set PodName will return an +// empty string +func PodName() string { + return cmp.Or( + os.Getenv("POD_NAME"), + os.Getenv("HOSTNAME"), + ) +} diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/LICENSE b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/doc.go b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/doc.go new file mode 100644 index 0000000000..fabf952c46 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/doc.go @@ -0,0 +1,34 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package runtime implements the conventional runtime metrics specified by OpenTelemetry. +// +// The metric events 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. +// +// When the OTEL_GO_X_DEPRECATED_RUNTIME_METRICS environment variable is set to +// true, the following deprecated metrics are produced: +// +// 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 runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime/doc.go b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime/doc.go new file mode 100644 index 0000000000..9fb44efa8d --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/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/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime/runtime.go b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime/runtime.go new file mode 100644 index 0000000000..86c7c9e34a --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime/runtime.go @@ -0,0 +1,296 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package deprecatedruntime // import "go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime" + +import ( + "context" + "math" + 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, clampUint64(memStats.HeapAlloc)) + o.ObserveInt64(heapIdle, clampUint64(memStats.HeapIdle)) + o.ObserveInt64(heapInuse, clampUint64(memStats.HeapInuse)) + o.ObserveInt64(heapObjects, clampUint64(memStats.HeapObjects)) + o.ObserveInt64(heapReleased, clampUint64(memStats.HeapReleased)) + o.ObserveInt64(heapSys, clampUint64(memStats.HeapSys)) + o.ObserveInt64(liveObjects, clampUint64(memStats.Mallocs-memStats.Frees)) + o.ObserveInt64(ptrLookups, clampUint64(memStats.Lookups)) + o.ObserveInt64(gcCount, int64(memStats.NumGC)) + o.ObserveInt64(pauseTotalNs, clampUint64(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 clampUint64(v uint64) int64 { + if v > math.MaxInt64 { + return math.MaxInt64 + } + return int64(v) // nolint: gosec // Overflow checked above. +} + +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 + } + + n := len(circular) + if n < 0 { + // Only the case in error situations. + return + } + + length := uint64(n) // nolint: gosec // n >= 0 + + i := uint64(lastNumGC) % length + j := uint64(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, clampUint64(pause)) + } +} diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/internal/x/README.md b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/internal/x/README.md new file mode 100644 index 0000000000..00170e1a68 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/internal/x/README.md @@ -0,0 +1,29 @@ +# 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 + +To temporarily re-enable the deprecated metrics: + +```console +export OTEL_GO_X_DEPRECATED_RUNTIME_METRICS=true +``` + +Eventually, 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]: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/runtime@v0.52.0 +[OpenTelemetry Go Runtime conventions]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/runtime/go-metrics.md diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/internal/x/x.go b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/internal/x/x.go new file mode 100644 index 0000000000..95a05d5993 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/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 instrumentation 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" + "strconv" +) + +// 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 enable this feature set the OTEL_GO_X_DEPRECATED_RUNTIME_METRICS environment variable +// to the case-insensitive string value of "true" (i.e. "True" and "TRUE" +// will also enable this). +var DeprecatedRuntimeMetrics = newFeature("DEPRECATED_RUNTIME_METRICS", false) + +// 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) + + val, err := strconv.ParseBool(v) + if err != nil { + return f.defaultVal + } + + return val +} diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/options.go b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/options.go new file mode 100644 index 0000000000..580dc5dcd5 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/options.go @@ -0,0 +1,99 @@ +// 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) +} + +// ProducerOption supports configuring optional settings for runtime metrics using a +// metric producer in addition to standard instrumentation. +type ProducerOption interface { + Option + applyProducer(*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) + } +} + +func (o minimumReadMemStatsIntervalOption) applyProducer(c *config) { o.apply(c) } + +// 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(), + } + for _, opt := range opts { + opt.apply(&c) + } + if c.MinimumReadMemStatsInterval <= 0 { + c.MinimumReadMemStatsInterval = DefaultMinimumReadMemStatsInterval + } + return c +} + +// newConfig computes a config from the supplied ProducerOptions. +func newProducerConfig(opts ...ProducerOption) config { + c := config{} + for _, opt := range opts { + opt.applyProducer(&c) + } + if c.MinimumReadMemStatsInterval <= 0 { + c.MinimumReadMemStatsInterval = DefaultMinimumReadMemStatsInterval + } + return c +} diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/producer.go b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/producer.go new file mode 100644 index 0000000000..82426b3f2a --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/producer.go @@ -0,0 +1,120 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" + +import ( + "context" + "errors" + "math" + "runtime/metrics" + "sync" + "time" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/instrumentation" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" +) + +var startTime time.Time + +func init() { + startTime = time.Now() +} + +var histogramMetrics = []string{goSchedLatencies} + +// Producer is a metric.Producer, which provides precomputed histogram metrics from the go runtime. +type Producer struct { + lock sync.Mutex + collector *goCollector +} + +var _ metric.Producer = (*Producer)(nil) + +// NewProducer creates a Producer which provides precomputed histogram metrics from the go runtime. +func NewProducer(opts ...ProducerOption) *Producer { + c := newProducerConfig(opts...) + return &Producer{ + collector: newCollector(c.MinimumReadMemStatsInterval, histogramMetrics), + } +} + +// Produce returns precomputed histogram metrics from the go runtime, or an error if unsuccessful. +func (p *Producer) Produce(context.Context) ([]metricdata.ScopeMetrics, error) { + p.lock.Lock() + p.collector.refresh() + schedHist := p.collector.getHistogram(goSchedLatencies) + p.lock.Unlock() + // Use the last collection time (which may or may not be now) for the timestamp. + histDp := convertRuntimeHistogram(schedHist, p.collector.lastCollect) + if len(histDp) == 0 { + return nil, errors.New("unable to obtain go.schedule.duration metric from the runtime") + } + return []metricdata.ScopeMetrics{ + { + Scope: instrumentation.Scope{ + Name: ScopeName, + Version: Version(), + }, + Metrics: []metricdata.Metrics{ + { + Name: "go.schedule.duration", + Description: "The time goroutines have spent in the scheduler in a runnable state before actually running.", + Unit: "s", + Data: metricdata.Histogram[float64]{ + Temporality: metricdata.CumulativeTemporality, + DataPoints: histDp, + }, + }, + }, + }, + }, nil +} + +var emptySet = attribute.EmptySet() + +func convertRuntimeHistogram(runtimeHist *metrics.Float64Histogram, ts time.Time) []metricdata.HistogramDataPoint[float64] { + if runtimeHist == nil { + return nil + } + bounds := runtimeHist.Buckets + counts := runtimeHist.Counts + if len(bounds) < 2 { + // runtime histograms are guaranteed to have at least two bucket boundaries. + return nil + } + // trim the first bucket since it is a lower bound. OTel histogram boundaries only have an upper bound. + bounds = bounds[1:] + if bounds[len(bounds)-1] == math.Inf(1) { + // trim the last bucket if it is +Inf, since the +Inf boundary is implicit in OTel. + bounds = bounds[:len(bounds)-1] + } else { + // if the last bucket is not +Inf, append an extra zero count since + // the implicit +Inf bucket won't have any observations. + counts = append(counts, 0) + } + count := uint64(0) + sum := float64(0) + for i, c := range counts { + count += c + // This computed sum is an underestimate, since it assumes each + // observation happens at the bucket's lower bound. + if i > 0 && count != 0 { + sum += bounds[i-1] * float64(count) + } + } + + return []metricdata.HistogramDataPoint[float64]{ + { + StartTime: startTime, + Count: count, + Sum: sum, + Time: ts, + Bounds: bounds, + BucketCounts: counts, + Attributes: *emptySet, + }, + } +} diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/runtime.go b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/runtime.go new file mode 100644 index 0000000000..fec833b573 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/runtime.go @@ -0,0 +1,200 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" + +import ( + "context" + "math" + "runtime/metrics" + "sync" + "time" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/semconv/v1.34.0/goconv" + + "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" + +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" + goSchedLatencies = "/sched/latencies:seconds" +) + +// Start initializes reporting of runtime metrics using the supplied config. +// For goroutine scheduling metrics, additionally see [NewProducer]. +func Start(opts ...Option) error { + c := newConfig(opts...) + meter := c.MeterProvider.Meter( + ScopeName, + metric.WithInstrumentationVersion(Version()), + ) + if x.DeprecatedRuntimeMetrics.Enabled() { + if err := deprecatedruntime.Start(meter, c.MinimumReadMemStatsInterval); err != nil { + return err + } + } + memoryUsed, err := goconv.NewMemoryUsed(meter) + if err != nil { + return err + } + memoryLimit, err := goconv.NewMemoryLimit(meter) + if err != nil { + return err + } + memoryAllocated, err := goconv.NewMemoryAllocated(meter) + if err != nil { + return err + } + memoryAllocations, err := goconv.NewMemoryAllocations(meter) + if err != nil { + return err + } + memoryGCGoal, err := goconv.NewMemoryGCGoal(meter) + if err != nil { + return err + } + goroutineCount, err := goconv.NewGoroutineCount(meter) + if err != nil { + return err + } + processorLimit, err := goconv.NewProcessorLimit(meter) + if err != nil { + return err + } + configGogc, err := goconv.NewConfigGogc(meter) + if err != nil { + return err + } + + otherMemoryOpt := metric.WithAttributeSet( + attribute.NewSet(memoryUsed.AttrMemoryType(goconv.MemoryTypeOther)), + ) + stackMemoryOpt := metric.WithAttributeSet( + attribute.NewSet(memoryUsed.AttrMemoryType(goconv.MemoryTypeStack)), + ) + collector := newCollector(c.MinimumReadMemStatsInterval, runtimeMetrics) + var lock sync.Mutex + _, err = meter.RegisterCallback( + func(ctx context.Context, o metric.Observer) error { + lock.Lock() + defer lock.Unlock() + collector.refresh() + stackMemory := collector.getInt(goHeapMemory) + o.ObserveInt64(memoryUsed.Inst(), stackMemory, stackMemoryOpt) + totalMemory := collector.getInt(goTotalMemory) - collector.getInt(goMemoryReleased) + otherMemory := totalMemory - stackMemory + o.ObserveInt64(memoryUsed.Inst(), otherMemory, otherMemoryOpt) + // Only observe the limit metric if a limit exists + if limit := collector.getInt(goMemoryLimit); limit != math.MaxInt64 { + o.ObserveInt64(memoryLimit.Inst(), limit) + } + o.ObserveInt64(memoryAllocated.Inst(), collector.getInt(goMemoryAllocated)) + o.ObserveInt64(memoryAllocations.Inst(), collector.getInt(goMemoryAllocations)) + o.ObserveInt64(memoryGCGoal.Inst(), collector.getInt(goMemoryGoal)) + o.ObserveInt64(goroutineCount.Inst(), collector.getInt(goGoroutines)) + o.ObserveInt64(processorLimit.Inst(), collector.getInt(goMaxProcs)) + o.ObserveInt64(configGogc.Inst(), collector.getInt(goConfigGC)) + return nil + }, + memoryUsed.Inst(), + memoryLimit.Inst(), + memoryAllocated.Inst(), + memoryAllocations.Inst(), + memoryGCGoal.Inst(), + goroutineCount.Inst(), + processorLimit.Inst(), + configGogc.Inst(), + ) + if err != nil { + return err + } + 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, metricNames []string) *goCollector { + g := &goCollector{ + sampleBuffer: make([]metrics.Sample, 0, len(metricNames)), + sampleMap: make(map[string]*metrics.Sample, len(metricNames)), + minimumInterval: minimumInterval, + now: time.Now, + } + for _, metricName := range metricNames { + g.sampleBuffer = append(g.sampleBuffer, metrics.Sample{Name: metricName}) + // 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[metricName] = &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) getInt(name string) int64 { + if s, ok := g.sampleMap[name]; ok && s.Value.Kind() == metrics.KindUint64 { + v := s.Value.Uint64() + if v > math.MaxInt64 { + return math.MaxInt64 + } + return int64(v) // nolint: gosec // Overflow checked above. + } + return 0 +} + +func (g *goCollector) getHistogram(name string) *metrics.Float64Histogram { + if s, ok := g.sampleMap[name]; ok && s.Value.Kind() == metrics.KindFloat64Histogram { + return s.Value.Float64Histogram() + } + return nil +} diff --git a/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/version.go b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/version.go new file mode 100644 index 0000000000..2d1da25495 --- /dev/null +++ b/vendor/go.opentelemetry.io/contrib/instrumentation/runtime/version.go @@ -0,0 +1,10 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package runtime // import "go.opentelemetry.io/contrib/instrumentation/runtime" + +// Version is the current release version of the runtime instrumentation. +func Version() string { + return "0.62.0" + // This string is updated by the pre_release.sh script during release +} diff --git a/vendor/go.opentelemetry.io/otel/semconv/v1.34.0/goconv/metric.go b/vendor/go.opentelemetry.io/otel/semconv/v1.34.0/goconv/metric.go new file mode 100644 index 0000000000..564ff88378 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/semconv/v1.34.0/goconv/metric.go @@ -0,0 +1,508 @@ +// Code generated from semantic convention specification. DO NOT EDIT. + +// Package httpconv provides types and functionality for OpenTelemetry semantic +// conventions in the "go" namespace. +package goconv + +import ( + "context" + "sync" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/noop" +) + +var ( + addOptPool = &sync.Pool{New: func() any { return &[]metric.AddOption{} }} + recOptPool = &sync.Pool{New: func() any { return &[]metric.RecordOption{} }} +) + +// MemoryTypeAttr is an attribute conforming to the go.memory.type semantic +// conventions. It represents the type of memory. +type MemoryTypeAttr string + +var ( + // MemoryTypeStack is the memory allocated from the heap that is reserved for + // stack space, whether or not it is currently in-use. + MemoryTypeStack MemoryTypeAttr = "stack" + // MemoryTypeOther is the memory used by the Go runtime, excluding other + // categories of memory usage described in this enumeration. + MemoryTypeOther MemoryTypeAttr = "other" +) + +// ConfigGogc is an instrument used to record metric values conforming to the +// "go.config.gogc" semantic conventions. It represents the heap size target +// percentage configured by the user, otherwise 100. +type ConfigGogc struct { + metric.Int64ObservableUpDownCounter +} + +// NewConfigGogc returns a new ConfigGogc instrument. +func NewConfigGogc( + m metric.Meter, + opt ...metric.Int64ObservableUpDownCounterOption, +) (ConfigGogc, error) { + // Check if the meter is nil. + if m == nil { + return ConfigGogc{noop.Int64ObservableUpDownCounter{}}, nil + } + + i, err := m.Int64ObservableUpDownCounter( + "go.config.gogc", + append([]metric.Int64ObservableUpDownCounterOption{ + metric.WithDescription("Heap size target percentage configured by the user, otherwise 100."), + metric.WithUnit("%"), + }, opt...)..., + ) + if err != nil { + return ConfigGogc{noop.Int64ObservableUpDownCounter{}}, err + } + return ConfigGogc{i}, nil +} + +// Inst returns the underlying metric instrument. +func (m ConfigGogc) Inst() metric.Int64ObservableUpDownCounter { + return m.Int64ObservableUpDownCounter +} + +// Name returns the semantic convention name of the instrument. +func (ConfigGogc) Name() string { + return "go.config.gogc" +} + +// Unit returns the semantic convention unit of the instrument +func (ConfigGogc) Unit() string { + return "%" +} + +// Description returns the semantic convention description of the instrument +func (ConfigGogc) Description() string { + return "Heap size target percentage configured by the user, otherwise 100." +} + +// GoroutineCount is an instrument used to record metric values conforming to the +// "go.goroutine.count" semantic conventions. It represents the count of live +// goroutines. +type GoroutineCount struct { + metric.Int64ObservableUpDownCounter +} + +// NewGoroutineCount returns a new GoroutineCount instrument. +func NewGoroutineCount( + m metric.Meter, + opt ...metric.Int64ObservableUpDownCounterOption, +) (GoroutineCount, error) { + // Check if the meter is nil. + if m == nil { + return GoroutineCount{noop.Int64ObservableUpDownCounter{}}, nil + } + + i, err := m.Int64ObservableUpDownCounter( + "go.goroutine.count", + append([]metric.Int64ObservableUpDownCounterOption{ + metric.WithDescription("Count of live goroutines."), + metric.WithUnit("{goroutine}"), + }, opt...)..., + ) + if err != nil { + return GoroutineCount{noop.Int64ObservableUpDownCounter{}}, err + } + return GoroutineCount{i}, nil +} + +// Inst returns the underlying metric instrument. +func (m GoroutineCount) Inst() metric.Int64ObservableUpDownCounter { + return m.Int64ObservableUpDownCounter +} + +// Name returns the semantic convention name of the instrument. +func (GoroutineCount) Name() string { + return "go.goroutine.count" +} + +// Unit returns the semantic convention unit of the instrument +func (GoroutineCount) Unit() string { + return "{goroutine}" +} + +// Description returns the semantic convention description of the instrument +func (GoroutineCount) Description() string { + return "Count of live goroutines." +} + +// MemoryAllocated is an instrument used to record metric values conforming to +// the "go.memory.allocated" semantic conventions. It represents the memory +// allocated to the heap by the application. +type MemoryAllocated struct { + metric.Int64ObservableCounter +} + +// NewMemoryAllocated returns a new MemoryAllocated instrument. +func NewMemoryAllocated( + m metric.Meter, + opt ...metric.Int64ObservableCounterOption, +) (MemoryAllocated, error) { + // Check if the meter is nil. + if m == nil { + return MemoryAllocated{noop.Int64ObservableCounter{}}, nil + } + + i, err := m.Int64ObservableCounter( + "go.memory.allocated", + append([]metric.Int64ObservableCounterOption{ + metric.WithDescription("Memory allocated to the heap by the application."), + metric.WithUnit("By"), + }, opt...)..., + ) + if err != nil { + return MemoryAllocated{noop.Int64ObservableCounter{}}, err + } + return MemoryAllocated{i}, nil +} + +// Inst returns the underlying metric instrument. +func (m MemoryAllocated) Inst() metric.Int64ObservableCounter { + return m.Int64ObservableCounter +} + +// Name returns the semantic convention name of the instrument. +func (MemoryAllocated) Name() string { + return "go.memory.allocated" +} + +// Unit returns the semantic convention unit of the instrument +func (MemoryAllocated) Unit() string { + return "By" +} + +// Description returns the semantic convention description of the instrument +func (MemoryAllocated) Description() string { + return "Memory allocated to the heap by the application." +} + +// MemoryAllocations is an instrument used to record metric values conforming to +// the "go.memory.allocations" semantic conventions. It represents the count of +// allocations to the heap by the application. +type MemoryAllocations struct { + metric.Int64ObservableCounter +} + +// NewMemoryAllocations returns a new MemoryAllocations instrument. +func NewMemoryAllocations( + m metric.Meter, + opt ...metric.Int64ObservableCounterOption, +) (MemoryAllocations, error) { + // Check if the meter is nil. + if m == nil { + return MemoryAllocations{noop.Int64ObservableCounter{}}, nil + } + + i, err := m.Int64ObservableCounter( + "go.memory.allocations", + append([]metric.Int64ObservableCounterOption{ + metric.WithDescription("Count of allocations to the heap by the application."), + metric.WithUnit("{allocation}"), + }, opt...)..., + ) + if err != nil { + return MemoryAllocations{noop.Int64ObservableCounter{}}, err + } + return MemoryAllocations{i}, nil +} + +// Inst returns the underlying metric instrument. +func (m MemoryAllocations) Inst() metric.Int64ObservableCounter { + return m.Int64ObservableCounter +} + +// Name returns the semantic convention name of the instrument. +func (MemoryAllocations) Name() string { + return "go.memory.allocations" +} + +// Unit returns the semantic convention unit of the instrument +func (MemoryAllocations) Unit() string { + return "{allocation}" +} + +// Description returns the semantic convention description of the instrument +func (MemoryAllocations) Description() string { + return "Count of allocations to the heap by the application." +} + +// MemoryGCGoal is an instrument used to record metric values conforming to the +// "go.memory.gc.goal" semantic conventions. It represents the heap size target +// for the end of the GC cycle. +type MemoryGCGoal struct { + metric.Int64ObservableUpDownCounter +} + +// NewMemoryGCGoal returns a new MemoryGCGoal instrument. +func NewMemoryGCGoal( + m metric.Meter, + opt ...metric.Int64ObservableUpDownCounterOption, +) (MemoryGCGoal, error) { + // Check if the meter is nil. + if m == nil { + return MemoryGCGoal{noop.Int64ObservableUpDownCounter{}}, nil + } + + i, err := m.Int64ObservableUpDownCounter( + "go.memory.gc.goal", + append([]metric.Int64ObservableUpDownCounterOption{ + metric.WithDescription("Heap size target for the end of the GC cycle."), + metric.WithUnit("By"), + }, opt...)..., + ) + if err != nil { + return MemoryGCGoal{noop.Int64ObservableUpDownCounter{}}, err + } + return MemoryGCGoal{i}, nil +} + +// Inst returns the underlying metric instrument. +func (m MemoryGCGoal) Inst() metric.Int64ObservableUpDownCounter { + return m.Int64ObservableUpDownCounter +} + +// Name returns the semantic convention name of the instrument. +func (MemoryGCGoal) Name() string { + return "go.memory.gc.goal" +} + +// Unit returns the semantic convention unit of the instrument +func (MemoryGCGoal) Unit() string { + return "By" +} + +// Description returns the semantic convention description of the instrument +func (MemoryGCGoal) Description() string { + return "Heap size target for the end of the GC cycle." +} + +// MemoryLimit is an instrument used to record metric values conforming to the +// "go.memory.limit" semantic conventions. It represents the go runtime memory +// limit configured by the user, if a limit exists. +type MemoryLimit struct { + metric.Int64ObservableUpDownCounter +} + +// NewMemoryLimit returns a new MemoryLimit instrument. +func NewMemoryLimit( + m metric.Meter, + opt ...metric.Int64ObservableUpDownCounterOption, +) (MemoryLimit, error) { + // Check if the meter is nil. + if m == nil { + return MemoryLimit{noop.Int64ObservableUpDownCounter{}}, nil + } + + i, err := m.Int64ObservableUpDownCounter( + "go.memory.limit", + append([]metric.Int64ObservableUpDownCounterOption{ + metric.WithDescription("Go runtime memory limit configured by the user, if a limit exists."), + metric.WithUnit("By"), + }, opt...)..., + ) + if err != nil { + return MemoryLimit{noop.Int64ObservableUpDownCounter{}}, err + } + return MemoryLimit{i}, nil +} + +// Inst returns the underlying metric instrument. +func (m MemoryLimit) Inst() metric.Int64ObservableUpDownCounter { + return m.Int64ObservableUpDownCounter +} + +// Name returns the semantic convention name of the instrument. +func (MemoryLimit) Name() string { + return "go.memory.limit" +} + +// Unit returns the semantic convention unit of the instrument +func (MemoryLimit) Unit() string { + return "By" +} + +// Description returns the semantic convention description of the instrument +func (MemoryLimit) Description() string { + return "Go runtime memory limit configured by the user, if a limit exists." +} + +// MemoryUsed is an instrument used to record metric values conforming to the +// "go.memory.used" semantic conventions. It represents the memory used by the Go +// runtime. +type MemoryUsed struct { + metric.Int64ObservableUpDownCounter +} + +// NewMemoryUsed returns a new MemoryUsed instrument. +func NewMemoryUsed( + m metric.Meter, + opt ...metric.Int64ObservableUpDownCounterOption, +) (MemoryUsed, error) { + // Check if the meter is nil. + if m == nil { + return MemoryUsed{noop.Int64ObservableUpDownCounter{}}, nil + } + + i, err := m.Int64ObservableUpDownCounter( + "go.memory.used", + append([]metric.Int64ObservableUpDownCounterOption{ + metric.WithDescription("Memory used by the Go runtime."), + metric.WithUnit("By"), + }, opt...)..., + ) + if err != nil { + return MemoryUsed{noop.Int64ObservableUpDownCounter{}}, err + } + return MemoryUsed{i}, nil +} + +// Inst returns the underlying metric instrument. +func (m MemoryUsed) Inst() metric.Int64ObservableUpDownCounter { + return m.Int64ObservableUpDownCounter +} + +// Name returns the semantic convention name of the instrument. +func (MemoryUsed) Name() string { + return "go.memory.used" +} + +// Unit returns the semantic convention unit of the instrument +func (MemoryUsed) Unit() string { + return "By" +} + +// Description returns the semantic convention description of the instrument +func (MemoryUsed) Description() string { + return "Memory used by the Go runtime." +} + +// AttrMemoryType returns an optional attribute for the "go.memory.type" semantic +// convention. It represents the type of memory. +func (MemoryUsed) AttrMemoryType(val MemoryTypeAttr) attribute.KeyValue { + return attribute.String("go.memory.type", string(val)) +} + +// ProcessorLimit is an instrument used to record metric values conforming to the +// "go.processor.limit" semantic conventions. It represents the number of OS +// threads that can execute user-level Go code simultaneously. +type ProcessorLimit struct { + metric.Int64ObservableUpDownCounter +} + +// NewProcessorLimit returns a new ProcessorLimit instrument. +func NewProcessorLimit( + m metric.Meter, + opt ...metric.Int64ObservableUpDownCounterOption, +) (ProcessorLimit, error) { + // Check if the meter is nil. + if m == nil { + return ProcessorLimit{noop.Int64ObservableUpDownCounter{}}, nil + } + + i, err := m.Int64ObservableUpDownCounter( + "go.processor.limit", + append([]metric.Int64ObservableUpDownCounterOption{ + metric.WithDescription("The number of OS threads that can execute user-level Go code simultaneously."), + metric.WithUnit("{thread}"), + }, opt...)..., + ) + if err != nil { + return ProcessorLimit{noop.Int64ObservableUpDownCounter{}}, err + } + return ProcessorLimit{i}, nil +} + +// Inst returns the underlying metric instrument. +func (m ProcessorLimit) Inst() metric.Int64ObservableUpDownCounter { + return m.Int64ObservableUpDownCounter +} + +// Name returns the semantic convention name of the instrument. +func (ProcessorLimit) Name() string { + return "go.processor.limit" +} + +// Unit returns the semantic convention unit of the instrument +func (ProcessorLimit) Unit() string { + return "{thread}" +} + +// Description returns the semantic convention description of the instrument +func (ProcessorLimit) Description() string { + return "The number of OS threads that can execute user-level Go code simultaneously." +} + +// ScheduleDuration is an instrument used to record metric values conforming to +// the "go.schedule.duration" semantic conventions. It represents the time +// goroutines have spent in the scheduler in a runnable state before actually +// running. +type ScheduleDuration struct { + metric.Float64Histogram +} + +// NewScheduleDuration returns a new ScheduleDuration instrument. +func NewScheduleDuration( + m metric.Meter, + opt ...metric.Float64HistogramOption, +) (ScheduleDuration, error) { + // Check if the meter is nil. + if m == nil { + return ScheduleDuration{noop.Float64Histogram{}}, nil + } + + i, err := m.Float64Histogram( + "go.schedule.duration", + append([]metric.Float64HistogramOption{ + metric.WithDescription("The time goroutines have spent in the scheduler in a runnable state before actually running."), + metric.WithUnit("s"), + }, opt...)..., + ) + if err != nil { + return ScheduleDuration{noop.Float64Histogram{}}, err + } + return ScheduleDuration{i}, nil +} + +// Inst returns the underlying metric instrument. +func (m ScheduleDuration) Inst() metric.Float64Histogram { + return m.Float64Histogram +} + +// Name returns the semantic convention name of the instrument. +func (ScheduleDuration) Name() string { + return "go.schedule.duration" +} + +// Unit returns the semantic convention unit of the instrument +func (ScheduleDuration) Unit() string { + return "s" +} + +// Description returns the semantic convention description of the instrument +func (ScheduleDuration) Description() string { + return "The time goroutines have spent in the scheduler in a runnable state before actually running." +} + +// Record records val to the current distribution. +// +// Computed from `/sched/latencies:seconds`. Bucket boundaries are provided by +// the runtime, and are subject to change. +func (m ScheduleDuration) Record(ctx context.Context, val float64, attrs ...attribute.KeyValue) { + if len(attrs) == 0 { + m.Float64Histogram.Record(ctx, val) + } + + o := recOptPool.Get().(*[]metric.RecordOption) + defer func() { + *o = (*o)[:0] + recOptPool.Put(o) + }() + + *o = append(*o, metric.WithAttributes(attrs...)) + m.Float64Histogram.Record(ctx, val, *o...) +} \ No newline at end of file diff --git a/vendor/modules.txt b/vendor/modules.txt index e59e6e736a..1f358c55b2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -214,6 +214,11 @@ go.opencensus.io/trace/tracestate ## explicit; go 1.22.0 go.opentelemetry.io/auto/sdk go.opentelemetry.io/auto/sdk/internal/telemetry +# go.opentelemetry.io/contrib/instrumentation/runtime v0.62.0 +## explicit; go 1.23.0 +go.opentelemetry.io/contrib/instrumentation/runtime +go.opentelemetry.io/contrib/instrumentation/runtime/internal/deprecatedruntime +go.opentelemetry.io/contrib/instrumentation/runtime/internal/x # go.opentelemetry.io/otel v1.37.0 ## explicit; go 1.23.0 go.opentelemetry.io/otel @@ -226,6 +231,7 @@ go.opentelemetry.io/otel/internal/global go.opentelemetry.io/otel/propagation go.opentelemetry.io/otel/semconv/v1.26.0 go.opentelemetry.io/otel/semconv/v1.34.0 +go.opentelemetry.io/otel/semconv/v1.34.0/goconv # go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 ## explicit; go 1.23.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc