@ -1,29 +1,42 @@
// DO NOT EDIT. COPIED AS-IS. SEE ../README.md
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Provenance-includes-location: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/95e8f8fdc2a9dc87230406c9a3cf02be4fd68bea/pkg/translator/prometheusremotewrite/helper.go
// Provenance-includes-license: Apache-2.0
// Provenance-includes-copyright: Copyright The OpenTelemetry Authors.
package prometheusremotewrite // import "github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheusremotewrite"
package prometheusremotewrite
import (
"encoding/hex"
"fmt"
"log"
"math"
"slices"
"sort"
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/cespare/xxhash/v2"
"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"
"github.com/prometheus/prometheus/model/timestamp"
"github.com/prometheus/prometheus/model/value"
"github.com/prometheus/prometheus/prompb"
prometheustranslator "github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheus"
)
@ -48,7 +61,7 @@ const (
)
type bucketBoundsData struct {
sig string
ts * prompb . TimeSeries
bound float64
}
@ -66,94 +79,47 @@ 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 ,
datatype string ) string {
if sample == nil || labels == nil || tsMap == nil {
// This shouldn't happen
return ""
}
// timeSeriesSignature returns a hashed label set signature.
// The label slice should not contain duplicate label names; this method sorts the slice by label name before creating
// the signature.
// The algorithm is the same as in Prometheus' labels.StableHash function.
func timeSeriesSignature ( labels [ ] prompb . Label ) uint64 {
sort . Sort ( ByLabelName ( labels ) )
sig := timeSeriesSignature ( datatype , labels )
ts := tsMap [ sig ]
if ts != nil {
ts . Samples = append ( ts . Samples , * sample )
} else {
newTs := & prompb . TimeSeries {
Labels : labels ,
Samples : [ ] prompb . Sample { * sample } ,
// Use xxhash.Sum64(b) for fast path as it's faster.
b := make ( [ ] byte , 0 , 1024 )
for i , v := range labels {
if len ( b ) + len ( v . Name ) + len ( v . Value ) + 2 >= cap ( b ) {
// If labels entry is 1KB+ do not allocate whole entry.
h := xxhash . New ( )
_ , _ = h . Write ( b )
for _ , v := range labels [ i : ] {
_ , _ = h . WriteString ( v . Name )
_ , _ = h . Write ( seps )
_ , _ = h . WriteString ( v . Value )
_ , _ = h . Write ( seps )
}
return h . Sum64 ( )
}
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 ) {
if len ( tsMap ) == 0 || len ( bucketBoundsData ) == 0 || len ( exemplars ) == 0 {
return
}
sort . Sort ( byBucketBoundsData ( bucketBoundsData ) )
for _ , exemplar := range exemplars {
addExemplar ( tsMap , bucketBoundsData , exemplar )
b = append ( b , v . Name ... )
b = append ( b , seps [ 0 ] )
b = append ( b , v . Value ... )
b = append ( b , seps [ 0 ] )
}
return xxhash . Sum64 ( b )
}
func addExemplar ( tsMap map [ string ] * prompb . TimeSeries , bucketBounds [ ] bucketBoundsData , exemplar prompb . Exemplar ) {
for _ , bucketBound := range bucketBounds {
sig := bucketBound . sig
bound := bucketBound . bound
ts := tsMap [ sig ]
if ts != nil && len ( ts . Samples ) > 0 && exemplar . Value <= bound {
ts . Exemplars = append ( ts . Exemplars , exemplar )
return
}
}
}
// 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.
func timeSeriesSignature ( datatype string , labels [ ] prompb . Label ) string {
length := len ( datatype )
for _ , lb := range labels {
length += 2 + len ( lb . GetName ( ) ) + len ( lb . GetValue ( ) )
}
b := strings . Builder { }
b . Grow ( length )
b . WriteString ( datatype )
sort . Sort ( ByLabelName ( labels ) )
for _ , lb := range labels {
b . WriteString ( "-" )
b . WriteString ( lb . GetName ( ) )
b . WriteString ( "-" )
b . WriteString ( lb . GetValue ( ) )
}
return b . String ( )
}
var seps = [ ] byte { '\xff' }
// 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.
func createAttributes ( resource pcommon . Resource , attributes pcommon . Map , externalLabels map [ string ] string , extras ... string ) [ ] prompb . Label {
serviceName , haveServiceName := resource . Attributes ( ) . Get ( conventions . AttributeServiceName )
instance , haveInstanceID := resource . Attributes ( ) . Get ( conventions . AttributeServiceInstanceID )
// Unpaired string values are ignored. String pairs overwrite OTLP labels if collisions happen and
// if logOnOverwrite is true, the overwrite is logged. Resulting label names are sanitized.
func createAttributes ( resource pcommon . Resource , attributes pcommon . Map , externalLabels map [ string ] string ,
ignoreAttrs [ ] string , logOnOverwrite bool , extras ... string ) [ ] prompb . Label {
resourceAttrs := resource . Attributes ( )
serviceName , haveServiceName := resourceAttrs . Get ( conventions . AttributeServiceName )
instance , haveInstanceID := resourceAttrs . 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
@ -171,9 +137,13 @@ func createAttributes(resource pcommon.Resource, attributes pcommon.Map, externa
// Ensure attributes are sorted by key for consistent merging of keys which
// collide when sanitized.
labels := make ( [ ] prompb . Label , 0 , attributes . Len ( ) )
labels := make ( [ ] prompb . Label , 0 , maxLabelCount )
// XXX: Should we always drop service namespace/service name/service instance ID from the labels
// (as they get mapped to other Prometheus labels)?
attributes . Range ( func ( key string , value pcommon . Value ) bool {
labels = append ( labels , prompb . Label { Name : key , Value : value . AsString ( ) } )
if ! slices . Contains ( ignoreAttrs , key ) {
labels = append ( labels , prompb . Label { Name : key , Value : value . AsString ( ) } )
}
return true
} )
sort . Stable ( ByLabelName ( labels ) )
@ -190,7 +160,7 @@ func createAttributes(resource pcommon.Resource, attributes pcommon.Map, externa
// Map service.name + service.namespace to job
if haveServiceName {
val := serviceName . AsString ( )
if serviceNamespace , ok := resource . Attribute s ( ) . Get ( conventions . AttributeServiceNamespace ) ; ok {
if serviceNamespace , ok := resourceAttrs . Get ( conventions . AttributeServiceNamespace ) ; ok {
val = fmt . Sprintf ( "%s/%s" , serviceNamespace . AsString ( ) , val )
}
l [ model . JobLabel ] = val
@ -213,7 +183,7 @@ func createAttributes(resource pcommon.Resource, attributes pcommon.Map, externa
break
}
_ , found := l [ extras [ i ] ]
if found {
if found && logOnOverwrite {
log . Println ( "label " + extras [ i ] + " is overwritten. Check if Prometheus reserved labels are used." )
}
// internal labels should be maintained
@ -224,12 +194,12 @@ func createAttributes(resource pcommon.Resource, attributes pcommon.Map, externa
l [ name ] = extras [ i + 1 ]
}
s := make ( [ ] prompb . Label , 0 , len ( l ) )
labels = labels [ : 0 ]
for k , v := range l {
s = append ( s , prompb . Label { Name : k , Value : v } )
label s = append ( label s, prompb . Label { Name : k , Value : v } )
}
return s
return label s
}
// isValidAggregationTemporality checks whether an OTel metric has a valid
@ -249,100 +219,84 @@ func isValidAggregationTemporality(metric pmetric.Metric) bool {
return false
}
// addSingleHistogramDataPoint converts pt to 2 + min(len(ExplicitBounds), len(BucketCount)) + 1 samples. It
// ignore extra buckets if len(ExplicitBounds) > len(BucketCounts)
func addSingleHistogramDataPoint ( pt pmetric . HistogramDataPoint , resource pcommon . Resource , metric pmetric . Metric , settings Settings , tsMap map [ string ] * prompb . TimeSeries , baseName string ) {
timestamp := convertTimeStamp ( pt . Timestamp ( ) )
baseLabels := createAttributes ( resource , pt . Attributes ( ) , settings . ExternalLabels )
func ( c * prometheusConverter ) addHistogramDataPoints ( dataPoints pmetric . HistogramDataPointSlice ,
resource pcommon . Resource , settings Settings , baseName string ) {
for x := 0 ; x < dataPoints . Len ( ) ; x ++ {
pt := dataPoints . At ( x )
timestamp := convertTimeStamp ( pt . Timestamp ( ) )
baseLabels := createAttributes ( resource , pt . Attributes ( ) , settings . ExternalLabels , nil , false )
// 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 )
}
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 )
sumlabels := createLabels ( baseName + sumStr , baseLabels )
c . addSample ( sum , sumlabels )
for extrasIdx := 0 ; extrasIdx < extraLabelCount ; extrasIdx ++ {
labels = append ( labels , prompb . Label { Name : extras [ extrasIdx ] , Value : extras [ extrasIdx + 1 ] } )
}
// sum, count, and buckets of the histogram should append suffix to baseName
labels = append ( labels , prompb . Label { Name : model . MetricNameLabel , Value : baseName + nameSuffix } )
return labels
}
// 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 ( ) ,
// treat count as a sample in an individual TimeSeries
count := & prompb . Sample {
Value : float64 ( pt . Count ( ) ) ,
Timestamp : timestamp ,
}
if pt . Flags ( ) . NoRecordedValue ( ) {
sum . Value = math . Float64frombits ( value . StaleNaN )
count . Value = math . Float64frombits ( value . StaleNaN )
}
sumlabels := createLabels ( sumStr )
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 )
}
countlabels := createLabels ( countStr )
addSample ( tsMap , count , countlabels , metric . Type ( ) . String ( ) )
countlabels := createLabels ( baseName + countStr , baseLabels )
c . addSample ( count , countlabels )
// cumulative count for conversion to cumulative histogram
var cumulativeCount uint64
// cumulative count for conversion to cumulative histogram
var cumulativeCount uint64
promExemplars := getPromExemplars [ pmetric . HistogramDataPoint ] ( pt )
var bucketBounds [ ] bucketBoundsData
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 )
labels := createLabels ( baseName + bucketStr , baseLabels , leStr , boundStr )
ts := c . addSample ( bucket , labels )
// 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 ) ,
bucketBounds = append ( bucketBounds , bucketBoundsData { ts : ts , bound : bound } )
}
// add le=+Inf bucket
infBucket := & prompb . Sample {
Timestamp : timestamp ,
}
if pt . Flags ( ) . NoRecordedValue ( ) {
bucket . Value = math . Float64frombits ( value . StaleNaN )
infBucket . Value = math . Float64frombits ( value . StaleNaN )
} else {
infBucket . Value = float64 ( pt . Count ( ) )
}
boundStr := strconv . FormatFloat ( bound , 'f' , - 1 , 64 )
labels := createLabels ( bucketStr , leStr , boundStr )
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 ( ) )
}
infLabels := createLabels ( bucketStr , leStr , pInfStr )
sig := addSample ( tsMap , infBucket , infLabels , metric . Type ( ) . String ( ) )
infLabels := createLabels ( baseName + bucketStr , baseLabels , leStr , pInfStr )
ts := c . addSample ( infBucket , infLabels )
bucketBounds = append ( bucketBounds , bucketBoundsData { sig : sig , bound : math . Inf ( 1 ) } )
addExemplars ( tsMap , promExemplars , bucketBounds )
bucketBounds = append ( bucketBounds , bucketBoundsData { ts : ts , bound : math . Inf ( 1 ) } )
c . addExemplars ( pt , bucketBounds )
// add _created time series if needed
startTimestamp := pt . StartTimestamp ( )
if settings . ExportCreatedMetric && startTimestamp != 0 {
labels := createLabels ( createdSuffix )
addCreatedTimeSeriesIfNeeded ( tsMap , labels , startTimestamp , pt . Timestamp ( ) , metric . Type ( ) . String ( ) )
startTimestamp := pt . StartTimestamp ( )
if settings . ExportCreatedMetric && startTimestamp != 0 {
labels := createLabels ( baseName + createdSuffix , baseLabels )
c . addTimeSeriesIfNeeded ( labels , startTimestamp , pt . Timestamp ( ) )
}
}
}
@ -441,129 +395,177 @@ func mostRecentTimestampInMetric(metric pmetric.Metric) pcommon.Timestamp {
return ts
}
// addSingleSummaryDataPoint converts pt to len(QuantileValues) + 2 samples.
func addSingleSummaryDataPoint ( pt pmetric . SummaryDataPoint , resource pcommon . Resource , metric pmetric . Metric , settings Settings ,
tsMap map [ string ] * prompb . TimeSeries , baseName string ) {
timestamp := convertTimeStamp ( pt . Timestamp ( ) )
baseLabels := createAttributes ( resource , pt . Attributes ( ) , settings . ExternalLabels )
func ( c * prometheusConverter ) addSummaryDataPoints ( dataPoints pmetric . SummaryDataPointSlice , resource pcommon . Resource ,
settings Settings , baseName string ) {
for x := 0 ; x < dataPoints . Len ( ) ; x ++ {
pt := dataPoints . At ( x )
timestamp := convertTimeStamp ( pt . Timestamp ( ) )
baseLabels := createAttributes ( resource , pt . Attributes ( ) , settings . ExternalLabels , nil , false )
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 ] } )
// 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 )
}
// sum and count of the summary should append suffix to baseName
sumlabels := createLabels ( baseName + sumStr , baseLabels )
c . addSample ( sum , sumlabels )
labels = append ( labels , prompb . Label { Name : model . MetricNameLabel , Value : name } )
// 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 )
}
countlabels := createLabels ( baseName + countStr , baseLabels )
c . addSample ( count , countlabels )
// 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 )
qtlabels := createLabels ( baseName , baseLabels , quantileStr , percentileStr )
c . addSample ( quantile , qtlabels )
}
return labels
startTimestamp := pt . StartTimestamp ( )
if settings . ExportCreatedMetric && startTimestamp != 0 {
createdLabels := createLabels ( baseName + createdSuffix , baseLabels )
c . addTimeSeriesIfNeeded ( createdLabels , startTimestamp , pt . Timestamp ( ) )
}
}
}
// 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 )
}
// sum and count of the summary should append suffix to baseName
sumlabels := createLabels ( baseName + sumStr )
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 )
// createLabels returns a copy of baseLabels, adding to it the pair model.MetricNameLabel=name.
// If extras are provided, corresponding label pairs are also added to the returned slice.
// If extras is uneven length, the last (unpaired) extra will be ignored.
func createLabels ( name string , baseLabels [ ] prompb . Label , extras ... string ) [ ] prompb . Label {
extraLabelCount := len ( extras ) / 2
labels := make ( [ ] prompb . Label , len ( baseLabels ) , len ( baseLabels ) + extraLabelCount + 1 ) // +1 for name
copy ( labels , baseLabels )
n := len ( extras )
n -= n % 2
for extrasIdx := 0 ; extrasIdx < n ; extrasIdx += 2 {
labels = append ( labels , prompb . Label { Name : extras [ extrasIdx ] , Value : extras [ extrasIdx + 1 ] } )
}
countlabels := createLabels ( baseName + countStr )
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 ,
labels = append ( labels , prompb . Label { Name : model . MetricNameLabel , Value : name } )
return labels
}
// getOrCreateTimeSeries returns the time series corresponding to the label set if existent, and false.
// Otherwise it creates a new one and returns that, and true.
func ( c * prometheusConverter ) getOrCreateTimeSeries ( lbls [ ] prompb . Label ) ( * prompb . TimeSeries , bool ) {
h := timeSeriesSignature ( lbls )
ts := c . unique [ h ]
if ts != nil {
if isSameMetric ( ts , lbls ) {
// We already have this metric
return ts , false
}
if pt . Flags ( ) . NoRecordedValue ( ) {
quantile . Value = math . Float64frombits ( value . StaleNaN )
// Look for a matching conflict
for _ , cTS := range c . conflicts [ h ] {
if isSameMetric ( cTS , lbls ) {
// We already have this metric
return cTS , false
}
}
percentileStr := strconv . FormatFloat ( qt . Quantile ( ) , 'f' , - 1 , 64 )
qtlabels := createLabels ( baseName , quantileStr , percentileStr )
addSample ( tsMap , quantile , qtlabels , metric . Type ( ) . String ( ) )
// New conflict
ts = & prompb . TimeSeries {
Labels : lbls ,
}
c . conflicts [ h ] = append ( c . conflicts [ h ] , ts )
return ts , true
}
// add _created time series if needed
startTimestamp := pt . StartTimestamp ( )
if settings . ExportCreatedMetric && startTimestamp != 0 {
createdLabels := createLabels ( baseName + createdSuffix )
addCreatedTimeSeriesIfNeeded ( tsMap , createdLabels , startTimestamp , pt . Timestamp ( ) , metric . Type ( ) . String ( ) )
// This metric is new
ts = & prompb . TimeSeries {
Labels : lbls ,
}
c . unique [ h ] = ts
return ts , true
}
// 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 ,
timestamp pcommon . Timestamp ,
metricType string ,
) {
sig := timeSeriesSignature ( metricType , labels )
if _ , ok := series [ sig ] ; ! ok {
series [ sig ] = & prompb . TimeSeries {
Labels : labels ,
Samples : [ ] prompb . Sample {
{ // convert ns to ms
Value : float64 ( convertTimeStamp ( startTimestamp ) ) ,
Timestamp : convertTimeStamp ( timestamp ) ,
} ,
// addTimeSeriesIfNeeded adds a corresponding time series if it doesn't already exist.
// If the time series doesn't already exist, it gets added with startTimestamp for its value and timestamp for its timestamp,
// both converted to milliseconds.
func ( c * prometheusConverter ) addTimeSeriesIfNeeded ( lbls [ ] prompb . Label , startTimestamp pcommon . Timestamp , timestamp pcommon . Timestamp ) {
ts , created := c . getOrCreateTimeSeries ( lbls )
if created {
ts . Samples = [ ] prompb . Sample {
{
// convert ns to ms
Value : float64 ( convertTimeStamp ( startTimestamp ) ) ,
Timestamp : convertTimeStamp ( timestamp ) ,
} ,
}
}
}
// 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 {
// addResourceTargetInfo converts the resource to the target info metric.
func addResourceTargetInfo ( resource pcommon . Resource , settings Settings , timestamp pcommon . Timestamp , converter * prometheusConverter ) {
if settings . DisableTargetInfo || timestamp == 0 {
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
attributes := resource . Attributes ( )
identifyingAttrs := [ ] string {
conventions . AttributeServiceNamespace ,
conventions . AttributeServiceName ,
conventions . AttributeServiceInstanceID ,
}
nonIdentifyingAttrsCount := attributes . Len ( )
for _ , a := range identifyingAttrs {
_ , haveAttr := attributes . Get ( a )
if haveAttr {
nonIdentifyingAttrsCount --
}
} )
if attributes . Len ( ) == 0 {
}
if nonIdentifyingAttrsCount == 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
}
labels := createAttributes ( resource , attributes , settings . ExternalLabels , model . MetricNameLabel , name )
labels := createAttributes ( resource , attributes , settings . ExternalLabels , identifyingAttrs , false , model . MetricNameLabel , name )
haveIdentifier := false
for _ , l := range labels {
if l . Name == model . JobLabel || l . Name == model . InstanceLabel {
haveIdentifier = true
break
}
}
if ! haveIdentifier {
// We need at least one identifying label to generate target_info.
return
}
sample := & prompb . Sample {
Value : float64 ( 1 ) ,
// convert ns to ms
Timestamp : convertTimeStamp ( timestamp ) ,
}
addSample ( tsMap , sample , labels , infoType )
converter . addSample ( sample , labels )
}
// convertTimeStamp converts OTLP timestamp in ns to timestamp in ms