Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[metricstransformprocessor] add new operation- convert_resource_attributes_to_metric_labels #1337

Closed
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion processor/metricstransformprocessor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Supported pipeline types: metrics
- e.g. If want to rename a metric or label to `new_name` while there is already a metric or label called `new_name`, this operation will not take any effect. There will also be an error logged

## Description
The metrics transform processor can be used to rename metrics, labels, or label values. It can also be used to perform aggregations on metrics across labels or label values.
The metrics transform processor can be used to rename metrics, labels, or label values. It can also be used to perform aggregations on metrics across labels or label values. Additionally, it supports converting resource attributes to metric labels. Customers can select which resource attributes they want to set as metric labels.

## Capabilities
- Rename metrics (e.g. rename `cpu/usage` to `cpu/usage_time`)
Expand All @@ -17,12 +17,15 @@ The metrics transform processor can be used to rename metrics, labels, or label
- Aggregation_type: sum, mean, max
- Add label to an existing metric
- When adding or updating a label value, specify `{{version}}` to include the application version number
- Convert resource attributes to metric labels

## Configuration
```yaml
# transforms is a list of transformations with each element transforming a metric selected by metric name
transforms:
# name is used to match with the metric to operate on. This implementation doesn’t utilize the filtermetric’s MatchProperties struct because it doesn’t match well with what I need at this phase. All is needed for this processor at this stage is a single name string that can be used to match with selected metrics. The list of metric names and the match type in the filtermetric’s MatchProperties struct are unnecessary. Also, based on the issue about improving filtering configuration, it seems like this struct is subject to be slightly modified.

# it also accepts `all_metrics` as the value of 'metric_name' field. This batch operation applies changes to all the metrics. However, currently, it only works and is tested for the 'convert_resource_attributes_to_labels' operation.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can you just implement it for all operations in this PR? Or create a separate PR for just the all_metrics change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For most of the update operations, it should work fine. I already did some manual testing. Need more unit tests to verify it works fine for the operations we are claiming. Separate PR would make the process faster. Here is the issue I created for tracking- #1342 . Also, for the aggregation operations, I don't know how it will work. That's why planned for a separate PR.

- metric_name: <current_metric_name>

# action specifies if the operations are performed on the current copy of the metric or on a newly created metric that will be inserted
Expand Down Expand Up @@ -55,6 +58,14 @@ transforms:
aggregated_values: [values...]
new_value: <new_value>
aggregation_type: {sum, mean, max}

# convert_resource_attributes_to_labels action converts the resourde attributes to metric labels.
- action: convert_resource_attributes_to_labels
# customers can select which resource attributes they want to set as metric labels. If `resource_attributes` are not set or the list is empty,
# it convert all the resource attributes to metric labels by default.
resource_attributes:
- resource_attribute_1
- resource_attribute_2
```

## Examples
Expand Down Expand Up @@ -148,3 +159,14 @@ operation:
label: label
label_value: value
```

### Convert Resource Attributes to Metric Labels
```yaml
# converts resource attributes to metric labels.
...
operation:
- action: convert_resource_attributes_to_labels
resource_attributes:
- ecs.cluster
- ecs.task_id
```
8 changes: 8 additions & 0 deletions processor/metricstransformprocessor/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ type Operation struct {

// LabelValue identifies the exact label value to operate on
LabelValue string `mapstructure:"label_value"`

// ResourceAttributes identifies the resource attributes to convert them to metric labels
ResourceAttributes []string `mapstructure:"resource_attributes"`
}

// ValueAction renames label values.
Expand Down Expand Up @@ -138,6 +141,11 @@ const (
// DeleteLabelValue deletes a label value by also removing all the points associated with this label value
DeleteLabelValue OperationAction = "delete_label_value"

// ConvertResourceAttributesToLabels converts resource attributes to metric labels.
// TODO: right now it converts all the attributes to metric labels by default. Make it
// configurable to select specific resource attributes as metric labels.
ConvertResourceAttributesToLabels OperationAction = "convert_resource_attributes_to_labels"

// Mean indicates taking the mean of the aggregated data.
Mean AggregationType = "mean"

Expand Down
10 changes: 10 additions & 0 deletions processor/metricstransformprocessor/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ var (
NewName: "new_name",
Operations: testDataOperations,
},
{
MetricName: "all_metrics",
Action: Update,
Operations: []Operation{
{
Action: ConvertResourceAttributesToLabels,
ResourceAttributes: []string{"ecs.cluster"},
},
},
},
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package metricstransformprocessor

import (
metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1"
"google.golang.org/protobuf/types/known/timestamppb"
)

Expand All @@ -33,6 +34,17 @@ func metricBuilder() builder {
}
}

// metricBuilderWithResource is used to build metrics with Resource for testing
func metricBuilderWithResource() builder {
return builder{
metric: &metricspb.Metric{
MetricDescriptor: &metricspb.MetricDescriptor{},
Timeseries: make([]*metricspb.TimeSeries, 0),
Resource: &resourcepb.Resource{},
},
}
}

// setName sets the name of the metric
func (b builder) setName(name string) builder {
b.metric.MetricDescriptor.Name = name
Expand All @@ -51,6 +63,12 @@ func (b builder) setLabels(labels []string) builder {
return b
}

// setResourceAttributes sets resource attributes
func (b builder) setResourceAttributes(attributes map[string]string) builder {
b.metric.Resource.Labels = attributes
return b
}

// addTimeseries adds new timeseries with the labelValuesVal and startTimestamp
func (b builder) addTimeseries(startTimestampSeconds int64, labelValuesVal []string) builder {
labelValues := make([]*metricspb.LabelValue, len(labelValuesVal))
Expand Down
28 changes: 24 additions & 4 deletions processor/metricstransformprocessor/metrics_transform_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ import (
)

type internalTransform struct {
MetricName string
Action ConfigAction
NewName string
Operations []internalOperation
MetricName string
Action ConfigAction
NewName string
Operations []internalOperation
resourceAttributeMap map[string]string
}

type internalOperation struct {
Expand Down Expand Up @@ -65,7 +66,24 @@ func (mtp *metricsTransformProcessor) ProcessMetrics(_ context.Context, md pdata
nameToMetricMapping[metric.MetricDescriptor.Name] = metric
}

resourceAttributes := make(map[string]string)
if data.Resource != nil {
resourceAttributes = data.Resource.Labels
}
for _, transform := range mtp.transforms {
transform.resourceAttributeMap = resourceAttributes

// When user sets "metric_name=all_metrics", these operations are going to be applied
// to all metric data points. Right now, this batch operation only works and is tested for
// `convert_resource_attributes_to_labels` operation, and does not break existing experience
// for other operations. After updating the metrics, it will continue for the next iteration.
if transform.MetricName == "all_metrics" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than use "all_metrics" as a special value, I think it would make more sense to do what @tigrannajaryan suggested and make MetricName optional in the config. If left out, then the transform applies to all metrics.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed an update. Nice suggestion. Thanks @james-bebbington.

for _, metric := range data.Metrics {
mtp.update(metric, transform)
}
continue
}

metric, ok := nameToMetricMapping[transform.MetricName]
if !ok {
continue
Expand Down Expand Up @@ -110,6 +128,8 @@ func (mtp *metricsTransformProcessor) update(metric *metricspb.Metric, transform
mtp.addLabelOp(metric, op)
case DeleteLabelValue:
mtp.deleteLabelValueOp(metric, op)
case ConvertResourceAttributesToLabels:
mtp.convertResourceAttributesToLabels(metric, transform.resourceAttributeMap, op)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,129 @@ var (
build(),
},
},
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a test case for the nil case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a new test case for nil or empty resource attributes.

name: "all_metrics_batch_operation_test",
transforms: []internalTransform{
{
MetricName: "all_metrics",
Action: Update,
Operations: []internalOperation{
{
configOperation: Operation{
Action: UpdateLabel,
Label: "label1",
NewLabel: "new/label1",
},
},
},
},
},
in: []*metricspb.Metric{
metricBuilder().setName("metric1").
setDataType(metricspb.MetricDescriptor_CUMULATIVE_INT64).
setLabels([]string{"label1"}).
addTimeseries(1, []string{"value1"}).
addInt64Point(0, 3, 2).build(),
},
out: []*metricspb.Metric{
metricBuilder().setName("metric1").
setDataType(metricspb.MetricDescriptor_CUMULATIVE_INT64).
setLabels([]string{"new/label1"}).
addTimeseries(1, []string{"value1"}).
addInt64Point(0, 3, 2).build(),
},
},
{
name: "convert_resource_attributes_to_labels_with_nil_resource",
transforms: []internalTransform{
{
MetricName: "all_metrics",
Action: Update,
Operations: []internalOperation{
{
configOperation: Operation{
Action: ConvertResourceAttributesToLabels,
},
},
},
},
},
in: []*metricspb.Metric{
metricBuilder().setName("metric1").
setDataType(metricspb.MetricDescriptor_CUMULATIVE_INT64).
setLabels([]string{"label1"}).
addTimeseries(1, []string{"value1"}).
addInt64Point(0, 3, 2).build(),
},
out: []*metricspb.Metric{
metricBuilder().setName("metric1").
setDataType(metricspb.MetricDescriptor_CUMULATIVE_INT64).
setLabels([]string{"label1"}).
addTimeseries(1, []string{"value1"}).
addInt64Point(0, 3, 2).build(),
},
},
{
name: "convert_resource_attributes_to_labels_empty_list_config",
transforms: []internalTransform{
{
MetricName: "all_metrics",
Action: Update,
Operations: []internalOperation{
{
configOperation: Operation{
Action: ConvertResourceAttributesToLabels,
},
},
},
},
},
in: []*metricspb.Metric{
metricBuilderWithResource().setName("metric1").
setDataType(metricspb.MetricDescriptor_CUMULATIVE_INT64).
setResourceAttributes(map[string]string{"label1": "value1"}).
addTimeseries(1, []string{}).
addInt64Point(0, 3, 2).build(),
},
out: []*metricspb.Metric{
metricBuilder().setName("metric1").
setDataType(metricspb.MetricDescriptor_CUMULATIVE_INT64).
setLabels([]string{"label1"}).
addTimeseries(1, []string{"value1"}).
addInt64Point(0, 3, 2).build(),
},
},
{
name: "convert_resource_attributes_to_labels_with_config_value",
transforms: []internalTransform{
{
MetricName: "all_metrics",
Action: Update,
Operations: []internalOperation{
{
configOperation: Operation{
Action: ConvertResourceAttributesToLabels,
ResourceAttributes: []string{"label1"},
},
},
},
},
},
in: []*metricspb.Metric{
metricBuilderWithResource().setName("metric1").
setDataType(metricspb.MetricDescriptor_CUMULATIVE_INT64).
setResourceAttributes(map[string]string{"label1": "value1"}).
addTimeseries(1, []string{}).
addInt64Point(0, 3, 2).build(),
},
out: []*metricspb.Metric{
metricBuilder().setName("metric1").
setDataType(metricspb.MetricDescriptor_CUMULATIVE_INT64).
setLabels([]string{"label1"}).
addTimeseries(1, []string{"value1"}).
addInt64Point(0, 3, 2).build(),
},
},
// INSERT
{
name: "metric_name_insert",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2020 OpenTelemetry 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.

package metricstransformprocessor

import (
metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
)

func (mtp *metricsTransformProcessor) convertResourceAttributesToLabels(metric *metricspb.Metric, resourceAttributes map[string]string, op internalOperation) {
expectedLabelMap := make(map[string]string)

if len(op.configOperation.ResourceAttributes) > 0 {
for _, attribute := range op.configOperation.ResourceAttributes {
attributeValue, ok := resourceAttributes[attribute]
if ok {
expectedLabelMap[attribute] = attributeValue
}
}
} else {
expectedLabelMap = resourceAttributes
}

for key, value := range expectedLabelMap {
lablelKey := &metricspb.LabelKey{
Key: key,
}
labelValue := &metricspb.LabelValue{
Value: value,
HasValue: true,
}

metric.MetricDescriptor.LabelKeys = append(metric.MetricDescriptor.LabelKeys, lablelKey)
for _, ts := range metric.Timeseries {
ts.LabelValues = append(ts.LabelValues, labelValue)
}
}
}
7 changes: 7 additions & 0 deletions processor/metricstransformprocessor/testdata/config_full.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ processors:
aggregated_values: [value1, value2]
new_value: new_value
aggregation_type: sum
- metric_name: all_metrics
action: update
operations:
- action: convert_resource_attributes_to_labels
resource_attributes:
- ecs.cluster

metricstransform/addlabel:
transforms:
- metric_name: some_name
Expand Down