2024-01-18 10:02:06 +00:00
|
|
|
// DO NOT EDIT. COPIED AS-IS. SEE ../README.md
|
2023-07-28 10:35:28 +00:00
|
|
|
|
|
|
|
// Copyright The OpenTelemetry Authors
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
package prometheusremotewrite // import "github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheusremotewrite"
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/hex"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"math"
|
|
|
|
"sort"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
"unicode/utf8"
|
|
|
|
|
|
|
|
"github.com/prometheus/common/model"
|
|
|
|
"github.com/prometheus/prometheus/model/timestamp"
|
|
|
|
"github.com/prometheus/prometheus/model/value"
|
|
|
|
"github.com/prometheus/prometheus/prompb"
|
|
|
|
"go.opentelemetry.io/collector/pdata/pcommon"
|
|
|
|
"go.opentelemetry.io/collector/pdata/pmetric"
|
|
|
|
conventions "go.opentelemetry.io/collector/semconv/v1.6.1"
|
|
|
|
|
|
|
|
prometheustranslator "github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheus"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
sumStr = "_sum"
|
|
|
|
countStr = "_count"
|
|
|
|
bucketStr = "_bucket"
|
|
|
|
leStr = "le"
|
|
|
|
quantileStr = "quantile"
|
|
|
|
pInfStr = "+Inf"
|
|
|
|
createdSuffix = "_created"
|
|
|
|
// maxExemplarRunes is the maximum number of UTF-8 exemplar characters
|
|
|
|
// according to the prometheus specification
|
|
|
|
// https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#exemplars
|
|
|
|
maxExemplarRunes = 128
|
|
|
|
// Trace and Span id keys are defined as part of the spec:
|
|
|
|
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification%2Fmetrics%2Fdatamodel.md#exemplars-2
|
|
|
|
traceIDKey = "trace_id"
|
|
|
|
spanIDKey = "span_id"
|
|
|
|
infoType = "info"
|
|
|
|
targetMetricName = "target_info"
|
|
|
|
)
|
|
|
|
|
|
|
|
type bucketBoundsData struct {
|
|
|
|
sig string
|
|
|
|
bound float64
|
|
|
|
}
|
|
|
|
|
|
|
|
// byBucketBoundsData enables the usage of sort.Sort() with a slice of bucket bounds
|
|
|
|
type byBucketBoundsData []bucketBoundsData
|
|
|
|
|
|
|
|
func (m byBucketBoundsData) Len() int { return len(m) }
|
|
|
|
func (m byBucketBoundsData) Less(i, j int) bool { return m[i].bound < m[j].bound }
|
|
|
|
func (m byBucketBoundsData) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
|
|
|
|
|
|
|
|
// ByLabelName enables the usage of sort.Sort() with a slice of labels
|
|
|
|
type ByLabelName []prompb.Label
|
|
|
|
|
|
|
|
func (a ByLabelName) Len() int { return len(a) }
|
|
|
|
func (a ByLabelName) Less(i, j int) bool { return a[i].Name < a[j].Name }
|
|
|
|
func (a ByLabelName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
|
|
|
|
// addSample finds a TimeSeries in tsMap that corresponds to the label set labels, and add sample to the TimeSeries; it
|
|
|
|
// creates a new TimeSeries in the map if not found and returns the time series signature.
|
|
|
|
// tsMap will be unmodified if either labels or sample is nil, but can still be modified if the exemplar is nil.
|
|
|
|
func addSample(tsMap map[string]*prompb.TimeSeries, sample *prompb.Sample, labels []prompb.Label,
|
2023-11-15 14:09:15 +00:00
|
|
|
datatype string) string {
|
2023-07-28 10:35:28 +00:00
|
|
|
if sample == nil || labels == nil || tsMap == nil {
|
2024-02-22 08:09:41 +00:00
|
|
|
// This shouldn't happen
|
2023-07-28 10:35:28 +00:00
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2024-02-22 08:09:41 +00:00
|
|
|
sig := timeSeriesSignature(datatype, labels)
|
|
|
|
ts := tsMap[sig]
|
|
|
|
if ts != nil {
|
2023-07-28 10:35:28 +00:00
|
|
|
ts.Samples = append(ts.Samples, *sample)
|
|
|
|
} else {
|
|
|
|
newTs := &prompb.TimeSeries{
|
|
|
|
Labels: labels,
|
|
|
|
Samples: []prompb.Sample{*sample},
|
|
|
|
}
|
|
|
|
tsMap[sig] = newTs
|
|
|
|
}
|
|
|
|
|
|
|
|
return sig
|
|
|
|
}
|
|
|
|
|
|
|
|
// addExemplars finds a bucket bound that corresponds to the exemplars value and add the exemplar to the specific sig;
|
|
|
|
// we only add exemplars if samples are presents
|
|
|
|
// tsMap is unmodified if either of its parameters is nil and samples are nil.
|
|
|
|
func addExemplars(tsMap map[string]*prompb.TimeSeries, exemplars []prompb.Exemplar, bucketBoundsData []bucketBoundsData) {
|
2024-02-22 08:09:41 +00:00
|
|
|
if len(tsMap) == 0 || len(bucketBoundsData) == 0 || len(exemplars) == 0 {
|
2023-07-28 10:35:28 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Sort(byBucketBoundsData(bucketBoundsData))
|
|
|
|
|
|
|
|
for _, exemplar := range exemplars {
|
|
|
|
addExemplar(tsMap, bucketBoundsData, exemplar)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func addExemplar(tsMap map[string]*prompb.TimeSeries, bucketBounds []bucketBoundsData, exemplar prompb.Exemplar) {
|
|
|
|
for _, bucketBound := range bucketBounds {
|
|
|
|
sig := bucketBound.sig
|
|
|
|
bound := bucketBound.bound
|
|
|
|
|
2024-02-22 08:09:41 +00:00
|
|
|
ts := tsMap[sig]
|
|
|
|
if ts != nil && len(ts.Samples) > 0 && exemplar.Value <= bound {
|
|
|
|
ts.Exemplars = append(ts.Exemplars, exemplar)
|
|
|
|
return
|
2023-07-28 10:35:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// timeSeries return a string signature in the form of:
|
|
|
|
//
|
|
|
|
// TYPE-label1-value1- ... -labelN-valueN
|
|
|
|
//
|
|
|
|
// the label slice should not contain duplicate label names; this method sorts the slice by label name before creating
|
|
|
|
// the signature.
|
2024-02-22 08:09:41 +00:00
|
|
|
func timeSeriesSignature(datatype string, labels []prompb.Label) string {
|
2023-11-15 14:09:15 +00:00
|
|
|
length := len(datatype)
|
|
|
|
|
2024-02-22 08:09:41 +00:00
|
|
|
for _, lb := range labels {
|
2023-11-15 14:09:15 +00:00
|
|
|
length += 2 + len(lb.GetName()) + len(lb.GetValue())
|
|
|
|
}
|
|
|
|
|
2023-07-28 10:35:28 +00:00
|
|
|
b := strings.Builder{}
|
2023-11-15 14:09:15 +00:00
|
|
|
b.Grow(length)
|
2023-07-28 10:35:28 +00:00
|
|
|
b.WriteString(datatype)
|
|
|
|
|
2024-02-22 08:09:41 +00:00
|
|
|
sort.Sort(ByLabelName(labels))
|
2023-07-28 10:35:28 +00:00
|
|
|
|
2024-02-22 08:09:41 +00:00
|
|
|
for _, lb := range labels {
|
2023-07-28 10:35:28 +00:00
|
|
|
b.WriteString("-")
|
|
|
|
b.WriteString(lb.GetName())
|
|
|
|
b.WriteString("-")
|
|
|
|
b.WriteString(lb.GetValue())
|
|
|
|
}
|
|
|
|
|
|
|
|
return b.String()
|
|
|
|
}
|
|
|
|
|
2024-02-22 08:09:41 +00:00
|
|
|
// createAttributes creates a slice of Prometheus Labels with OTLP attributes and pairs of string values.
|
|
|
|
// Unpaired string values are ignored. String pairs overwrite OTLP labels if collisions happen, and overwrites are
|
|
|
|
// logged. Resulting label names are sanitized.
|
2023-07-28 10:35:28 +00:00
|
|
|
func createAttributes(resource pcommon.Resource, attributes pcommon.Map, externalLabels map[string]string, extras ...string) []prompb.Label {
|
2023-11-15 14:09:15 +00:00
|
|
|
serviceName, haveServiceName := resource.Attributes().Get(conventions.AttributeServiceName)
|
|
|
|
instance, haveInstanceID := resource.Attributes().Get(conventions.AttributeServiceInstanceID)
|
|
|
|
|
|
|
|
// Calculate the maximum possible number of labels we could return so we can preallocate l
|
|
|
|
maxLabelCount := attributes.Len() + len(externalLabels) + len(extras)/2
|
|
|
|
|
|
|
|
if haveServiceName {
|
|
|
|
maxLabelCount++
|
|
|
|
}
|
|
|
|
|
|
|
|
if haveInstanceID {
|
|
|
|
maxLabelCount++
|
|
|
|
}
|
|
|
|
|
2023-07-28 10:35:28 +00:00
|
|
|
// map ensures no duplicate label name
|
2023-11-15 14:09:15 +00:00
|
|
|
l := make(map[string]string, maxLabelCount)
|
2023-07-28 10:35:28 +00:00
|
|
|
|
|
|
|
// Ensure attributes are sorted by key for consistent merging of keys which
|
|
|
|
// collide when sanitized.
|
|
|
|
labels := make([]prompb.Label, 0, attributes.Len())
|
|
|
|
attributes.Range(func(key string, value pcommon.Value) bool {
|
|
|
|
labels = append(labels, prompb.Label{Name: key, Value: value.AsString()})
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
sort.Stable(ByLabelName(labels))
|
|
|
|
|
|
|
|
for _, label := range labels {
|
2023-11-15 14:09:15 +00:00
|
|
|
var finalKey = prometheustranslator.NormalizeLabel(label.Name)
|
2024-02-22 08:09:41 +00:00
|
|
|
if existingValue, alreadyExists := l[finalKey]; alreadyExists {
|
|
|
|
l[finalKey] = existingValue + ";" + label.Value
|
2023-07-28 10:35:28 +00:00
|
|
|
} else {
|
2023-11-15 14:09:15 +00:00
|
|
|
l[finalKey] = label.Value
|
2023-07-28 10:35:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Map service.name + service.namespace to job
|
2023-11-15 14:09:15 +00:00
|
|
|
if haveServiceName {
|
2023-07-28 10:35:28 +00:00
|
|
|
val := serviceName.AsString()
|
|
|
|
if serviceNamespace, ok := resource.Attributes().Get(conventions.AttributeServiceNamespace); ok {
|
|
|
|
val = fmt.Sprintf("%s/%s", serviceNamespace.AsString(), val)
|
|
|
|
}
|
2023-11-15 14:09:15 +00:00
|
|
|
l[model.JobLabel] = val
|
2023-07-28 10:35:28 +00:00
|
|
|
}
|
|
|
|
// Map service.instance.id to instance
|
2023-11-15 14:09:15 +00:00
|
|
|
if haveInstanceID {
|
|
|
|
l[model.InstanceLabel] = instance.AsString()
|
2023-07-28 10:35:28 +00:00
|
|
|
}
|
|
|
|
for key, value := range externalLabels {
|
|
|
|
// External labels have already been sanitized
|
|
|
|
if _, alreadyExists := l[key]; alreadyExists {
|
|
|
|
// Skip external labels if they are overridden by metric attributes
|
|
|
|
continue
|
|
|
|
}
|
2023-11-15 14:09:15 +00:00
|
|
|
l[key] = value
|
2023-07-28 10:35:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < len(extras); i += 2 {
|
|
|
|
if i+1 >= len(extras) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
_, found := l[extras[i]]
|
|
|
|
if found {
|
|
|
|
log.Println("label " + extras[i] + " is overwritten. Check if Prometheus reserved labels are used.")
|
|
|
|
}
|
|
|
|
// internal labels should be maintained
|
|
|
|
name := extras[i]
|
|
|
|
if !(len(name) > 4 && name[:2] == "__" && name[len(name)-2:] == "__") {
|
|
|
|
name = prometheustranslator.NormalizeLabel(name)
|
|
|
|
}
|
2023-11-15 14:09:15 +00:00
|
|
|
l[name] = extras[i+1]
|
2023-07-28 10:35:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
s := make([]prompb.Label, 0, len(l))
|
2023-11-15 14:09:15 +00:00
|
|
|
for k, v := range l {
|
|
|
|
s = append(s, prompb.Label{Name: k, Value: v})
|
2023-07-28 10:35:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
// isValidAggregationTemporality checks whether an OTel metric has a valid
|
|
|
|
// aggregation temporality for conversion to a Prometheus metric.
|
|
|
|
func isValidAggregationTemporality(metric pmetric.Metric) bool {
|
2023-11-15 14:09:15 +00:00
|
|
|
//exhaustive:enforce
|
2023-07-28 10:35:28 +00:00
|
|
|
switch metric.Type() {
|
|
|
|
case pmetric.MetricTypeGauge, pmetric.MetricTypeSummary:
|
|
|
|
return true
|
|
|
|
case pmetric.MetricTypeSum:
|
|
|
|
return metric.Sum().AggregationTemporality() == pmetric.AggregationTemporalityCumulative
|
|
|
|
case pmetric.MetricTypeHistogram:
|
|
|
|
return metric.Histogram().AggregationTemporality() == pmetric.AggregationTemporalityCumulative
|
|
|
|
case pmetric.MetricTypeExponentialHistogram:
|
|
|
|
return metric.ExponentialHistogram().AggregationTemporality() == pmetric.AggregationTemporalityCumulative
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// addSingleHistogramDataPoint converts pt to 2 + min(len(ExplicitBounds), len(BucketCount)) + 1 samples. It
|
|
|
|
// ignore extra buckets if len(ExplicitBounds) > len(BucketCounts)
|
2024-02-22 08:09:41 +00:00
|
|
|
func addSingleHistogramDataPoint(pt pmetric.HistogramDataPoint, resource pcommon.Resource, metric pmetric.Metric, settings Settings, tsMap map[string]*prompb.TimeSeries, baseName string) {
|
2023-07-28 10:35:28 +00:00
|
|
|
timestamp := convertTimeStamp(pt.Timestamp())
|
2023-11-15 14:09:15 +00:00
|
|
|
baseLabels := createAttributes(resource, pt.Attributes(), settings.ExternalLabels)
|
|
|
|
|
|
|
|
createLabels := func(nameSuffix string, extras ...string) []prompb.Label {
|
|
|
|
extraLabelCount := len(extras) / 2
|
|
|
|
labels := make([]prompb.Label, len(baseLabels), len(baseLabels)+extraLabelCount+1) // +1 for name
|
|
|
|
copy(labels, baseLabels)
|
|
|
|
|
|
|
|
for extrasIdx := 0; extrasIdx < extraLabelCount; extrasIdx++ {
|
|
|
|
labels = append(labels, prompb.Label{Name: extras[extrasIdx], Value: extras[extrasIdx+1]})
|
|
|
|
}
|
|
|
|
|
2024-02-22 08:09:41 +00:00
|
|
|
// sum, count, and buckets of the histogram should append suffix to baseName
|
|
|
|
labels = append(labels, prompb.Label{Name: model.MetricNameLabel, Value: baseName + nameSuffix})
|
2023-11-15 14:09:15 +00:00
|
|
|
|
|
|
|
return labels
|
|
|
|
}
|
2023-07-28 10:35:28 +00:00
|
|
|
|
|
|
|
// If the sum is unset, it indicates the _sum metric point should be
|
|
|
|
// omitted
|
|
|
|
if pt.HasSum() {
|
|
|
|
// treat sum as a sample in an individual TimeSeries
|
|
|
|
sum := &prompb.Sample{
|
|
|
|
Value: pt.Sum(),
|
|
|
|
Timestamp: timestamp,
|
|
|
|
}
|
|
|
|
if pt.Flags().NoRecordedValue() {
|
|
|
|
sum.Value = math.Float64frombits(value.StaleNaN)
|
|
|
|
}
|
|
|
|
|
2023-11-15 14:09:15 +00:00
|
|
|
sumlabels := createLabels(sumStr)
|
2023-07-28 10:35:28 +00:00
|
|
|
addSample(tsMap, sum, sumlabels, metric.Type().String())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// treat count as a sample in an individual TimeSeries
|
|
|
|
count := &prompb.Sample{
|
|
|
|
Value: float64(pt.Count()),
|
|
|
|
Timestamp: timestamp,
|
|
|
|
}
|
|
|
|
if pt.Flags().NoRecordedValue() {
|
|
|
|
count.Value = math.Float64frombits(value.StaleNaN)
|
|
|
|
}
|
|
|
|
|
2023-11-15 14:09:15 +00:00
|
|
|
countlabels := createLabels(countStr)
|
2023-07-28 10:35:28 +00:00
|
|
|
addSample(tsMap, count, countlabels, metric.Type().String())
|
|
|
|
|
|
|
|
// cumulative count for conversion to cumulative histogram
|
|
|
|
var cumulativeCount uint64
|
|
|
|
|
|
|
|
promExemplars := getPromExemplars[pmetric.HistogramDataPoint](pt)
|
|
|
|
|
|
|
|
var bucketBounds []bucketBoundsData
|
|
|
|
|
|
|
|
// process each bound, based on histograms proto definition, # of buckets = # of explicit bounds + 1
|
|
|
|
for i := 0; i < pt.ExplicitBounds().Len() && i < pt.BucketCounts().Len(); i++ {
|
|
|
|
bound := pt.ExplicitBounds().At(i)
|
|
|
|
cumulativeCount += pt.BucketCounts().At(i)
|
|
|
|
bucket := &prompb.Sample{
|
|
|
|
Value: float64(cumulativeCount),
|
|
|
|
Timestamp: timestamp,
|
|
|
|
}
|
|
|
|
if pt.Flags().NoRecordedValue() {
|
|
|
|
bucket.Value = math.Float64frombits(value.StaleNaN)
|
|
|
|
}
|
|
|
|
boundStr := strconv.FormatFloat(bound, 'f', -1, 64)
|
2023-11-15 14:09:15 +00:00
|
|
|
labels := createLabels(bucketStr, leStr, boundStr)
|
2023-07-28 10:35:28 +00:00
|
|
|
sig := addSample(tsMap, bucket, labels, metric.Type().String())
|
|
|
|
|
|
|
|
bucketBounds = append(bucketBounds, bucketBoundsData{sig: sig, bound: bound})
|
|
|
|
}
|
|
|
|
// add le=+Inf bucket
|
|
|
|
infBucket := &prompb.Sample{
|
|
|
|
Timestamp: timestamp,
|
|
|
|
}
|
|
|
|
if pt.Flags().NoRecordedValue() {
|
|
|
|
infBucket.Value = math.Float64frombits(value.StaleNaN)
|
|
|
|
} else {
|
|
|
|
infBucket.Value = float64(pt.Count())
|
|
|
|
}
|
2023-11-15 14:09:15 +00:00
|
|
|
infLabels := createLabels(bucketStr, leStr, pInfStr)
|
2023-07-28 10:35:28 +00:00
|
|
|
sig := addSample(tsMap, infBucket, infLabels, metric.Type().String())
|
|
|
|
|
|
|
|
bucketBounds = append(bucketBounds, bucketBoundsData{sig: sig, bound: math.Inf(1)})
|
|
|
|
addExemplars(tsMap, promExemplars, bucketBounds)
|
|
|
|
|
|
|
|
// add _created time series if needed
|
|
|
|
startTimestamp := pt.StartTimestamp()
|
|
|
|
if settings.ExportCreatedMetric && startTimestamp != 0 {
|
2023-11-15 14:09:15 +00:00
|
|
|
labels := createLabels(createdSuffix)
|
2024-02-22 08:09:41 +00:00
|
|
|
addCreatedTimeSeriesIfNeeded(tsMap, labels, startTimestamp, pt.Timestamp(), metric.Type().String())
|
2023-07-28 10:35:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type exemplarType interface {
|
|
|
|
pmetric.ExponentialHistogramDataPoint | pmetric.HistogramDataPoint | pmetric.NumberDataPoint
|
|
|
|
Exemplars() pmetric.ExemplarSlice
|
|
|
|
}
|
|
|
|
|
|
|
|
func getPromExemplars[T exemplarType](pt T) []prompb.Exemplar {
|
2024-02-22 08:09:41 +00:00
|
|
|
promExemplars := make([]prompb.Exemplar, 0, pt.Exemplars().Len())
|
2023-07-28 10:35:28 +00:00
|
|
|
for i := 0; i < pt.Exemplars().Len(); i++ {
|
|
|
|
exemplar := pt.Exemplars().At(i)
|
|
|
|
exemplarRunes := 0
|
|
|
|
|
2024-02-22 08:09:41 +00:00
|
|
|
promExemplar := prompb.Exemplar{
|
2023-07-28 10:35:28 +00:00
|
|
|
Value: exemplar.DoubleValue(),
|
|
|
|
Timestamp: timestamp.FromTime(exemplar.Timestamp().AsTime()),
|
|
|
|
}
|
|
|
|
if traceID := exemplar.TraceID(); !traceID.IsEmpty() {
|
|
|
|
val := hex.EncodeToString(traceID[:])
|
|
|
|
exemplarRunes += utf8.RuneCountInString(traceIDKey) + utf8.RuneCountInString(val)
|
|
|
|
promLabel := prompb.Label{
|
|
|
|
Name: traceIDKey,
|
|
|
|
Value: val,
|
|
|
|
}
|
|
|
|
promExemplar.Labels = append(promExemplar.Labels, promLabel)
|
|
|
|
}
|
|
|
|
if spanID := exemplar.SpanID(); !spanID.IsEmpty() {
|
|
|
|
val := hex.EncodeToString(spanID[:])
|
|
|
|
exemplarRunes += utf8.RuneCountInString(spanIDKey) + utf8.RuneCountInString(val)
|
|
|
|
promLabel := prompb.Label{
|
|
|
|
Name: spanIDKey,
|
|
|
|
Value: val,
|
|
|
|
}
|
|
|
|
promExemplar.Labels = append(promExemplar.Labels, promLabel)
|
|
|
|
}
|
|
|
|
|
2024-02-22 08:09:41 +00:00
|
|
|
attrs := exemplar.FilteredAttributes()
|
|
|
|
labelsFromAttributes := make([]prompb.Label, 0, attrs.Len())
|
|
|
|
attrs.Range(func(key string, value pcommon.Value) bool {
|
2023-07-28 10:35:28 +00:00
|
|
|
val := value.AsString()
|
|
|
|
exemplarRunes += utf8.RuneCountInString(key) + utf8.RuneCountInString(val)
|
|
|
|
promLabel := prompb.Label{
|
|
|
|
Name: key,
|
|
|
|
Value: val,
|
|
|
|
}
|
|
|
|
|
|
|
|
labelsFromAttributes = append(labelsFromAttributes, promLabel)
|
|
|
|
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
if exemplarRunes <= maxExemplarRunes {
|
|
|
|
// only append filtered attributes if it does not cause exemplar
|
|
|
|
// labels to exceed the max number of runes
|
|
|
|
promExemplar.Labels = append(promExemplar.Labels, labelsFromAttributes...)
|
|
|
|
}
|
|
|
|
|
2024-02-22 08:09:41 +00:00
|
|
|
promExemplars = append(promExemplars, promExemplar)
|
2023-07-28 10:35:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return promExemplars
|
|
|
|
}
|
|
|
|
|
|
|
|
// mostRecentTimestampInMetric returns the latest timestamp in a batch of metrics
|
|
|
|
func mostRecentTimestampInMetric(metric pmetric.Metric) pcommon.Timestamp {
|
|
|
|
var ts pcommon.Timestamp
|
|
|
|
// handle individual metric based on type
|
2023-11-15 14:09:15 +00:00
|
|
|
//exhaustive:enforce
|
2023-07-28 10:35:28 +00:00
|
|
|
switch metric.Type() {
|
|
|
|
case pmetric.MetricTypeGauge:
|
|
|
|
dataPoints := metric.Gauge().DataPoints()
|
|
|
|
for x := 0; x < dataPoints.Len(); x++ {
|
|
|
|
ts = maxTimestamp(ts, dataPoints.At(x).Timestamp())
|
|
|
|
}
|
|
|
|
case pmetric.MetricTypeSum:
|
|
|
|
dataPoints := metric.Sum().DataPoints()
|
|
|
|
for x := 0; x < dataPoints.Len(); x++ {
|
|
|
|
ts = maxTimestamp(ts, dataPoints.At(x).Timestamp())
|
|
|
|
}
|
|
|
|
case pmetric.MetricTypeHistogram:
|
|
|
|
dataPoints := metric.Histogram().DataPoints()
|
|
|
|
for x := 0; x < dataPoints.Len(); x++ {
|
|
|
|
ts = maxTimestamp(ts, dataPoints.At(x).Timestamp())
|
|
|
|
}
|
|
|
|
case pmetric.MetricTypeExponentialHistogram:
|
|
|
|
dataPoints := metric.ExponentialHistogram().DataPoints()
|
|
|
|
for x := 0; x < dataPoints.Len(); x++ {
|
|
|
|
ts = maxTimestamp(ts, dataPoints.At(x).Timestamp())
|
|
|
|
}
|
|
|
|
case pmetric.MetricTypeSummary:
|
|
|
|
dataPoints := metric.Summary().DataPoints()
|
|
|
|
for x := 0; x < dataPoints.Len(); x++ {
|
|
|
|
ts = maxTimestamp(ts, dataPoints.At(x).Timestamp())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ts
|
|
|
|
}
|
|
|
|
|
|
|
|
func maxTimestamp(a, b pcommon.Timestamp) pcommon.Timestamp {
|
2024-04-22 10:03:19 +00:00
|
|
|
return max(a, b)
|
2023-07-28 10:35:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// addSingleSummaryDataPoint converts pt to len(QuantileValues) + 2 samples.
|
|
|
|
func addSingleSummaryDataPoint(pt pmetric.SummaryDataPoint, resource pcommon.Resource, metric pmetric.Metric, settings Settings,
|
2024-02-22 08:09:41 +00:00
|
|
|
tsMap map[string]*prompb.TimeSeries, baseName string) {
|
2023-07-28 10:35:28 +00:00
|
|
|
timestamp := convertTimeStamp(pt.Timestamp())
|
2023-11-15 14:09:15 +00:00
|
|
|
baseLabels := createAttributes(resource, pt.Attributes(), settings.ExternalLabels)
|
|
|
|
|
|
|
|
createLabels := func(name string, extras ...string) []prompb.Label {
|
|
|
|
extraLabelCount := len(extras) / 2
|
|
|
|
labels := make([]prompb.Label, len(baseLabels), len(baseLabels)+extraLabelCount+1) // +1 for name
|
|
|
|
copy(labels, baseLabels)
|
|
|
|
|
|
|
|
for extrasIdx := 0; extrasIdx < extraLabelCount; extrasIdx++ {
|
|
|
|
labels = append(labels, prompb.Label{Name: extras[extrasIdx], Value: extras[extrasIdx+1]})
|
|
|
|
}
|
|
|
|
|
2024-02-22 08:09:41 +00:00
|
|
|
labels = append(labels, prompb.Label{Name: model.MetricNameLabel, Value: name})
|
2023-11-15 14:09:15 +00:00
|
|
|
|
|
|
|
return labels
|
|
|
|
}
|
|
|
|
|
2023-07-28 10:35:28 +00:00
|
|
|
// treat sum as a sample in an individual TimeSeries
|
|
|
|
sum := &prompb.Sample{
|
|
|
|
Value: pt.Sum(),
|
|
|
|
Timestamp: timestamp,
|
|
|
|
}
|
|
|
|
if pt.Flags().NoRecordedValue() {
|
|
|
|
sum.Value = math.Float64frombits(value.StaleNaN)
|
|
|
|
}
|
2024-02-22 08:09:41 +00:00
|
|
|
// sum and count of the summary should append suffix to baseName
|
2023-11-15 14:09:15 +00:00
|
|
|
sumlabels := createLabels(baseName + sumStr)
|
2023-07-28 10:35:28 +00:00
|
|
|
addSample(tsMap, sum, sumlabels, metric.Type().String())
|
|
|
|
|
|
|
|
// treat count as a sample in an individual TimeSeries
|
|
|
|
count := &prompb.Sample{
|
|
|
|
Value: float64(pt.Count()),
|
|
|
|
Timestamp: timestamp,
|
|
|
|
}
|
|
|
|
if pt.Flags().NoRecordedValue() {
|
|
|
|
count.Value = math.Float64frombits(value.StaleNaN)
|
|
|
|
}
|
2023-11-15 14:09:15 +00:00
|
|
|
countlabels := createLabels(baseName + countStr)
|
2023-07-28 10:35:28 +00:00
|
|
|
addSample(tsMap, count, countlabels, metric.Type().String())
|
|
|
|
|
|
|
|
// process each percentile/quantile
|
|
|
|
for i := 0; i < pt.QuantileValues().Len(); i++ {
|
|
|
|
qt := pt.QuantileValues().At(i)
|
|
|
|
quantile := &prompb.Sample{
|
|
|
|
Value: qt.Value(),
|
|
|
|
Timestamp: timestamp,
|
|
|
|
}
|
|
|
|
if pt.Flags().NoRecordedValue() {
|
|
|
|
quantile.Value = math.Float64frombits(value.StaleNaN)
|
|
|
|
}
|
|
|
|
percentileStr := strconv.FormatFloat(qt.Quantile(), 'f', -1, 64)
|
2023-11-15 14:09:15 +00:00
|
|
|
qtlabels := createLabels(baseName, quantileStr, percentileStr)
|
2023-07-28 10:35:28 +00:00
|
|
|
addSample(tsMap, quantile, qtlabels, metric.Type().String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// add _created time series if needed
|
|
|
|
startTimestamp := pt.StartTimestamp()
|
|
|
|
if settings.ExportCreatedMetric && startTimestamp != 0 {
|
2023-11-15 14:09:15 +00:00
|
|
|
createdLabels := createLabels(baseName + createdSuffix)
|
2024-02-22 08:09:41 +00:00
|
|
|
addCreatedTimeSeriesIfNeeded(tsMap, createdLabels, startTimestamp, pt.Timestamp(), metric.Type().String())
|
2023-07-28 10:35:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// addCreatedTimeSeriesIfNeeded adds {name}_created time series with a single
|
|
|
|
// sample. If the series exists, then new samples won't be added.
|
|
|
|
func addCreatedTimeSeriesIfNeeded(
|
|
|
|
series map[string]*prompb.TimeSeries,
|
|
|
|
labels []prompb.Label,
|
|
|
|
startTimestamp pcommon.Timestamp,
|
2024-02-22 08:09:41 +00:00
|
|
|
timestamp pcommon.Timestamp,
|
2023-07-28 10:35:28 +00:00
|
|
|
metricType string,
|
|
|
|
) {
|
2024-02-22 08:09:41 +00:00
|
|
|
sig := timeSeriesSignature(metricType, labels)
|
2023-07-28 10:35:28 +00:00
|
|
|
if _, ok := series[sig]; !ok {
|
|
|
|
series[sig] = &prompb.TimeSeries{
|
|
|
|
Labels: labels,
|
|
|
|
Samples: []prompb.Sample{
|
|
|
|
{ // convert ns to ms
|
2024-02-22 08:09:41 +00:00
|
|
|
Value: float64(convertTimeStamp(startTimestamp)),
|
|
|
|
Timestamp: convertTimeStamp(timestamp),
|
2023-07-28 10:35:28 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// addResourceTargetInfo converts the resource to the target info metric
|
|
|
|
func addResourceTargetInfo(resource pcommon.Resource, settings Settings, timestamp pcommon.Timestamp, tsMap map[string]*prompb.TimeSeries) {
|
|
|
|
if settings.DisableTargetInfo {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// Use resource attributes (other than those used for job+instance) as the
|
|
|
|
// metric labels for the target info metric
|
|
|
|
attributes := pcommon.NewMap()
|
|
|
|
resource.Attributes().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
|
|
|
|
}
|
|
|
|
})
|
|
|
|
if attributes.Len() == 0 {
|
|
|
|
// If we only have job + instance, then target_info isn't useful, so don't add it.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// create parameters for addSample
|
|
|
|
name := targetMetricName
|
|
|
|
if len(settings.Namespace) > 0 {
|
|
|
|
name = settings.Namespace + "_" + name
|
|
|
|
}
|
2024-02-22 08:09:41 +00:00
|
|
|
labels := createAttributes(resource, attributes, settings.ExternalLabels, model.MetricNameLabel, name)
|
2023-07-28 10:35:28 +00:00
|
|
|
sample := &prompb.Sample{
|
|
|
|
Value: float64(1),
|
|
|
|
// convert ns to ms
|
|
|
|
Timestamp: convertTimeStamp(timestamp),
|
|
|
|
}
|
|
|
|
addSample(tsMap, sample, labels, infoType)
|
|
|
|
}
|
|
|
|
|
|
|
|
// convertTimeStamp converts OTLP timestamp in ns to timestamp in ms
|
|
|
|
func convertTimeStamp(timestamp pcommon.Timestamp) int64 {
|
|
|
|
return timestamp.AsTime().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond))
|
|
|
|
}
|