Skip to content

Commit 37e2e95

Browse files
committed
Add crd metrics usage information
Signed-off-by: Ruben Vargas <[email protected]>
1 parent 9944af6 commit 37e2e95

File tree

8 files changed

+239
-4
lines changed

8 files changed

+239
-4
lines changed

.chloggen/usage_metrics.yaml

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
2+
change_type: enhancement
3+
4+
# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action)
5+
component: collector
6+
7+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
8+
note: Add usage metrics for the collector
9+
10+
# One or more tracking issues related to the change
11+
issues: [2829]
12+
13+
# (Optional) One or more lines of additional information to render under the primary note.
14+
# These lines will be padded with 2 spaces and then inserted directly into the document.
15+
# Use pipe (|) for multiline entries.
16+
subtext: |
17+
This change will add metrics to the OpenTelemetry operator about how the collector is used in the cluster,
18+
it will add the following metrics to the opentelemetry-operator metrics endpoint
19+
opentelemetry_collector_receivers{collector_name="collector_name", namespace="ns", type="otlp"} 1
20+
opentelemetry_collector_exporters{collector_name="collector_name", namespace="ns", type="otlp"} 1
21+
opentelemetry_collector_processors{collector_name="collector_name", namespace="ns", type="otlp"} 1
22+
opentelemetry_collector_processors{collector_name="collector_name", namespace="ns", type="otlp"} 0

apis/v1beta1/collector_webhook.go

+47-4
Original file line numberDiff line numberDiff line change
@@ -166,23 +166,66 @@ func (c CollectorWebhook) ValidateCreate(ctx context.Context, obj runtime.Object
166166
if !ok {
167167
return nil, fmt.Errorf("expected an OpenTelemetryCollector, received %T", obj)
168168
}
169-
return c.validate(ctx, otelcol)
169+
170+
warnings, err := c.validate(ctx, otelcol)
171+
if err != nil {
172+
return warnings, err
173+
}
174+
175+
err = IncCounters(ctx, otelcol)
176+
if err != nil {
177+
return warnings, err
178+
}
179+
180+
return warnings, nil
170181
}
171182

172-
func (c CollectorWebhook) ValidateUpdate(ctx context.Context, _, newObj runtime.Object) (admission.Warnings, error) {
183+
func (c CollectorWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
173184
otelcol, ok := newObj.(*OpenTelemetryCollector)
174185
if !ok {
175186
return nil, fmt.Errorf("expected an OpenTelemetryCollector, received %T", newObj)
176187
}
177-
return c.validate(ctx, otelcol)
188+
189+
otelcolOld, ok := oldObj.(*OpenTelemetryCollector)
190+
if !ok {
191+
return nil, fmt.Errorf("expected an OpenTelemetryCollector, received %T", oldObj)
192+
}
193+
194+
warnings, err := c.validate(ctx, otelcol)
195+
if err != nil {
196+
return warnings, err
197+
}
198+
199+
// Decrease all metrics related to old CR
200+
err = DecCounters(ctx, otelcolOld)
201+
if err != nil {
202+
return warnings, err
203+
}
204+
205+
// Increase all metrics related to new CR
206+
err = IncCounters(ctx, otelcolOld)
207+
if err != nil {
208+
return warnings, err
209+
}
210+
return warnings, nil
178211
}
179212

180213
func (c CollectorWebhook) ValidateDelete(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
181214
otelcol, ok := obj.(*OpenTelemetryCollector)
182215
if !ok || otelcol == nil {
183216
return nil, fmt.Errorf("expected an OpenTelemetryCollector, received %T", obj)
184217
}
185-
return c.validate(ctx, otelcol)
218+
219+
warnings, err := c.validate(ctx, otelcol)
220+
if err != nil {
221+
return warnings, err
222+
}
223+
err = DecCounters(ctx, otelcol)
224+
if err != nil {
225+
return warnings, err
226+
}
227+
228+
return warnings, nil
186229
}
187230

188231
func (c CollectorWebhook) validate(ctx context.Context, r *OpenTelemetryCollector) (admission.Warnings, error) {

apis/v1beta1/metrics.go

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package v1beta1
16+
17+
import (
18+
"context"
19+
"strings"
20+
21+
"go.opentelemetry.io/otel"
22+
"go.opentelemetry.io/otel/attribute"
23+
"go.opentelemetry.io/otel/exporters/prometheus"
24+
"go.opentelemetry.io/otel/metric"
25+
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
26+
"sigs.k8s.io/controller-runtime/pkg/metrics"
27+
)
28+
29+
const (
30+
meterName = "crd-metrics"
31+
)
32+
33+
// Metric labels
34+
35+
const (
36+
prefix = "opentelemetry_collector_"
37+
receivers = prefix + "receivers"
38+
exporters = prefix + "exporters"
39+
processors = prefix + "processors"
40+
extensions = prefix + "extensions"
41+
mode = prefix + "info"
42+
)
43+
44+
type components struct {
45+
receivers []string
46+
processors []string
47+
exporters []string
48+
extensions []string
49+
}
50+
51+
// BootstrapMetrics configures the OpenTelemetry meter provider with the Prometheus exporter.
52+
func BootstrapMetrics() error {
53+
54+
exporter, err := prometheus.New(prometheus.WithRegisterer(metrics.Registry))
55+
if err != nil {
56+
return err
57+
}
58+
provider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(exporter))
59+
otel.SetMeterProvider(provider)
60+
return err
61+
}
62+
63+
func extractElements(elements map[string]interface{}) []string {
64+
if elements == nil {
65+
return []string{}
66+
}
67+
68+
itemsMap := map[string]struct{}{}
69+
var items []string
70+
for key := range elements {
71+
itemName := strings.SplitN(key, "/", 2)[0]
72+
itemsMap[itemName] = struct{}{}
73+
}
74+
for key := range itemsMap {
75+
items = append(items, key)
76+
}
77+
return items
78+
}
79+
80+
func getComponentsFromConfigV1Beta1(yamlContent Config) *components {
81+
82+
info := &components{
83+
receivers: extractElements(yamlContent.Receivers.Object),
84+
exporters: extractElements(yamlContent.Exporters.Object),
85+
}
86+
87+
if yamlContent.Processors != nil {
88+
info.processors = extractElements(yamlContent.Processors.Object)
89+
}
90+
91+
if yamlContent.Extensions != nil {
92+
info.extensions = extractElements(yamlContent.Extensions.Object)
93+
}
94+
return info
95+
}
96+
97+
func IncCounters(ctx context.Context, collector *OpenTelemetryCollector) error {
98+
return updateCounter(ctx, collector, true)
99+
}
100+
101+
func DecCounters(ctx context.Context, collector *OpenTelemetryCollector) error {
102+
return updateCounter(ctx, collector, false)
103+
}
104+
105+
func updateCounter(ctx context.Context, collector *OpenTelemetryCollector, up bool) error {
106+
meter := otel.Meter(meterName)
107+
receiversCounter, err := meter.Int64UpDownCounter(receivers)
108+
if err != nil {
109+
return err
110+
}
111+
112+
exporterCounter, err := meter.Int64UpDownCounter(exporters)
113+
if err != nil {
114+
return err
115+
}
116+
117+
processorCounter, err := meter.Int64UpDownCounter(processors)
118+
if err != nil {
119+
return err
120+
}
121+
122+
extensionsCounter, err := meter.Int64UpDownCounter(extensions)
123+
if err != nil {
124+
return err
125+
}
126+
127+
components := getComponentsFromConfigV1Beta1(collector.Spec.Config)
128+
moveCounter(ctx, collector, components.receivers, receiversCounter, up)
129+
moveCounter(ctx, collector, components.exporters, exporterCounter, up)
130+
moveCounter(ctx, collector, components.processors, processorCounter, up)
131+
moveCounter(ctx, collector, components.extensions, extensionsCounter, up)
132+
133+
return nil
134+
}
135+
136+
func moveCounter(
137+
ctx context.Context, collector *OpenTelemetryCollector, types []string, upDown metric.Int64UpDownCounter, up bool) {
138+
for _, exporter := range types {
139+
inc := 1
140+
if !up {
141+
inc = -1
142+
}
143+
upDown.Add(ctx, int64(inc), metric.WithAttributes(
144+
attribute.Key("collector_name").String(collector.Name),
145+
attribute.Key("namespace").String(collector.Namespace),
146+
attribute.Key("type").String(exporter),
147+
))
148+
}
149+
}

config/manager/kustomization.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11
resources:
22
- manager.yaml
3+
apiVersion: kustomize.config.k8s.io/v1beta1
4+
kind: Kustomization
5+
images:
6+
- name: controller
7+
newName: quay.io/rvargasp/opentelemetry-operator
8+
newTag: 1714976402.0.0

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ require (
190190
go.mongodb.org/mongo-driver v1.14.0 // indirect
191191
go.opencensus.io v0.24.0 // indirect
192192
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
193+
go.opentelemetry.io/otel/exporters/prometheus v0.48.0 // indirect
193194
go.opentelemetry.io/otel/trace v1.26.0 // indirect
194195
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
195196
go.uber.org/atomic v1.11.0 // indirect

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm
645645
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
646646
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
647647
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
648+
go.opentelemetry.io/otel/exporters/prometheus v0.48.0 h1:sBQe3VNGUjY9IKWQC6z2lNqa5iGbDSxhs60ABwK4y0s=
649+
go.opentelemetry.io/otel/exporters/prometheus v0.48.0/go.mod h1:DtrbMzoZWwQHyrQmCfLam5DZbnmorsGbOtTbYHycU5o=
648650
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
649651
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
650652
go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8=

main.go

+10
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ func main() {
118118
enableNginxInstrumentation bool
119119
enableNodeJSInstrumentation bool
120120
enableJavaInstrumentation bool
121+
enableCRMetrics bool
121122
collectorImage string
122123
targetAllocatorImage string
123124
operatorOpAMPBridgeImage string
@@ -149,6 +150,8 @@ func main() {
149150
pflag.BoolVar(&enableNginxInstrumentation, constants.FlagNginx, false, "Controls whether the operator supports nginx auto-instrumentation")
150151
pflag.BoolVar(&enableNodeJSInstrumentation, constants.FlagNodeJS, true, "Controls whether the operator supports nodejs auto-instrumentation")
151152
pflag.BoolVar(&enableJavaInstrumentation, constants.FlagJava, true, "Controls whether the operator supports java auto-instrumentation")
153+
pflag.BoolVar(&enableCRMetrics, constants.FlagCRMetrics, false, "Controls whether the CR metrics is enabled")
154+
152155
stringFlagOrEnv(&collectorImage, "collector-image", "RELATED_IMAGE_COLLECTOR", fmt.Sprintf("ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector:%s", v.OpenTelemetryCollector), "The default OpenTelemetry collector image. This image is used when no image is specified in the CustomResource.")
153156
stringFlagOrEnv(&targetAllocatorImage, "target-allocator-image", "RELATED_IMAGE_TARGET_ALLOCATOR", fmt.Sprintf("ghcr.io/open-telemetry/opentelemetry-operator/target-allocator:%s", v.TargetAllocator), "The default OpenTelemetry target allocator image. This image is used when no image is specified in the CustomResource.")
154157
stringFlagOrEnv(&operatorOpAMPBridgeImage, "operator-opamp-bridge-image", "RELATED_IMAGE_OPERATOR_OPAMP_BRIDGE", fmt.Sprintf("ghcr.io/open-telemetry/opentelemetry-operator/operator-opamp-bridge:%s", v.OperatorOpAMPBridge), "The default OpenTelemetry Operator OpAMP Bridge image. This image is used when no image is specified in the CustomResource.")
@@ -313,6 +316,13 @@ func main() {
313316
}
314317
}
315318
}
319+
320+
if enableCRMetrics {
321+
if metricsErr := otelv1beta1.BootstrapMetrics(); metricsErr != nil {
322+
setupLog.Error(metricsErr, "Error bootstrapping CRD metrics")
323+
}
324+
}
325+
316326
if cfg.LabelsFilter() != nil {
317327
for _, basePattern := range cfg.LabelsFilter() {
318328
_, compileErr := regexp.Compile(basePattern)

pkg/constants/env.go

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ const (
3737
EnvNodeName = "OTEL_RESOURCE_ATTRIBUTES_NODE_NAME"
3838
EnvNodeIP = "OTEL_NODE_IP"
3939

40+
FlagCRMetrics = "enable-cr-metrics"
41+
4042
FlagApacheHttpd = "enable-apache-httpd-instrumentation"
4143
FlagDotNet = "enable-dotnet-instrumentation"
4244
FlagGo = "enable-go-instrumentation"

0 commit comments

Comments
 (0)