in collector/otlp/metrics.go [302:381]
func (t *OltpMetricWriter) addOltpExpHistogramPoints(ctx context.Context, name string, points []*metricsv1.ExponentialHistogramDataPoint, wr *prompb.WriteRequest) error {
for _, point := range points {
timestamp := unixNanoToUnixMillis(point.TimeUnixNano)
// Add count series
series := newSeries(fmt.Sprintf("%s_count", name), point.Attributes)
series.Samples = []*prompb.Sample{
{
Timestamp: timestamp,
Value: float64(point.Count),
},
}
if err := t.addSeriesAndFlushIfNecessary(ctx, wr, series); err != nil {
return err
}
// Add sum series, if present
if point.Sum != nil {
series := newSeries(fmt.Sprintf("%s_sum", name), point.Attributes)
series.Samples = []*prompb.Sample{
{
Timestamp: timestamp,
Value: float64(*point.Sum),
},
}
if err := t.addSeriesAndFlushIfNecessary(ctx, wr, series); err != nil {
return err
}
}
// See https://opentelemetry.io/blog/2023/exponential-histograms/
base := math.Pow(2.0, math.Pow(2.0, float64(point.Scale)))
if point.Negative != nil {
buckets := point.Negative
offset := buckets.Offset
for i := int32(len(buckets.BucketCounts)) - 1; i >= 0; i-- {
bucketIdx := i + offset
// For negative buckets, the "upper bound" is the lower bound of the bucket
// The lower bound is the higher number.
upperBound := fmt.Sprintf("%f", math.Pow(-base, float64(bucketIdx)))
series := newSeries(fmt.Sprintf("%s_bucket", name), point.Attributes)
series.AppendLabelString("le", upperBound)
series.AppendSample(timestamp, float64(buckets.BucketCounts[i]))
if err := t.addSeriesAndFlushIfNecessary(ctx, wr, series); err != nil {
return err
}
}
}
// TODO - understand better how to handle point.ZeroCount
if point.Positive != nil {
buckets := point.Positive
offset := buckets.Offset
for i := int32(0); i < int32(len(buckets.BucketCounts)); i++ {
bucketIdx := i + offset
// For positive buckets, the "upper bound" is the higher bound of the bucket
upperBound := fmt.Sprintf("%f", math.Pow(base, float64(bucketIdx+1)))
series := newSeries(fmt.Sprintf("%s_bucket", name), point.Attributes)
series.Labels = append(series.Labels, &prompb.Label{
Name: []byte("le"),
Value: []byte(upperBound),
})
series.Samples = []*prompb.Sample{
{
Timestamp: timestamp,
Value: float64(buckets.BucketCounts[i]),
},
}
if err := t.addSeriesAndFlushIfNecessary(ctx, wr, series); err != nil {
return err
}
}
}
}
return nil
}