Skip to content

Commit

Permalink
Introduce "split" metric schema transformation
Browse files Browse the repository at this point in the history
This is a new transformation type that allows to describe a change
where a metric is converted to several other metrics by eliminating
an attribute.

An example of such change that happened recently is this:
open-telemetry/opentelemetry-specification#2617

This PR implements specification change open-telemetry/opentelemetry-specification#2653
  • Loading branch information
tigrannajaryan committed Jul 7, 2022
1 parent eb9e058 commit ed94410
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 95 deletions.
3 changes: 2 additions & 1 deletion schema/v1.0/ast/common.go
Expand Up @@ -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 {
Expand All @@ -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
9 changes: 9 additions & 0 deletions schema/v1.0/ast/metrics.go
Expand Up @@ -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
Expand All @@ -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"`
}
4 changes: 2 additions & 2 deletions schema/v1.0/parser.go
Expand Up @@ -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) {
Expand Down
201 changes: 111 additions & 90 deletions schema/v1.0/parser_test.go
Expand Up @@ -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) {
Expand Down Expand Up @@ -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"))
}
19 changes: 17 additions & 2 deletions 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).
Expand Down Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions schema/v1.0/types/types.go
Expand Up @@ -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{}

0 comments on commit ed94410

Please sign in to comment.