Skip to content

Commit 7dbd664

Browse files
authored
Allow defining extra resource labels in prometheus (#1742)
* Allow defining extra resource labels in prometheus * reformat
1 parent d90962d commit 7dbd664

File tree

4 files changed

+53
-7
lines changed

4 files changed

+53
-7
lines changed

docs/sources/configure/export-data.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,23 @@ no Prometheus endpoint is open.
180180

181181
Specifies the HTTP query path to fetch the list of Prometheus metrics.
182182

183+
| YAML | Environment variable | Type | Default |
184+
|-----------------------------|----------------------------------------------|-----------------|---------|
185+
| `extra_resource_attributes` | `BEYLA_PROMETHEUS_EXTRA_RESOURCE_ATTRIBUTES` | list of strings | (empty) |
186+
187+
A list of additional resource attributes to be added to the reported `target_info` metric.
188+
189+
Due to internal limitations of the Prometheus API client, Beyla needs to know beforehand which attributes are exposed
190+
for each metric. This would cause that some attributes that are discovered at runtime, during instrumentation, won't
191+
be visible by default. For example, attributes defined on each application via Kubernetes annotations, or in the
192+
target application's `OTEL_RESOURCE_ATTRIBUTES` environment variable.
193+
194+
For example, an application defining the `OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production` as environment
195+
variable, the `target_info{deployment.environment="production"}` attribute would be visible by default if the metrics
196+
are exported via OpenTelemetry but not if they are exported via Prometheus.
197+
198+
To make `deployment_environment` visible in Prometheus, you need to add it to the `extra_resource_attributes` list.
199+
183200
| YAML | Environment variable | Type | Default |
184201
| ----- | ---------------------- | -------- | ------- |
185202
| `ttl` | `BEYLA_PROMETHEUS_TTL` | Duration | `5m` |

pkg/export/prom/prom.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"runtime"
77
"slices"
88
"strconv"
9+
"strings"
910
"time"
1011

1112
"github.com/hashicorp/golang-lru/v2/expirable"
@@ -121,6 +122,11 @@ type PrometheusConfig struct {
121122
// Registry is only used for embedding Beyla within the Grafana Agent.
122123
// It must be nil when Beyla runs as standalone
123124
Registry *prometheus.Registry `yaml:"-"`
125+
126+
// ExtraResourceLabels adds extra metadata labels to Prometheus metrics from sources whose availability can't be known
127+
// beforehand. For example, to add the OTEL deployment.environment resource attribute as a Prometheus resource attribute,
128+
// you should add `deployment.environment`.
129+
ExtraResourceLabels []string `yaml:"extra_resource_attributes" env:"BEYLA_PROMETHEUS_EXTRA_RESOURCE_ATTRIBUTES" envSeparator:","`
124130
}
125131

126132
func (p *PrometheusConfig) SpanMetricsEnabled() bool {
@@ -161,7 +167,8 @@ func (p *PrometheusConfig) Enabled() bool {
161167
}
162168

163169
type metricsReporter struct {
164-
cfg *PrometheusConfig
170+
cfg *PrometheusConfig
171+
extraMetadataLabels []attr.Name
165172

166173
beylaInfo *Expirer[prometheus.Gauge]
167174
httpDuration *Expirer[prometheus.Histogram]
@@ -308,11 +315,13 @@ func newReporter(
308315
kubeEnabled := ctxInfo.K8sInformer.IsKubeEnabled()
309316
// If service name is not explicitly set, we take the service name as set by the
310317
// executable inspector
318+
extraMetadataLabels := parseExtraMetadata(cfg.ExtraResourceLabels)
311319
mr := &metricsReporter{
312320
bgCtx: ctx,
313321
ctxInfo: ctxInfo,
314322
cfg: cfg,
315323
kubeEnabled: kubeEnabled,
324+
extraMetadataLabels: extraMetadataLabels,
316325
hostID: ctxInfo.HostID,
317326
clock: clock,
318327
is: is,
@@ -457,7 +466,7 @@ func newReporter(
457466
return NewExpirer[prometheus.Gauge](prometheus.NewGaugeVec(prometheus.GaugeOpts{
458467
Name: TracesTargetInfo,
459468
Help: "target service information in trace span metric format",
460-
}, labelNamesTargetInfo(kubeEnabled)).MetricVec, clock.Time, cfg.TTL)
469+
}, labelNamesTargetInfo(kubeEnabled, extraMetadataLabels)).MetricVec, clock.Time, cfg.TTL)
461470
}),
462471
tracesHostInfo: optionalGaugeProvider(cfg.SpanMetricsEnabled() || cfg.ServiceGraphMetricsEnabled(), func() *Expirer[prometheus.Gauge] {
463472
return NewExpirer[prometheus.Gauge](prometheus.NewGaugeVec(prometheus.GaugeOpts{
@@ -500,7 +509,7 @@ func newReporter(
500509
targetInfo: NewExpirer[prometheus.Gauge](prometheus.NewGaugeVec(prometheus.GaugeOpts{
501510
Name: TargetInfo,
502511
Help: "attributes associated to a given monitored entity",
503-
}, labelNamesTargetInfo(kubeEnabled)).MetricVec, clock.Time, cfg.TTL),
512+
}, labelNamesTargetInfo(kubeEnabled, extraMetadataLabels)).MetricVec, clock.Time, cfg.TTL),
504513
gpuKernelCallsTotal: optionalCounterProvider(is.GPUEnabled(), func() *Expirer[prometheus.Counter] {
505514
return NewExpirer[prometheus.Counter](prometheus.NewCounterVec(prometheus.CounterOpts{
506515
Name: attributes.GPUKernelLaunchCalls.Prom,
@@ -597,6 +606,16 @@ func newReporter(
597606
return mr, nil
598607
}
599608

609+
func parseExtraMetadata(labels []string) []attr.Name {
610+
// first, we convert any metric in snake_format to dotted.format,
611+
// as it is the internal representation of metadata labels
612+
attrNames := make([]attr.Name, len(labels))
613+
for i, label := range labels {
614+
attrNames[i] = attr.Name(strings.ReplaceAll(label, "_", "."))
615+
}
616+
return attrNames
617+
}
618+
600619
func optionalHistogramProvider(enable bool, provider func() *Expirer[prometheus.Histogram]) *Expirer[prometheus.Histogram] {
601620
if !enable {
602621
return nil
@@ -796,13 +815,17 @@ func (r *metricsReporter) labelValuesSpans(span *request.Span) []string {
796815
}
797816
}
798817

799-
func labelNamesTargetInfo(kubeEnabled bool) []string {
818+
func labelNamesTargetInfo(kubeEnabled bool, extraMetadataLabelNames []attr.Name) []string {
800819
names := []string{hostIDKey, hostNameKey, serviceKey, serviceNamespaceKey, serviceInstanceKey, serviceJobKey, telemetryLanguageKey, telemetrySDKKey, sourceKey}
801820

802821
if kubeEnabled {
803822
names = appendK8sLabelNames(names)
804823
}
805824

825+
for _, mdn := range extraMetadataLabelNames {
826+
names = append(names, mdn.Prom())
827+
}
828+
806829
return names
807830
}
808831

@@ -823,6 +846,10 @@ func (r *metricsReporter) labelValuesTargetInfo(service svc.Attrs) []string {
823846
values = appendK8sLabelValuesService(values, service)
824847
}
825848

849+
for _, k := range r.extraMetadataLabels {
850+
values = append(values, service.Metadata[k])
851+
}
852+
826853
return values
827854
}
828855

test/integration/configs/instrumenter-config-promscrape.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ prometheus_export:
77
features:
88
- application
99
- application_process
10+
extra_resource_attributes: ["deployment_environment"]
1011
attributes:
1112
select:
1213
process_cpu_utilization:

test/integration/k8s/common/k8s_metrics_testfuncs.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,10 @@ func FeatureGRPCMetricsDecoration(manifest string, overrideAttrs map[string]stri
229229
attributeMap(allAttributes, overrideAttrs))).
230230
Assess("target_info metrics exist",
231231
testMetricsDecoration([]string{"target_info"}, `{job=~".*testserver"}`, map[string]string{
232-
"host_name": "testserver",
233-
"host_id": HostIDRegex,
234-
"instance": targetInfoInstance,
232+
"host_name": "testserver",
233+
"host_id": HostIDRegex,
234+
"instance": targetInfoInstance,
235+
"deployment_environment": "integration-test",
235236
}),
236237
).Feature()
237238
}

0 commit comments

Comments
 (0)