diff --git a/CHANGELOG.md b/CHANGELOG.md index d94994675e7..6fd839b37e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add support for `opentracing.TextMap` format in the `Inject` and `Extract` methods of the `"go.opentelemetry.io/otel/bridge/opentracing".BridgeTracer` type. (#2911) +- Introduce "split" metric schema transformation (#2999) ### Changed diff --git a/schema/v1.0/ast/common.go b/schema/v1.0/ast/common.go index 7321454f679..99646c0c99e 100644 --- a/schema/v1.0/ast/common.go +++ b/schema/v1.0/ast/common.go @@ -13,6 +13,7 @@ // limitations under the License. package ast // import "go.opentelemetry.io/otel/schema/v1.0/ast" +import "go.opentelemetry.io/otel/schema/v1.0/types" // RenameAttributes corresponds to a section that describes attribute renaming. type RenameAttributes struct { @@ -22,4 +23,4 @@ type RenameAttributes struct { // AttributeMap corresponds to a section representing a mapping of attribute names. // The keys are the old attribute name used in the previous version, the values are the // new attribute name starting from this version. -type AttributeMap map[string]string +type AttributeMap map[types.AttributeName]types.AttributeName diff --git a/schema/v1.0/ast/metrics.go b/schema/v1.0/ast/metrics.go index 9212ad89d91..e3b430a47f3 100644 --- a/schema/v1.0/ast/metrics.go +++ b/schema/v1.0/ast/metrics.go @@ -26,6 +26,7 @@ type Metrics struct { type MetricsChange struct { RenameMetrics map[types.MetricName]types.MetricName `yaml:"rename_metrics"` RenameAttributes *AttributeMapForMetrics `yaml:"rename_attributes"` + Split *SplitMetric `yaml:"split"` } // AttributeMapForMetrics corresponds to a section representing a translation of @@ -34,3 +35,11 @@ type AttributeMapForMetrics struct { ApplyToMetrics []types.MetricName `yaml:"apply_to_metrics"` AttributeMap AttributeMap `yaml:"attribute_map"` } + +// SplitMetric corresponds to a section representing a splitting of a metric +// into multiple metrics by eliminating an attribute. +type SplitMetric struct { + ApplyToMetric types.MetricName `yaml:"apply_to_metric"` + ByAttribute types.AttributeName `yaml:"by_attribute"` + AttributesToMetrics map[types.AttributeValue]types.MetricName `yaml:"attributes_to_metrics"` +} diff --git a/schema/v1.0/parser.go b/schema/v1.0/parser.go index 413cb64c219..f8c6af95981 100644 --- a/schema/v1.0/parser.go +++ b/schema/v1.0/parser.go @@ -32,11 +32,11 @@ import ( const supportedFormatMajor = 1 // Maximum minor version number that this library supports. -const supportedFormatMinor = 0 +const supportedFormatMinor = 1 // Maximum major+minor version number that this library supports, as a string. var supportedFormatMajorMinor = strconv.Itoa(supportedFormatMajor) + "." + - strconv.Itoa(supportedFormatMinor) // 1.0 + strconv.Itoa(supportedFormatMinor) // 1.1 // ParseFile a schema file. schemaFilePath is the file path. func ParseFile(schemaFilePath string) (*ast.Schema, error) { diff --git a/schema/v1.0/parser_test.go b/schema/v1.0/parser_test.go index eefe3257992..588a6c8d6ea 100644 --- a/schema/v1.0/parser_test.go +++ b/schema/v1.0/parser_test.go @@ -28,123 +28,142 @@ func TestParseSchemaFile(t *testing.T) { ts, err := ParseFile("testdata/valid-example.yaml") assert.NoError(t, err) assert.NotNil(t, ts) - assert.EqualValues(t, &ast.Schema{ - FileFormat: "1.0.0", - SchemaURL: "https://opentelemetry.io/schemas/1.1.0", - Versions: map[types.TelemetryVersion]ast.VersionDef{ - "1.0.0": {}, - - "1.1.0": { - All: ast.Attributes{ - Changes: []ast.AttributeChange{ - {RenameAttributes: &ast.AttributeMap{ - "k8s.cluster.name": "kubernetes.cluster.name", - "k8s.namespace.name": "kubernetes.namespace.name", - "k8s.node.name": "kubernetes.node.name", - "k8s.node.uid": "kubernetes.node.uid", - "k8s.pod.name": "kubernetes.pod.name", - "k8s.pod.uid": "kubernetes.pod.uid", - "k8s.container.name": "kubernetes.container.name", - "k8s.replicaset.name": "kubernetes.replicaset.name", - "k8s.replicaset.uid": "kubernetes.replicaset.uid", - "k8s.cronjob.name": "kubernetes.cronjob.name", - "k8s.cronjob.uid": "kubernetes.cronjob.uid", - "k8s.job.name": "kubernetes.job.name", - "k8s.job.uid": "kubernetes.job.uid", - "k8s.statefulset.name": "kubernetes.statefulset.name", - "k8s.statefulset.uid": "kubernetes.statefulset.uid", - "k8s.daemonset.name": "kubernetes.daemonset.name", - "k8s.daemonset.uid": "kubernetes.daemonset.uid", - "k8s.deployment.name": "kubernetes.deployment.name", - "k8s.deployment.uid": "kubernetes.deployment.uid", - "service.namespace": "service.namespace.name", - }}, + assert.EqualValues( + t, &ast.Schema{ + FileFormat: "1.1.0", + SchemaURL: "https://opentelemetry.io/schemas/1.1.0", + Versions: map[types.TelemetryVersion]ast.VersionDef{ + "1.0.0": {}, + + "1.1.0": { + All: ast.Attributes{ + Changes: []ast.AttributeChange{ + { + RenameAttributes: &ast.AttributeMap{ + "k8s.cluster.name": "kubernetes.cluster.name", + "k8s.namespace.name": "kubernetes.namespace.name", + "k8s.node.name": "kubernetes.node.name", + "k8s.node.uid": "kubernetes.node.uid", + "k8s.pod.name": "kubernetes.pod.name", + "k8s.pod.uid": "kubernetes.pod.uid", + "k8s.container.name": "kubernetes.container.name", + "k8s.replicaset.name": "kubernetes.replicaset.name", + "k8s.replicaset.uid": "kubernetes.replicaset.uid", + "k8s.cronjob.name": "kubernetes.cronjob.name", + "k8s.cronjob.uid": "kubernetes.cronjob.uid", + "k8s.job.name": "kubernetes.job.name", + "k8s.job.uid": "kubernetes.job.uid", + "k8s.statefulset.name": "kubernetes.statefulset.name", + "k8s.statefulset.uid": "kubernetes.statefulset.uid", + "k8s.daemonset.name": "kubernetes.daemonset.name", + "k8s.daemonset.uid": "kubernetes.daemonset.uid", + "k8s.deployment.name": "kubernetes.deployment.name", + "k8s.deployment.uid": "kubernetes.deployment.uid", + "service.namespace": "service.namespace.name", + }, + }, + }, }, - }, - Resources: ast.Attributes{ - Changes: []ast.AttributeChange{ - { - RenameAttributes: &ast.AttributeMap{ - "telemetry.auto.version": "telemetry.auto_instr.version", + Resources: ast.Attributes{ + Changes: []ast.AttributeChange{ + { + RenameAttributes: &ast.AttributeMap{ + "telemetry.auto.version": "telemetry.auto_instr.version", + }, }, }, }, - }, - Spans: ast.Spans{ - Changes: []ast.SpansChange{ - { - RenameAttributes: &ast.AttributeMapForSpans{ - AttributeMap: ast.AttributeMap{ - "peer.service": "peer.service.name", + Spans: ast.Spans{ + Changes: []ast.SpansChange{ + { + RenameAttributes: &ast.AttributeMapForSpans{ + AttributeMap: ast.AttributeMap{ + "peer.service": "peer.service.name", + }, + ApplyToSpans: []types.SpanName{"HTTP GET"}, }, - ApplyToSpans: []types.SpanName{"HTTP GET"}, }, }, }, - }, - SpanEvents: ast.SpanEvents{ - Changes: []ast.SpanEventsChange{ - { - RenameEvents: &ast.RenameSpanEvents{ - EventNameMap: map[string]string{ - "exception.stacktrace": "exception.stack_trace", + SpanEvents: ast.SpanEvents{ + Changes: []ast.SpanEventsChange{ + { + RenameEvents: &ast.RenameSpanEvents{ + EventNameMap: map[string]string{ + "exception.stacktrace": "exception.stack_trace", + }, }, }, - }, - { - RenameAttributes: &ast.RenameSpanEventAttributes{ - ApplyToEvents: []types.EventName{"exception.stack_trace"}, - AttributeMap: ast.AttributeMap{ - "peer.service": "peer.service.name", + { + RenameAttributes: &ast.RenameSpanEventAttributes{ + ApplyToEvents: []types.EventName{"exception.stack_trace"}, + AttributeMap: ast.AttributeMap{ + "peer.service": "peer.service.name", + }, }, }, }, }, - }, - Logs: ast.Logs{Changes: []ast.LogsChange{ - {RenameAttributes: &ast.RenameAttributes{ - AttributeMap: map[string]string{ - "process.executable_name": "process.executable.name", - }, - }}, - }}, - - Metrics: ast.Metrics{ - Changes: []ast.MetricsChange{ - { - RenameAttributes: &ast.AttributeMapForMetrics{ - AttributeMap: map[string]string{ - "http.status_code": "http.response_status_code", + Logs: ast.Logs{ + Changes: []ast.LogsChange{ + { + RenameAttributes: &ast.RenameAttributes{ + AttributeMap: map[types.AttributeName]types.AttributeName{ + "process.executable_name": "process.executable.name", + }, }, - }}, - { - RenameMetrics: map[types.MetricName]types.MetricName{ - "container.cpu.usage.total": "cpu.usage.total", - "container.memory.usage.max": "memory.usage.max", }, }, - { - RenameAttributes: &ast.AttributeMapForMetrics{ - ApplyToMetrics: []types.MetricName{ - "system.cpu.utilization", - "system.memory.usage", - "system.memory.utilization", - "system.paging.usage", + }, + + Metrics: ast.Metrics{ + Changes: []ast.MetricsChange{ + { + RenameAttributes: &ast.AttributeMapForMetrics{ + AttributeMap: map[types.AttributeName]types.AttributeName{ + "http.status_code": "http.response_status_code", + }, + }, + }, + { + RenameMetrics: map[types.MetricName]types.MetricName{ + "container.cpu.usage.total": "cpu.usage.total", + "container.memory.usage.max": "memory.usage.max", + }, + }, + { + RenameAttributes: &ast.AttributeMapForMetrics{ + ApplyToMetrics: []types.MetricName{ + "system.cpu.utilization", + "system.memory.usage", + "system.memory.utilization", + "system.paging.usage", + }, + AttributeMap: map[types.AttributeName]types.AttributeName{ + "status": "state", + }, }, - AttributeMap: map[string]string{ - "status": "state", + }, + { + Split: &ast.SplitMetric{ + ApplyToMetric: "system.paging.operations", + ByAttribute: "direction", + AttributesToMetrics: map[types.AttributeValue]types.MetricName{ + "in": "system.paging.operations.in", + "out": "system.paging.operations.out", + }, }, }, }, }, }, }, - }, - }, ts) + }, ts, + ) } func TestFailParseSchemaFile(t *testing.T) { @@ -172,10 +191,12 @@ func TestCheckFileFormatField(t *testing.T) { // Invalid file format version numbers. assert.Error(t, checkFileFormatField("not a semver")) assert.Error(t, checkFileFormatField("2.0.0")) - assert.Error(t, checkFileFormatField("1.1.0")) + assert.Error(t, checkFileFormatField("1.2.0")) // Valid cases. assert.NoError(t, checkFileFormatField("1.0.0")) assert.NoError(t, checkFileFormatField("1.0.1")) assert.NoError(t, checkFileFormatField("1.0.10000-alpha+4857")) + assert.NoError(t, checkFileFormatField("1.1.0")) + assert.NoError(t, checkFileFormatField("1.1.1")) } diff --git a/schema/v1.0/testdata/valid-example.yaml b/schema/v1.0/testdata/valid-example.yaml index b7759249269..70181c6093d 100644 --- a/schema/v1.0/testdata/valid-example.yaml +++ b/schema/v1.0/testdata/valid-example.yaml @@ -1,10 +1,10 @@ -file_format: 1.0.0 +file_format: 1.1.0 schema_url: https://opentelemetry.io/schemas/1.1.0 versions: 1.1.0: - # Section "all" applies to attributes names for all data types: resources, spans, logs, + # Section "all" applies to attribute names for all data types: resources, spans, logs, # span events, metric labels. # # The translations in "all" section are performed first (for each particular version). @@ -120,6 +120,21 @@ versions: # the new attribute name starting from this version. status: state + - split: + # Rules to split a metric into several metrics using an attribute for split. + # This example rule implements the change done by + # https://github.com/open-telemetry/opentelemetry-specification/pull/2617 + # Name of old metric to split. + apply_to_metric: system.paging.operations + # Name of attribute in the old metric to use for splitting. The attribute will be + # eliminated, the new metric will not have it. + by_attribute: direction + # Names of new metrics to create, one for each possible value of the attribute. + attributes_to_metrics: + # If "direction" attribute equals "in" create a new metric called "system.paging.operations.in". + in: system.paging.operations.in + out: system.paging.operations.out + logs: changes: - rename_attributes: diff --git a/schema/v1.0/types/types.go b/schema/v1.0/types/types.go index 02caa4485a1..3f04d5d44d2 100644 --- a/schema/v1.0/types/types.go +++ b/schema/v1.0/types/types.go @@ -25,3 +25,9 @@ type EventName string // MetricName is a metric name string. type MetricName string + +// AttributeName is an attribute name string. +type AttributeName string + +// AttributeValue is an attribute value. +type AttributeValue interface{}