Skip to content

Commit

Permalink
[prometheusexporter] Export target_info metrics (#11514)
Browse files Browse the repository at this point in the history
Export target_info metrics

Signed-off-by: Goutham Veeramachaneni <[email protected]>
  • Loading branch information
gouthamve authored Aug 3, 2022
1 parent bb07aa9 commit 9dead84
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 1 deletion.
128 changes: 128 additions & 0 deletions exporter/prometheusexporter/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ import (
prometheustranslator "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus"
)

const (
targetMetricName = "target_info"
)

var (
separatorString = string([]byte{model.SeparatorByte})
)

type collector struct {
accumulator accumulator
logger *zap.Logger
Expand Down Expand Up @@ -220,6 +228,94 @@ func (c *collector) convertDoubleHistogram(metric pmetric.Metric, resourceAttrs
return m, nil
}

func (c *collector) createTargetInfoMetrics(resourceAttrs []pcommon.Map) ([]prometheus.Metric, error) {
var metrics []prometheus.Metric
var lastErr error

// deduplicate resourceAttrs by job and instance
deduplicatedResourceAttrs := make([]pcommon.Map, 0, len(resourceAttrs))
seenResource := map[string]struct{}{}
for _, attrs := range resourceAttrs {
sig := resourceSignature(attrs)
if sig == "" {
continue
}
if _, ok := seenResource[resourceSignature(attrs)]; !ok {
seenResource[resourceSignature(attrs)] = struct{}{}
deduplicatedResourceAttrs = append(deduplicatedResourceAttrs, attrs)
}
}

for _, rAttributes := range deduplicatedResourceAttrs {
// map ensures no duplicate label name
labels := make(map[string]string, rAttributes.Len()+2) // +2 for job and instance labels.

// Use resource attributes (other than those used for job+instance) as the
// metric labels for the target info metric
attributes := pcommon.NewMap()
rAttributes.CopyTo(attributes)
attributes.RemoveIf(func(k string, _ pcommon.Value) bool {
switch k {
case conventions.AttributeServiceName, conventions.AttributeServiceNamespace, conventions.AttributeServiceInstanceID:
// Remove resource attributes used for job + instance
return true
default:
return false
}
})

attributes.Range(func(k string, v pcommon.Value) bool {
finalKey := prometheustranslator.NormalizeLabel(k)
if existingVal, ok := labels[finalKey]; ok {
labels[finalKey] = existingVal + ";" + v.AsString()
} else {
labels[finalKey] = v.AsString()
}

return true
})

// Map service.name + service.namespace to job
if serviceName, ok := rAttributes.Get(conventions.AttributeServiceName); ok {
val := serviceName.AsString()
if serviceNamespace, ok := rAttributes.Get(conventions.AttributeServiceNamespace); ok {
val = fmt.Sprintf("%s/%s", serviceNamespace.AsString(), val)
}
labels[model.JobLabel] = val
}
// Map service.instance.id to instance
if instance, ok := rAttributes.Get(conventions.AttributeServiceInstanceID); ok {
labels[model.InstanceLabel] = instance.AsString()
}

name := targetMetricName
if len(c.namespace) > 0 {
name = c.namespace + "_" + name
}

keys := make([]string, 0, len(labels))
values := make([]string, 0, len(labels))
for key, value := range labels {
keys = append(keys, key)
values = append(values, value)
}

metric, err := prometheus.NewConstMetric(
prometheus.NewDesc(name, "Target metadata", keys, nil),
prometheus.GaugeValue,
1,
values...,
)
if err != nil {
lastErr = err
continue
}

metrics = append(metrics, metric)
}
return metrics, lastErr
}

/*
Reporting
*/
Expand All @@ -228,6 +324,15 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {

inMetrics, resourceAttrs := c.accumulator.Collect()

targetMetrics, err := c.createTargetInfoMetrics(resourceAttrs)
if err != nil {
c.logger.Error(fmt.Sprintf("failed to convert metric %s: %s", targetMetricName, err.Error()))
}
for _, m := range targetMetrics {
ch <- m
c.logger.Debug(fmt.Sprintf("metric served: %s", m.Desc().String()))
}

for i := range inMetrics {
pMetric := inMetrics[i]
rAttr := resourceAttrs[i]
Expand All @@ -242,3 +347,26 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
c.logger.Debug(fmt.Sprintf("metric served: %s", m.Desc().String()))
}
}

func resourceSignature(attributes pcommon.Map) string {
job := ""
instance := ""

// Map service.name + service.namespace to job
if serviceName, ok := attributes.Get(conventions.AttributeServiceName); ok {
job = serviceName.AsString()
if serviceNamespace, ok := attributes.Get(conventions.AttributeServiceNamespace); ok {
job = fmt.Sprintf("%s/%s", serviceNamespace.AsString(), job)
}
}
// Map service.instance.id to instance
if inst, ok := attributes.Get(conventions.AttributeServiceInstanceID); ok {
instance = inst.AsString()
}

if job == "" && instance == "" {
return ""
}

return job + separatorString + instance
}
16 changes: 15 additions & 1 deletion exporter/prometheusexporter/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package prometheusexporter

import (
"strings"
"testing"
"time"

Expand Down Expand Up @@ -320,6 +321,19 @@ func TestCollectMetrics(t *testing.T) {
j := 0
for m := range ch {
j++

if strings.Contains(m.Desc().String(), "fqName: \"test_space_target_info\"") {
pbMetric := io_prometheus_client.Metric{}
require.NoError(t, m.Write(&pbMetric))

labelsKeys := map[string]string{"job": "prod/testapp", "instance": "localhost:9090"}
for _, l := range pbMetric.Label {
require.Equal(t, labelsKeys[*l.Name], *l.Value)
}

continue
}

require.Contains(t, m.Desc().String(), "fqName: \"test_space_test_metric\"")
require.Contains(t, m.Desc().String(), "variableLabels: [label_1 label_2 job instance]")

Expand Down Expand Up @@ -350,7 +364,7 @@ func TestCollectMetrics(t *testing.T) {
require.Nil(t, pbMetric.Summary)
}
}
require.Equal(t, 1, j)
require.Equal(t, 2, j)
})
}
}
Expand Down
3 changes: 3 additions & 0 deletions exporter/prometheusexporter/end_to_end_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ func TestEndToEndSummarySupport(t *testing.T) {
`. HELP test_up The scraping was successful`,
`. TYPE test_up gauge`,
`test_up.instance="127.0.0.1:.*",job="otel-collector". 1 .*`,
`. HELP test_target_info Target metadata`,
`. TYPE test_target_info gauge`,
`test_target_info.http_scheme="http",instance="127.0.0.1:.*",job="otel-collector",net_host_port=".*". 1`,
}

// 5.5: Perform a complete line by line prefix verification to ensure we extract back the inputs
Expand Down
16 changes: 16 additions & 0 deletions unreleased/export-target-info-prometheusexporter.yaml
Original file line number Diff line number Diff line change
@@ -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. filelogreceiver)
component: prometheusexporter

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Export target_info metrics with the resource attributes.

# One or more tracking issues related to the change
issues: [8265]

# (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:

0 comments on commit 9dead84

Please sign in to comment.