Skip to content

Commit

Permalink
[processor/transform] Add extract_sum_metric OTTL function (#24368)
Browse files Browse the repository at this point in the history
**Description:**
Added a function for extracting the sum from a Histogram,
ExponentialHistogram or Summary as an individual metric. The added
function also works for Summaries, so we can later deprecate the
Summary-specific `summary_sum_val_to_sum` function.

**Link to tracking Issue:** #22853 

**Testing:**
Added unit tests both for the function itself and OTTL statements
involving it.

**Documentation:**
Added a section in the README.

---------

Co-authored-by: Evan Bradley <11745660+evan-bradley@users.noreply.github.com>
  • Loading branch information
swiatekm and evan-bradley authored Aug 3, 2023
1 parent 88c3006 commit 4600e13
Show file tree
Hide file tree
Showing 8 changed files with 544 additions and 47 deletions.
20 changes: 20 additions & 0 deletions .chloggen/feat_transformprocessor_histogram-convert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Use this changelog template to create an entry for release notes.
# If your change doesn't affect end users, such as a test fix or a tooling change,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.

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

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Add extract_sum_metric OTTL function to transform processor

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [22853]

# (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:
24 changes: 24 additions & 0 deletions processor/transformprocessor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,30 @@ Examples:

- `convert_gauge_to_sum("delta", true)`

### extract_sum_metric

> [!NOTE]
> This function supports Histograms, ExponentialHistograms and Summaries.

`extract_sum_metric(is_monotonic)`

The `extract_sum_metric` function creates a new Sum metric from a Histogram, ExponentialHistogram or Summary's sum value. If the sum value of a Histogram or ExponentialHistogram data point is missing, no data point is added to the output metric. A metric will only be created if there is at least one data point.

`is_monotonic` is a boolean representing the monotonicity of the new metric.

The name for the new metric will be `<original metric name>_sum`. The fields that are copied are: `timestamp`, `starttimestamp`, `attibutes`, `description`, and `aggregation_temporality`. As metrics of type Summary don't have an `aggregation_temporality` field, this field will be set to `AGGREGATION_TEMPORALITY_CUMULATIVE` for those metrics.

The new metric that is created will be passed to all subsequent statements in the metrics statements list.

> [!WARNING]
> This function may cause a metric to break semantics for [Sum metrics](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/data-model.md#sums). Use only if you're confident you know what the resulting monotonicity should be.

Examples:

- `extract_sum_metric(true)`

- `extract_sum_metric(false)`

### convert_summary_count_val_to_sum

`convert_summary_count_val_to_sum(aggregation_temporality, is_monotonic)`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,51 +13,6 @@ import (
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottldatapoint"
)

func getTestSummaryMetric() pmetric.Metric {
metricInput := pmetric.NewMetric()
metricInput.SetEmptySummary()
metricInput.SetName("summary_metric")
input := metricInput.Summary().DataPoints().AppendEmpty()
input.SetCount(100)
input.SetSum(12.34)

qVal1 := input.QuantileValues().AppendEmpty()
qVal1.SetValue(1)
qVal1.SetQuantile(.99)

qVal2 := input.QuantileValues().AppendEmpty()
qVal2.SetValue(2)
qVal2.SetQuantile(.95)

qVal3 := input.QuantileValues().AppendEmpty()
qVal3.SetValue(3)
qVal3.SetQuantile(.50)

attrs := getTestAttributes()
attrs.CopyTo(input.Attributes())
return metricInput
}

func getTestGaugeMetric() pmetric.Metric {
metricInput := pmetric.NewMetric()
metricInput.SetEmptyGauge()
metricInput.SetName("gauge_metric")
input := metricInput.Gauge().DataPoints().AppendEmpty()
input.SetIntValue(12)

attrs := getTestAttributes()
attrs.CopyTo(input.Attributes())
return metricInput
}

func getTestAttributes() pcommon.Map {
attrs := pcommon.NewMap()
attrs.PutStr("test", "hello world")
attrs.PutInt("test2", 3)
attrs.PutBool("test3", true)
return attrs
}

type summaryTestCase struct {
name string
input pmetric.Metric
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package metrics // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor/internal/metrics"

import (
"context"
"fmt"

"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"

"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/contexts/ottlmetric"
)

type extractSumMetricArguments struct {
Monotonic bool `ottlarg:"0"`
}

func newExtractSumMetricFactory() ottl.Factory[ottlmetric.TransformContext] {
return ottl.NewFactory("extract_sum_metric", &extractSumMetricArguments{}, createExtractSumMetricFunction)
}

func createExtractSumMetricFunction(_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[ottlmetric.TransformContext], error) {
args, ok := oArgs.(*extractSumMetricArguments)

if !ok {
return nil, fmt.Errorf("extractSumMetricFactory args must be of type *extractSumMetricArguments")
}

return extractSumMetric(args.Monotonic)
}

// this interface helps unify the logic for extracting data from different histogram types
// all supported metric types' datapoints implement it
type SumCountDataPoint interface {
Attributes() pcommon.Map
Sum() float64
Count() uint64
StartTimestamp() pcommon.Timestamp
Timestamp() pcommon.Timestamp
}

func extractSumMetric(monotonic bool) (ottl.ExprFunc[ottlmetric.TransformContext], error) {
return func(_ context.Context, tCtx ottlmetric.TransformContext) (interface{}, error) {
var aggTemp pmetric.AggregationTemporality
metric := tCtx.GetMetric()
invalidMetricTypeError := fmt.Errorf("extract_sum_metric requires an input metric of type Histogram, ExponentialHistogram or Summary, got %s", metric.Type())

switch metric.Type() {
case pmetric.MetricTypeHistogram:
aggTemp = metric.Histogram().AggregationTemporality()
case pmetric.MetricTypeExponentialHistogram:
aggTemp = metric.ExponentialHistogram().AggregationTemporality()
case pmetric.MetricTypeSummary:
// Summaries don't have an aggregation temporality, but they *should* be cumulative based on the Openmetrics spec.
// This should become an optional argument once those are available in OTTL.
aggTemp = pmetric.AggregationTemporalityCumulative
default:
return nil, invalidMetricTypeError
}

sumMetric := pmetric.NewMetric()
sumMetric.SetDescription(metric.Description())
sumMetric.SetName(metric.Name() + "_sum")
sumMetric.SetUnit(metric.Unit())
sumMetric.SetEmptySum().SetAggregationTemporality(aggTemp)
sumMetric.Sum().SetIsMonotonic(monotonic)

switch metric.Type() {
case pmetric.MetricTypeHistogram:
dataPoints := metric.Histogram().DataPoints()
for i := 0; i < dataPoints.Len(); i++ {
dataPoint := dataPoints.At(i)
if dataPoint.HasSum() {
addSumDataPoint(dataPoint, sumMetric.Sum().DataPoints())
}
}
case pmetric.MetricTypeExponentialHistogram:
dataPoints := metric.ExponentialHistogram().DataPoints()
for i := 0; i < dataPoints.Len(); i++ {
dataPoint := dataPoints.At(i)
if dataPoint.HasSum() {
addSumDataPoint(dataPoint, sumMetric.Sum().DataPoints())
}
}
case pmetric.MetricTypeSummary:
dataPoints := metric.Summary().DataPoints()
// note that unlike Histograms, the Sum field is required for Summaries
for i := 0; i < dataPoints.Len(); i++ {
addSumDataPoint(dataPoints.At(i), sumMetric.Sum().DataPoints())
}
default:
return nil, invalidMetricTypeError
}

if sumMetric.Sum().DataPoints().Len() > 0 {
sumMetric.MoveTo(tCtx.GetMetrics().AppendEmpty())
}

return nil, nil
}, nil
}

func addSumDataPoint(dataPoint SumCountDataPoint, destination pmetric.NumberDataPointSlice) {
newDp := destination.AppendEmpty()
dataPoint.Attributes().CopyTo(newDp.Attributes())
newDp.SetDoubleValue(dataPoint.Sum())
newDp.SetStartTimestamp(dataPoint.StartTimestamp())
newDp.SetTimestamp(dataPoint.Timestamp())
}
Loading

0 comments on commit 4600e13

Please sign in to comment.