From f3962ffb403c7b6882d0a565644d58117c5ed8d4 Mon Sep 17 00:00:00 2001 From: Christophe Collot Date: Thu, 14 Nov 2024 15:45:50 +0100 Subject: [PATCH 1/2] feat: add support overriding nodejs buckets using .spec.nodejs.histogramBuckets --- apis/v1alpha1/instrumentation_types.go | 9 ++++++ apis/v1alpha1/zz_generated.deepcopy.go | 27 ++++++++++++++++ .../nodejs/src/autoinstrumentation.ts | 31 ++++++++++++++++++- .../opentelemetry.io_instrumentations.yaml | 11 +++++++ pkg/instrumentation/nodejs.go | 18 ++++++++++- 5 files changed, 94 insertions(+), 2 deletions(-) diff --git a/apis/v1alpha1/instrumentation_types.go b/apis/v1alpha1/instrumentation_types.go index e290f4033b..ec38aa7aa3 100644 --- a/apis/v1alpha1/instrumentation_types.go +++ b/apis/v1alpha1/instrumentation_types.go @@ -217,6 +217,15 @@ type NodeJS struct { // Resources describes the compute resource requirements. // +optional Resources corev1.ResourceRequirements `json:"resourceRequirements,omitempty"` + + // HistogramBuckets defines the histogram buckets for metrics generated by the NodeJS SDK. + // +optional + HistogramBuckets []HistogramBucketsItem `json:"histogramBuckets,omitempty"` +} + +type HistogramBucketsItem struct { + Name string `json:"instrumentName"` + Buckets []float64 `json:"buckets"` } // Python defines Python SDK and instrumentation configuration. diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index 35c04992cb..48c853edb6 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -234,6 +234,26 @@ func (in *Go) DeepCopy() *Go { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HistogramBucketsItem) DeepCopyInto(out *HistogramBucketsItem) { + *out = *in + if in.Buckets != nil { + in, out := &in.Buckets, &out.Buckets + *out = make([]float64, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HistogramBucketsItem. +func (in *HistogramBucketsItem) DeepCopy() *HistogramBucketsItem { + if in == nil { + return nil + } + out := new(HistogramBucketsItem) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Ingress) DeepCopyInto(out *Ingress) { *out = *in @@ -503,6 +523,13 @@ func (in *NodeJS) DeepCopyInto(out *NodeJS) { } } in.Resources.DeepCopyInto(&out.Resources) + if in.HistogramBuckets != nil { + in, out := &in.HistogramBuckets, &out.HistogramBuckets + *out = make([]HistogramBucketsItem, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeJS. diff --git a/autoinstrumentation/nodejs/src/autoinstrumentation.ts b/autoinstrumentation/nodejs/src/autoinstrumentation.ts index 2a4aabc4a7..0693fc7fc6 100644 --- a/autoinstrumentation/nodejs/src/autoinstrumentation.ts +++ b/autoinstrumentation/nodejs/src/autoinstrumentation.ts @@ -4,7 +4,7 @@ import { OTLPTraceExporter as OTLPHttpTraceExporter } from '@opentelemetry/expor import { OTLPTraceExporter as OTLPGrpcTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc'; import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc'; import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'; -import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'; +import { ExplicitBucketHistogramAggregation, PeriodicExportingMetricReader, View } from "@opentelemetry/sdk-metrics"; import { alibabaCloudEcsDetector } from '@opentelemetry/resource-detector-alibaba-cloud'; import { awsEc2Detector, awsEksDetector } from '@opentelemetry/resource-detector-aws'; import { containerDetector } from '@opentelemetry/resource-detector-container'; @@ -50,11 +50,39 @@ function getMetricReader() { } } +function getViews() { + const histogramBuckets = process.env.OTEL_OPERATOR_NODEJS_HISTOGRAM_BUCKETS; + if (histogramBuckets) { + try { + let views = []; + for (const viewCfg of JSON.parse(histogramBuckets)) { + views.push( + new View({ + instrumentName: viewCfg["instrumentName"], + aggregation: new ExplicitBucketHistogramAggregation( + viewCfg["buckets"], + ), + }), + ); + } + return views; + } catch (error) { + diag.error( + "Failed to parse OTEL_OPERATOR_NODEJS_HISTOGRAM_BUCKETS:", + error, + ); + } + return []; + } + return []; +} + const sdk = new NodeSDK({ autoDetectResources: true, instrumentations: [getNodeAutoInstrumentations()], traceExporter: getTraceExporter(), metricReader: getMetricReader(), + views: getViews(), resourceDetectors: [ // Standard resource detectors. @@ -75,3 +103,4 @@ const sdk = new NodeSDK({ }); sdk.start(); + diff --git a/config/crd/bases/opentelemetry.io_instrumentations.yaml b/config/crd/bases/opentelemetry.io_instrumentations.yaml index 4032a33613..1505048761 100644 --- a/config/crd/bases/opentelemetry.io_instrumentations.yaml +++ b/config/crd/bases/opentelemetry.io_instrumentations.yaml @@ -1393,6 +1393,17 @@ spec: type: object nodejs: properties: + histogramBuckets: + type: array + items: + type: object + properties: + instrumentName: + type: string + buckets: + type: array + items: + type: number env: items: properties: diff --git a/pkg/instrumentation/nodejs.go b/pkg/instrumentation/nodejs.go index a3d02ea53d..788094cdfc 100644 --- a/pkg/instrumentation/nodejs.go +++ b/pkg/instrumentation/nodejs.go @@ -16,7 +16,7 @@ package instrumentation import ( corev1 "k8s.io/api/core/v1" - + "encoding/json" "github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1" ) @@ -26,9 +26,25 @@ const ( nodejsInitContainerName = initContainerName + "-nodejs" nodejsVolumeName = volumeName + "-nodejs" nodejsInstrMountPath = "/otel-auto-instrumentation-nodejs" + histogramBucketsEnvVarName = "OTEL_OPERATOR_NODEJS_HISTOGRAM_BUCKETS" ) func injectNodeJSSDK(nodeJSSpec v1alpha1.NodeJS, pod corev1.Pod, index int) (corev1.Pod, error) { + if len(nodeJSSpec.HistogramBuckets) > 0 { + bucketsJSON, err := json.Marshal(nodeJSSpec.HistogramBuckets) + if err != nil { + // handle error, possibly log and skip injection for this resource + return pod, err + } + + // Inject as an environment variable in the pod + bucketsJSONEnvVar := corev1.EnvVar{ + Name: histogramBucketsEnvVarName, + Value: string(bucketsJSON), + } + nodeJSSpec.Env = append(nodeJSSpec.Env, bucketsJSONEnvVar) + } + volume := instrVolume(nodeJSSpec.VolumeClaimTemplate, nodejsVolumeName, nodeJSSpec.VolumeSizeLimit) // caller checks if there is at least one container. From f726eb2f9f3f84f96295e8a9d340af45ba05bc2d Mon Sep 17 00:00:00 2001 From: Christophe Collot Date: Thu, 14 Nov 2024 15:49:03 +0100 Subject: [PATCH 2/2] chore: add changelog file --- .../3436-nodejs-autosintrumentation-buckets.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100755 .chloggen/3436-nodejs-autosintrumentation-buckets.yaml diff --git a/.chloggen/3436-nodejs-autosintrumentation-buckets.yaml b/.chloggen/3436-nodejs-autosintrumentation-buckets.yaml new file mode 100755 index 0000000000..0ac1999253 --- /dev/null +++ b/.chloggen/3436-nodejs-autosintrumentation-buckets.yaml @@ -0,0 +1,16 @@ +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action) +component: auto-instrumentation + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Support overriding the default histogram bucket list via the `spec.nodejs.histogramBuckets` field on the Instrumentation Custom Resource. + +# One or more tracking issues related to the change +issues: [3436] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: