diff --git a/internal/goldendataset/generator_commons.go b/internal/goldendataset/generator_commons.go index fa580f72cdd..919341ea5d3 100644 --- a/internal/goldendataset/generator_commons.go +++ b/internal/goldendataset/generator_commons.go @@ -56,6 +56,16 @@ func constructAttributeKeyValue(key string, value interface{}) *otlpcommon.KeyVa Key: key, Value: &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_BoolValue{BoolValue: cast.ToBool(val)}}, } + case *otlpcommon.ArrayValue: + attr = otlpcommon.KeyValue{ + Key: key, + Value: &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_ArrayValue{ArrayValue: val}}, + } + case *otlpcommon.KeyValueList: + attr = otlpcommon.KeyValue{ + Key: key, + Value: &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_KvlistValue{KvlistValue: val}}, + } default: attr = otlpcommon.KeyValue{ Key: key, diff --git a/internal/goldendataset/resource_generator.go b/internal/goldendataset/resource_generator.go index 9224fd10d79..1dc5ee18781 100644 --- a/internal/goldendataset/resource_generator.go +++ b/internal/goldendataset/resource_generator.go @@ -15,6 +15,7 @@ package goldendataset import ( + otlpcommon "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/common/v1" otlpresource "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/resource/v1" "go.opentelemetry.io/collector/translator/conventions" ) @@ -69,10 +70,14 @@ func generateOnpremVMAttributes() map[string]interface{} { attrMap[conventions.AttributeServiceName] = "customers" attrMap[conventions.AttributeServiceNamespace] = "production" attrMap[conventions.AttributeServiceVersion] = "semver:0.7.3" - attrMap[conventions.AttributeHostHostname] = "tc-prod9.internal.example.com" - attrMap[conventions.AttributeHostName] = "172.18.36.18" + subMap := make(map[string]interface{}) + subMap["public"] = "tc-prod9.internal.example.com" + subMap["internal"] = "172.18.36.18" + attrMap[conventions.AttributeHostName] = &otlpcommon.KeyValueList{ + Values: convertMapToAttributeKeyValues(subMap), + } attrMap[conventions.AttributeHostImageID] = "661ADFA6-E293-4870-9EFA-1AA052C49F18" - attrMap[conventions.AttributeTelemetrySDKLanguage] = "java" + attrMap[conventions.AttributeTelemetrySDKLanguage] = conventions.AttributeSDKLangValueJava attrMap[conventions.AttributeTelemetrySDKName] = "opentelemetry" attrMap[conventions.AttributeTelemetrySDKVersion] = "0.3.0" return attrMap @@ -84,7 +89,7 @@ func generateCloudVMAttributes() map[string]interface{} { attrMap[conventions.AttributeServiceName] = "customers" attrMap[conventions.AttributeServiceNamespace] = "production" attrMap[conventions.AttributeServiceVersion] = "semver:0.7.3" - attrMap[conventions.AttributeTelemetrySDKLanguage] = "java" + attrMap[conventions.AttributeTelemetrySDKLanguage] = conventions.AttributeSDKLangValueJava attrMap[conventions.AttributeTelemetrySDKName] = "opentelemetry" attrMap[conventions.AttributeTelemetrySDKVersion] = "0.3.0" attrMap[conventions.AttributeHostHostname] = "env-check" @@ -119,8 +124,11 @@ func generateCloudK8sAttributes() map[string]interface{} { attrMap[conventions.AttributeK8sCluster] = "erp-dev" attrMap[conventions.AttributeK8sNamespace] = "monitoring" attrMap[conventions.AttributeK8sDeployment] = "otel-collector" + attrMap[conventions.AttributeK8sDeploymentUID] = "4D614B27-EDAF-409B-B631-6963D8F6FCD4" + attrMap[conventions.AttributeK8sReplicaSet] = "otel-collector-2983fd34" + attrMap[conventions.AttributeK8sReplicaSetUID] = "EC7D59EF-D5B6-48B7-881E-DA6B7DD539B6" attrMap[conventions.AttributeK8sPod] = "otel-collector-6484db5844-c6f9m" - attrMap[conventions.AttributeHostHostname] = "ip-10-99-118-157.ec2.internal" + attrMap[conventions.AttributeK8sPodUID] = "FDFD941E-2A7A-4945-B601-88DD486161A4" attrMap[conventions.AttributeHostID] = "ec2e3fdaffa294348bdf355156b94cda" attrMap[conventions.AttributeHostName] = "10.99.118.157" attrMap[conventions.AttributeHostImageID] = "ami-011c865bf7da41a9d" @@ -147,10 +155,18 @@ func generateFassAttributes() map[string]interface{} { func generateExecAttributes() map[string]interface{} { attrMap := make(map[string]interface{}) attrMap[conventions.AttributeProcessExecutableName] = "otelcol" - attrMap[conventions.AttributeProcessCommandLine] = - "--config=/etc/otel-collector-config.yaml --mem-ballast-size-mib=683" + parts := make([]*otlpcommon.AnyValue, 3) + parts[0] = &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "otelcol"}} + parts[1] = &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "--config=/etc/otel-collector-config.yaml"}} + parts[2] = &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "--mem-ballast-size-mib=683"}} + attrMap[conventions.AttributeProcessCommandLine] = &otlpcommon.ArrayValue{ + Values: parts, + } attrMap[conventions.AttributeProcessExecutablePath] = "/usr/local/bin/otelcol" attrMap[conventions.AttributeProcessID] = 2020 attrMap[conventions.AttributeProcessOwner] = "otel" + attrMap[conventions.AttributeOSType] = "LINUX" + attrMap[conventions.AttributeOSDescription] = + "Linux ubuntu 5.4.0-42-generic #46-Ubuntu SMP Fri Jul 10 00:24:02 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux" return attrMap } diff --git a/internal/goldendataset/span_generator.go b/internal/goldendataset/span_generator.go index e02f3ba57e0..070a0c5ccd2 100644 --- a/internal/goldendataset/span_generator.go +++ b/internal/goldendataset/span_generator.go @@ -435,8 +435,20 @@ func generateMaxCountAttributes(includeStatus bool) map[string]interface{} { attrMap["ai-sampler.absolute"] = false attrMap["ai-sampler.maxhops"] = int64(6) attrMap["application.create.location"] = "https://api.opentelemetry.io/blog/posts/806673B9-4F4D-4284-9635-3A3E3E3805BE" - attrMap["application.svcmap"] = "Blogosphere" - attrMap["application.abflags"] = "UIx=false,UI4=true,flow-alt3=false" + stages := make([]*otlpcommon.AnyValue, 3) + stages[0] = &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "Launch"}} + stages[1] = &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "Injestion"}} + stages[2] = &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "Validation"}} + attrMap["application.stages"] = &otlpcommon.ArrayValue{ + Values: stages, + } + subMap := make(map[string]interface{}) + subMap["UIx"] = false + subMap["UI4"] = true + subMap["flow-alt3"] = false + attrMap["application.abflags"] = &otlpcommon.KeyValueList{ + Values: convertMapToAttributeKeyValues(subMap), + } attrMap["application.thread"] = "proc-pool-14" attrMap["application.session"] = "" attrMap["application.persist.size"] = int64(1172184) diff --git a/testbed/correctness/.gitignore b/testbed/correctness/.gitignore new file mode 100644 index 00000000000..0482cb4e736 --- /dev/null +++ b/testbed/correctness/.gitignore @@ -0,0 +1,2 @@ +results + diff --git a/testbed/correctness/traces/correctness_test.go b/testbed/correctness/traces/correctness_test.go index c14022f8c16..5cf526b51de 100644 --- a/testbed/correctness/traces/correctness_test.go +++ b/testbed/correctness/traces/correctness_test.go @@ -19,7 +19,7 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/service/defaultcomponents" "go.opentelemetry.io/collector/testbed/correctness" @@ -34,7 +34,7 @@ func TestMain(m *testing.M) { func TestTracingGoldenData(t *testing.T) { tests, err := correctness.LoadPictOutputPipelineDefs("testdata/generated_pict_pairs_traces_pipeline.txt") - assert.NoError(t, err) + require.NoError(t, err) processors := map[string]string{ "batch": ` batch: @@ -64,12 +64,12 @@ func testWithTracingGoldenDataset( "", 161803) factories, err := defaultcomponents.Components() - assert.NoError(t, err) + require.NoError(t, err, "default components resulted in: %v", err) runner := testbed.NewInProcessCollector(factories, sender.GetCollectorPort()) validator := testbed.NewCorrectTestValidator(dataProvider) config := correctness.CreateConfigYaml(sender, receiver, processors, "traces") configCleanup, cfgErr := runner.PrepareConfig(config) - assert.NoError(t, cfgErr) + require.NoError(t, cfgErr, "collector configuration resulted in: %v", cfgErr) defer configCleanup() tc := testbed.NewTestCase( t, diff --git a/testbed/testbed/validator.go b/testbed/testbed/validator.go index 2f55b20133b..21d25794581 100644 --- a/testbed/testbed/validator.go +++ b/testbed/testbed/validator.go @@ -16,6 +16,7 @@ package testbed import ( "encoding/hex" + "encoding/json" "fmt" "log" "reflect" @@ -420,20 +421,15 @@ func (v *CorrectnessTestValidator) diffAttributesSlice(spanName string, recdAttr if ok { sentVal := retrieveAttributeValue(sentAttr) recdVal := retrieveAttributeValue(recdAttr) - if !reflect.DeepEqual(sentVal, recdVal) { - sentStr := fmt.Sprintf("%v", sentVal) - recdStr := fmt.Sprintf("%v", recdVal) - if sentStr != recdStr { - af := &TraceAssertionFailure{ - typeName: "Span", - dataComboName: spanName, - fieldPath: fmt.Sprintf(fmtStr, sentAttr.Key), - expectedValue: sentVal, - actualValue: recdVal, - } - v.assertionFailures = append(v.assertionFailures, af) - } + switch val := sentVal.(type) { + case *otlpcommon.KeyValueList: + v.compareKeyValueList(spanName, val, recdVal, fmtStr, sentAttr.Key) + case *otlpcommon.ArrayValue: + v.compareArrayList(spanName, val, recdVal, fmtStr, sentAttr.Key) + default: + v.compareSimpleValues(spanName, sentVal, recdVal, fmtStr, sentAttr.Key) } + } else { af := &TraceAssertionFailure{ typeName: "Span", @@ -447,6 +443,64 @@ func (v *CorrectnessTestValidator) diffAttributesSlice(spanName string, recdAttr } } +func (v *CorrectnessTestValidator) compareSimpleValues(spanName string, sentVal interface{}, recdVal interface{}, + fmtStr string, attrKey string) { + if !reflect.DeepEqual(sentVal, recdVal) { + sentStr := fmt.Sprintf("%v", sentVal) + recdStr := fmt.Sprintf("%v", recdVal) + if !strings.EqualFold(sentStr, recdStr) { + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: spanName, + fieldPath: fmt.Sprintf(fmtStr, attrKey), + expectedValue: sentVal, + actualValue: recdVal, + } + v.assertionFailures = append(v.assertionFailures, af) + } + } +} + +func (v *CorrectnessTestValidator) compareKeyValueList(spanName string, sentKVList *otlpcommon.KeyValueList, + recdVal interface{}, fmtStr string, attrKey string) { + switch val := recdVal.(type) { + case *otlpcommon.KeyValueList: + v.diffAttributesSlice(spanName, val.Values, sentKVList.Values, fmtStr) + case string: + jsonStr := convertKVListToJSONString(sentKVList.Values) + v.compareSimpleValues(spanName, jsonStr, val, fmtStr, attrKey) + default: + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: spanName, + fieldPath: fmt.Sprintf(fmtStr, attrKey), + expectedValue: sentKVList, + actualValue: recdVal, + } + v.assertionFailures = append(v.assertionFailures, af) + } +} + +func (v *CorrectnessTestValidator) compareArrayList(spanName string, sentArray *otlpcommon.ArrayValue, + recdVal interface{}, fmtStr string, attrKey string) { + switch val := recdVal.(type) { + case *otlpcommon.ArrayValue: + v.compareSimpleValues(spanName, sentArray.Values, val.Values, fmtStr, attrKey) + case string: + jsonStr := convertArrayValuesToJSONString(sentArray.Values) + v.compareSimpleValues(spanName, jsonStr, val, fmtStr, attrKey) + default: + af := &TraceAssertionFailure{ + typeName: "Span", + dataComboName: spanName, + fieldPath: fmt.Sprintf(fmtStr, attrKey), + expectedValue: sentArray, + actualValue: recdVal, + } + v.assertionFailures = append(v.assertionFailures, af) + } +} + func convertAttributesSliceToMap(attributes []*otlpcommon.KeyValue) map[string]*otlpcommon.KeyValue { attrMap := make(map[string]*otlpcommon.KeyValue) for _, attr := range attributes { @@ -517,3 +571,60 @@ func notWithinOneMillisecond(sentNs uint64, recdNs uint64) bool { } return diff > uint64(1100000) } + +func convertKVListToJSONString(values []*otlpcommon.KeyValue) string { + jsonStr, err := json.Marshal(convertKVListToRawMap(values)) + if err == nil { + return string(jsonStr) + } + return "" +} + +func convertArrayValuesToJSONString(values []*otlpcommon.AnyValue) string { + jsonStr, err := json.Marshal(convertArrayValuesToRawSlice(values)) + if err == nil { + return string(jsonStr) + } + return "" +} + +func convertKVListToRawMap(values []*otlpcommon.KeyValue) map[string]interface{} { + rawMap := make(map[string]interface{}) + for _, kv := range values { + var value *otlpcommon.AnyValue = kv.GetValue() + switch val := value.GetValue().(type) { + case *otlpcommon.AnyValue_StringValue: + rawMap[kv.Key] = val.StringValue + case *otlpcommon.AnyValue_IntValue: + rawMap[kv.Key] = val.IntValue + case *otlpcommon.AnyValue_DoubleValue: + rawMap[kv.Key] = val.DoubleValue + case *otlpcommon.AnyValue_BoolValue: + rawMap[kv.Key] = val.BoolValue + case *otlpcommon.AnyValue_KvlistValue: + rawMap[kv.Key] = convertKVListToRawMap(val.KvlistValue.Values) + case *otlpcommon.AnyValue_ArrayValue: + rawMap[kv.Key] = convertArrayValuesToRawSlice(val.ArrayValue.Values) + default: + rawMap[kv.Key] = val + } + } + return rawMap +} + +func convertArrayValuesToRawSlice(values []*otlpcommon.AnyValue) []interface{} { + rawSlice := make([]interface{}, 0, len(values)) + for _, v := range values { + switch val := v.GetValue().(type) { + case *otlpcommon.AnyValue_StringValue: + rawSlice = append(rawSlice, val.StringValue) + case *otlpcommon.AnyValue_IntValue: + rawSlice = append(rawSlice, val.IntValue) + case *otlpcommon.AnyValue_DoubleValue: + rawSlice = append(rawSlice, val.DoubleValue) + case *otlpcommon.AnyValue_BoolValue: + rawSlice = append(rawSlice, val.BoolValue) + } + } + return rawSlice +} diff --git a/translator/conventions/opentelemetry.go b/translator/conventions/opentelemetry.go index 5e9f3ad98ea..db7edda5658 100644 --- a/translator/conventions/opentelemetry.go +++ b/translator/conventions/opentelemetry.go @@ -15,7 +15,7 @@ package conventions // OpenTelemetry Semantic Convention values for Resource attribute names. -// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/resource/semantic_conventions/README.md +// See: https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/resource/semantic_conventions/README.md const ( AttributeCloudAccount = "cloud.account.id" AttributeCloudProvider = "cloud.provider" @@ -54,6 +54,8 @@ const ( AttributeK8sReplicaSetUID = "k8s.replicaset.uid" AttributeK8sStatefulSet = "k8s.statefulset.name" AttributeK8sStatefulSetUID = "k8s.statefulset.uid" + AttributeOSType = "os.type" + AttributeOSDescription = "os.description" AttributeProcessCommand = "process.command" AttributeProcessCommandLine = "process.command_line" AttributeProcessExecutableName = "process.executable.name" @@ -70,6 +72,21 @@ const ( AttributeTelemetrySDKVersion = "telemetry.sdk.version" ) +// OpenTelemetry Semantic Convention values for Resource attribute "telemetry.sdk.language" values. +// See: https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/resource/semantic_conventions/README.md +const ( + AttributeSDKLangValueCPP = "cpp" + AttributeSDKLangValueDotNET = "dotnet" + AttributeSDKLangValueErlang = "erlang" + AttributeSDKLangValueGo = "go" + AttributeSDKLangValueJava = "java" + AttributeSDKLangValueNodeJS = "nodejs" + AttributeSDKLangValuePHP = "php" + AttributeSDKLangValuePython = "python" + AttributeSDKLangValueRuby = "ruby" + AttributeSDKLangValueWebJS = "webjs" +) + // GetResourceSemanticConventionAttributeNames a slice with all the Resource Semantic Conventions attribute names. func GetResourceSemanticConventionAttributeNames() []string { return []string{ @@ -77,6 +94,7 @@ func GetResourceSemanticConventionAttributeNames() []string { AttributeCloudProvider, AttributeCloudRegion, AttributeCloudZone, + AttributeContainerID, AttributeContainerImage, AttributeContainerName, AttributeContainerTag, @@ -93,9 +111,24 @@ func GetResourceSemanticConventionAttributeNames() []string { AttributeHostName, AttributeHostType, AttributeK8sCluster, + AttributeK8sContainer, + AttributeK8sCronJob, + AttributeK8sCronJobUID, + AttributeK8sDaemonSet, + AttributeK8sDaemonSetUID, AttributeK8sDeployment, + AttributeK8sDeploymentUID, + AttributeK8sJob, + AttributeK8sJobUID, AttributeK8sNamespace, AttributeK8sPod, + AttributeK8sPodUID, + AttributeK8sReplicaSet, + AttributeK8sReplicaSetUID, + AttributeK8sStatefulSet, + AttributeK8sStatefulSetUID, + AttributeOSType, + AttributeOSDescription, AttributeProcessCommand, AttributeProcessCommandLine, AttributeProcessExecutableName, @@ -106,6 +139,7 @@ func GetResourceSemanticConventionAttributeNames() []string { AttributeServiceName, AttributeServiceNamespace, AttributeServiceVersion, + AttributeTelemetryAutoVersion, AttributeTelemetrySDKLanguage, AttributeTelemetrySDKName, AttributeTelemetrySDKVersion, diff --git a/translator/internaldata/metrics_to_oc_test.go b/translator/internaldata/metrics_to_oc_test.go index ab316538aa7..53e3ae5aba9 100644 --- a/translator/internaldata/metrics_to_oc_test.go +++ b/translator/internaldata/metrics_to_oc_test.go @@ -36,7 +36,7 @@ func TestMetricsToOC(t *testing.T) { attrs.Upsert(conventions.AttributeHostHostname, pdata.NewAttributeValueString("host1")) attrs.Upsert(conventions.OCAttributeProcessID, pdata.NewAttributeValueInt(123)) attrs.Upsert(conventions.OCAttributeProcessStartTime, pdata.NewAttributeValueString("2020-02-11T20:26:00Z")) - attrs.Upsert(conventions.AttributeTelemetrySDKLanguage, pdata.NewAttributeValueString("CPP")) + attrs.Upsert(conventions.AttributeTelemetrySDKLanguage, pdata.NewAttributeValueString("cpp")) attrs.Upsert(conventions.AttributeTelemetrySDKVersion, pdata.NewAttributeValueString("v2.0.1")) attrs.Upsert(conventions.OCAttributeExporterVersion, pdata.NewAttributeValueString("v1.2.0")) diff --git a/translator/internaldata/oc_testdata_test.go b/translator/internaldata/oc_testdata_test.go index 2689f06427f..84994504ad2 100644 --- a/translator/internaldata/oc_testdata_test.go +++ b/translator/internaldata/oc_testdata_test.go @@ -473,7 +473,7 @@ func generateResourceWithOcNodeAndResource() pdata.Resource { conventions.OCAttributeProcessID: pdata.NewAttributeValueInt(123), conventions.AttributeTelemetrySDKVersion: pdata.NewAttributeValueString("v2.0.1"), conventions.OCAttributeExporterVersion: pdata.NewAttributeValueString("v1.2.0"), - conventions.AttributeTelemetrySDKLanguage: pdata.NewAttributeValueString("CPP"), + conventions.AttributeTelemetrySDKLanguage: pdata.NewAttributeValueString("cpp"), conventions.OCAttributeResourceType: pdata.NewAttributeValueString("good-resource"), "node-str-attr": pdata.NewAttributeValueString("node-str-attr-val"), "resource-str-attr": pdata.NewAttributeValueString("resource-str-attr-val"), diff --git a/translator/internaldata/oc_to_resource.go b/translator/internaldata/oc_to_resource.go index 0c5a0eb1309..c4e167ca53a 100644 --- a/translator/internaldata/oc_to_resource.go +++ b/translator/internaldata/oc_to_resource.go @@ -24,6 +24,23 @@ import ( "go.opentelemetry.io/collector/translator/conventions" ) +var ocLangCodeToLangMap = getOCLangCodeToLangMap() + +func getOCLangCodeToLangMap() map[occommon.LibraryInfo_Language]string { + mappings := make(map[occommon.LibraryInfo_Language]string) + mappings[1] = conventions.AttributeSDKLangValueCPP + mappings[2] = conventions.AttributeSDKLangValueDotNET + mappings[3] = conventions.AttributeSDKLangValueErlang + mappings[4] = conventions.AttributeSDKLangValueGo + mappings[5] = conventions.AttributeSDKLangValueJava + mappings[6] = conventions.AttributeSDKLangValueNodeJS + mappings[7] = conventions.AttributeSDKLangValuePHP + mappings[8] = conventions.AttributeSDKLangValuePython + mappings[9] = conventions.AttributeSDKLangValueRuby + mappings[10] = conventions.AttributeSDKLangValueWebJS + return mappings +} + func ocNodeResourceToInternal(ocNode *occommon.Node, ocResource *ocresource.Resource, dest pdata.Resource) { if ocNode == nil && ocResource == nil { return @@ -97,7 +114,9 @@ func ocNodeResourceToInternal(ocNode *occommon.Node, ocResource *ocresource.Reso attrs.UpsertString(conventions.OCAttributeExporterVersion, ocNode.LibraryInfo.ExporterVersion) } if ocNode.LibraryInfo.Language != occommon.LibraryInfo_LANGUAGE_UNSPECIFIED { - attrs.UpsertString(conventions.AttributeTelemetrySDKLanguage, ocNode.LibraryInfo.Language.String()) + if str, ok := ocLangCodeToLangMap[ocNode.LibraryInfo.Language]; ok { + attrs.UpsertString(conventions.AttributeTelemetrySDKLanguage, str) + } } } } diff --git a/translator/internaldata/resource_to_oc.go b/translator/internaldata/resource_to_oc.go index 70bbe6b5950..473fed2cd00 100644 --- a/translator/internaldata/resource_to_oc.go +++ b/translator/internaldata/resource_to_oc.go @@ -17,7 +17,6 @@ package internaldata import ( "fmt" "strconv" - "strings" "time" occommon "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" @@ -27,6 +26,7 @@ import ( "go.opentelemetry.io/collector/consumer/pdata" "go.opentelemetry.io/collector/translator/conventions" + tracetranslator "go.opentelemetry.io/collector/translator/trace" ) type ocInferredResourceType struct { @@ -62,6 +62,23 @@ var labelPresenceToResourceType = []ocInferredResourceType{ }, } +var langToOCLangCodeMap = getSDKLangToOCLangCodeMap() + +func getSDKLangToOCLangCodeMap() map[string]int32 { + mappings := make(map[string]int32) + mappings[conventions.AttributeSDKLangValueCPP] = 1 + mappings[conventions.AttributeSDKLangValueDotNET] = 2 + mappings[conventions.AttributeSDKLangValueErlang] = 3 + mappings[conventions.AttributeSDKLangValueGo] = 4 + mappings[conventions.AttributeSDKLangValueJava] = 5 + mappings[conventions.AttributeSDKLangValueNodeJS] = 6 + mappings[conventions.AttributeSDKLangValuePHP] = 7 + mappings[conventions.AttributeSDKLangValuePython] = 8 + mappings[conventions.AttributeSDKLangValueRuby] = 9 + mappings[conventions.AttributeSDKLangValueWebJS] = 10 + return mappings +} + func internalResourceToOC(resource pdata.Resource) (*occommon.Node, *ocresource.Resource) { if resource.IsNil() { return nil, nil @@ -122,7 +139,7 @@ func internalResourceToOC(resource pdata.Resource) (*occommon.Node, *ocresource. } ocNode.LibraryInfo.ExporterVersion = val case conventions.AttributeTelemetrySDKLanguage: - if code, ok := occommon.LibraryInfo_Language_value[val]; ok { + if code, ok := langToOCLangCodeMap[val]; ok { if ocNode.LibraryInfo == nil { ocNode.LibraryInfo = &occommon.LibraryInfo{} } @@ -169,40 +186,10 @@ func attributeValueToString(attr pdata.AttributeValue, jsonLike bool) string { return strconv.FormatInt(attr.IntVal(), 10) case pdata.AttributeValueMAP: - // OpenCensus attributes cannot represent maps natively. Convert the - // map to a JSON-like string. - var sb strings.Builder - sb.WriteString("{") - m := attr.MapVal() - first := true - m.ForEach(func(k string, v pdata.AttributeValue) { - if !first { - sb.WriteString(",") - } - first = false - sb.WriteString(fmt.Sprintf("%q:%s", k, attributeValueToString(v, true))) - }) - sb.WriteString("}") - return sb.String() + return tracetranslator.AttributeValueToString(attr, false) case pdata.AttributeValueARRAY: - // OpenCensus attributes cannot represent arrays natively. Convert the - // array to a JSON-like string. - var sb strings.Builder - sb.WriteString("[") - m := attr.ArrayVal() - first := true - len := m.Len() - for i := 0; i < len; i++ { - v := m.At(i) - if !first { - sb.WriteString(",") - } - first = false - sb.WriteString(attributeValueToString(v, true)) - } - sb.WriteString("]") - return sb.String() + return tracetranslator.AttributeValueToString(attr, false) default: return fmt.Sprintf("", attr.Type()) diff --git a/translator/internaldata/resource_to_oc_test.go b/translator/internaldata/resource_to_oc_test.go index 002ed3dd5b6..35002c31cca 100644 --- a/translator/internaldata/resource_to_oc_test.go +++ b/translator/internaldata/resource_to_oc_test.go @@ -15,6 +15,7 @@ package internaldata import ( + "strconv" "testing" occommon "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" @@ -27,6 +28,8 @@ import ( "google.golang.org/protobuf/testing/protocmp" "go.opentelemetry.io/collector/consumer/pdata" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" + "go.opentelemetry.io/collector/internal/goldendataset" "go.opentelemetry.io/collector/translator/conventions" ) @@ -133,11 +136,15 @@ func TestAttributeValueToString(t *testing.T) { assert.EqualValues(t, `{"a\"\\":"b\"\\","c":123,"d":null,"e":{"a\"\\":"b\"\\","c":123,"d":null}}`, attributeValueToString(v, false)) v = pdata.NewAttributeValueArray() - v.ArrayVal().Append(pdata.NewAttributeValueString(`b"\`)) - v.ArrayVal().Append(pdata.NewAttributeValueInt(123)) - v.ArrayVal().Append(pdata.NewAttributeValueNull()) - v.ArrayVal().Append(pdata.NewAttributeValueArray()) - assert.EqualValues(t, `["b\"\\",123,null,[]]`, attributeValueToString(v, false)) + av := pdata.NewAttributeValueString(`b"\`) + v.ArrayVal().Append(av) + av = pdata.NewAttributeValueInt(123) + v.ArrayVal().Append(av) + av = pdata.NewAttributeValueNull() + v.ArrayVal().Append(av) + av = pdata.NewAttributeValueArray() + v.ArrayVal().Append(av) + assert.EqualValues(t, `["b\"\\",123,null,"\u003cInvalid array value\u003e"]`, attributeValueToString(v, false)) } func TestInferResourceType(t *testing.T) { @@ -212,6 +219,49 @@ func TestInferResourceType(t *testing.T) { } } +func TestResourceToOCAndBack(t *testing.T) { + tests := []goldendataset.PICTInputResource{ + goldendataset.ResourceNil, + goldendataset.ResourceEmpty, + goldendataset.ResourceVMOnPrem, + goldendataset.ResourceVMCloud, + goldendataset.ResourceK8sOnPrem, + goldendataset.ResourceK8sCloud, + goldendataset.ResourceFaas, + goldendataset.ResourceExec, + } + for _, test := range tests { + t.Run(string(test), func(t *testing.T) { + rSpans := make([]*otlptrace.ResourceSpans, 1) + rSpans[0] = &otlptrace.ResourceSpans{ + Resource: goldendataset.GenerateResource(test), + InstrumentationLibrarySpans: nil, + } + traces := pdata.TracesFromOtlp(rSpans) + expected := traces.ResourceSpans().At(0).Resource() + ocNode, ocResource := internalResourceToOC(expected) + actual := pdata.NewResource() + ocNodeResourceToInternal(ocNode, ocResource, actual) + if expected.IsNil() { + assert.True(t, actual.IsNil()) + } else { + expected.Attributes().ForEach(func(k string, v pdata.AttributeValue) { + a, ok := actual.Attributes().Get(k) + assert.True(t, ok) + switch v.Type() { + case pdata.AttributeValueINT: + assert.Equal(t, strconv.FormatInt(v.IntVal(), 10), a.StringVal()) + case pdata.AttributeValueMAP, pdata.AttributeValueARRAY: + assert.Equal(t, a, a) + default: + assert.Equal(t, v, a) + } + }) + } + }) + } +} + func BenchmarkInternalResourceToOC(b *testing.B) { resource := generateResourceWithOcNodeAndResource() diff --git a/translator/internaldata/traces_to_oc.go b/translator/internaldata/traces_to_oc.go index fcb225d7cf3..15e99460017 100644 --- a/translator/internaldata/traces_to_oc.go +++ b/translator/internaldata/traces_to_oc.go @@ -158,6 +158,14 @@ func attributeValueToOC(attr pdata.AttributeValue) *octrace.AttributeValue { a.Value = &octrace.AttributeValue_IntValue{ IntValue: attr.IntVal(), } + case pdata.AttributeValueMAP: + a.Value = &octrace.AttributeValue_StringValue{ + StringValue: stringToTruncatableString(tracetranslator.AttributeValueToString(attr, false)), + } + case pdata.AttributeValueARRAY: + a.Value = &octrace.AttributeValue_StringValue{ + StringValue: stringToTruncatableString(tracetranslator.AttributeValueToString(attr, false)), + } default: a.Value = &octrace.AttributeValue_StringValue{ StringValue: stringToTruncatableString(fmt.Sprintf("", attr.Type())), diff --git a/translator/internaldata/traces_to_oc_test.go b/translator/internaldata/traces_to_oc_test.go index b14cf7ec654..d1667c444f3 100644 --- a/translator/internaldata/traces_to_oc_test.go +++ b/translator/internaldata/traces_to_oc_test.go @@ -15,6 +15,8 @@ package internaldata import ( + "io" + "math/rand" "testing" occommon "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1" @@ -27,7 +29,9 @@ import ( "go.opentelemetry.io/collector/consumer/consumerdata" "go.opentelemetry.io/collector/consumer/pdata" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" "go.opentelemetry.io/collector/internal/data/testdata" + "go.opentelemetry.io/collector/internal/goldendataset" "go.opentelemetry.io/collector/translator/conventions" tracetranslator "go.opentelemetry.io/collector/translator/trace" ) @@ -460,3 +464,22 @@ func TestInternalToOC(t *testing.T) { }) } } + +func TestInternalTracesToOCTracesAndBack(t *testing.T) { + rscSpans, err := goldendataset.GenerateResourceSpans( + "../../internal/goldendataset/testdata/generated_pict_pairs_traces.txt", + "../../internal/goldendataset/testdata/generated_pict_pairs_spans.txt", + io.Reader(rand.New(rand.NewSource(2004)))) + assert.NoError(t, err) + for _, rs := range rscSpans { + orig := make([]*otlptrace.ResourceSpans, 1) + orig[0] = rs + td := pdata.TracesFromOtlp(orig) + ocTraceData := TraceDataToOC(td) + assert.Equal(t, 1, len(ocTraceData)) + assert.Equal(t, td.SpanCount(), len(ocTraceData[0].Spans)) + tdFromOC := OCToTraceData(ocTraceData[0]) + assert.NotNil(t, tdFromOC) + assert.Equal(t, td.SpanCount(), tdFromOC.SpanCount()) + } +} diff --git a/translator/trace/jaeger/traces_to_jaegerproto.go b/translator/trace/jaeger/traces_to_jaegerproto.go index 19dd9dcf2e5..cf7b402b632 100644 --- a/translator/trace/jaeger/traces_to_jaegerproto.go +++ b/translator/trace/jaeger/traces_to_jaegerproto.go @@ -171,6 +171,9 @@ func attributeToJaegerProtoTag(key string, attr pdata.AttributeValue) model.KeyV case pdata.AttributeValueDOUBLE: tag.VType = model.ValueType_FLOAT64 tag.VFloat64 = attr.DoubleVal() + case pdata.AttributeValueMAP, pdata.AttributeValueARRAY: + tag.VType = model.ValueType_STRING + tag.VStr = tracetranslator.AttributeValueToString(attr, false) } return tag } diff --git a/translator/trace/jaeger/traces_to_jaegerproto_test.go b/translator/trace/jaeger/traces_to_jaegerproto_test.go index 9bd4c0a6223..5736a33cf2d 100644 --- a/translator/trace/jaeger/traces_to_jaegerproto_test.go +++ b/translator/trace/jaeger/traces_to_jaegerproto_test.go @@ -15,6 +15,8 @@ package jaeger import ( + "io" + "math/rand" "testing" "github.com/jaegertracing/jaeger/model" @@ -22,7 +24,9 @@ import ( "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/consumer/pdata" + otlptrace "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1" "go.opentelemetry.io/collector/internal/data/testdata" + "go.opentelemetry.io/collector/internal/goldendataset" "go.opentelemetry.io/collector/translator/conventions" tracetranslator "go.opentelemetry.io/collector/translator/trace" ) @@ -321,6 +325,24 @@ func TestInternalTracesToJaegerProto(t *testing.T) { } } +func TestInternalTracesToJaegerProtoBatchesAndBack(t *testing.T) { + rscSpans, err := goldendataset.GenerateResourceSpans( + "../../../internal/goldendataset/testdata/generated_pict_pairs_traces.txt", + "../../../internal/goldendataset/testdata/generated_pict_pairs_spans.txt", + io.Reader(rand.New(rand.NewSource(2004)))) + assert.NoError(t, err) + for _, rs := range rscSpans { + orig := make([]*otlptrace.ResourceSpans, 1) + orig[0] = rs + td := pdata.TracesFromOtlp(orig) + protoBatches, err := InternalTracesToJaegerProto(td) + assert.NoError(t, err) + tdFromPB := ProtoBatchesToInternalTraces(protoBatches) + assert.NotNil(t, tdFromPB) + assert.Equal(t, td.SpanCount(), tdFromPB.SpanCount()) + } +} + // generateProtoChildSpanWithErrorTags generates a jaeger span to be used in // internal->jaeger translation test. It supposed to be the same as generateProtoChildSpan // that used in jaeger->internal, but jaeger->internal translation infers status code from http status if diff --git a/translator/trace/protospan_translation.go b/translator/trace/protospan_translation.go index 582e8cac54e..57eefe573a7 100644 --- a/translator/trace/protospan_translation.go +++ b/translator/trace/protospan_translation.go @@ -15,9 +15,11 @@ package tracetranslator import ( + "encoding/json" "fmt" + "math" + "regexp" "strconv" - "strings" "go.opentelemetry.io/collector/consumer/pdata" ) @@ -78,6 +80,38 @@ const ( SpanEventDataFormat = "%s|%s|%d" ) +type attrValDescript struct { + regex *regexp.Regexp + attrType pdata.AttributeValueType +} + +var attrValDescriptions = getAttrValDescripts() +var complexAttrValDescriptions = getComplexAttrValDescripts() + +func getAttrValDescripts() []*attrValDescript { + descriptions := make([]*attrValDescript, 0, 5) + descriptions = append(descriptions, constructAttrValDescript("^$", pdata.AttributeValueNULL)) + descriptions = append(descriptions, constructAttrValDescript(`^-?\d+$`, pdata.AttributeValueINT)) + descriptions = append(descriptions, constructAttrValDescript(`^-?\d+\.\d+$`, pdata.AttributeValueDOUBLE)) + descriptions = append(descriptions, constructAttrValDescript(`^(true|false)$`, pdata.AttributeValueBOOL)) + descriptions = append(descriptions, constructAttrValDescript(`^\{"\w+":.+\}$`, pdata.AttributeValueMAP)) + descriptions = append(descriptions, constructAttrValDescript(`^\[.*\]$`, pdata.AttributeValueARRAY)) + return descriptions +} + +func getComplexAttrValDescripts() []*attrValDescript { + descriptions := getAttrValDescripts() + return descriptions[4:] +} + +func constructAttrValDescript(regex string, attrType pdata.AttributeValueType) *attrValDescript { + regexc := regexp.MustCompile(regex) + return &attrValDescript{ + regex: regexc, + attrType: attrType, + } +} + // AttributeValueToString converts an OTLP AttributeValue object to its equivalent string representation func AttributeValueToString(attr pdata.AttributeValue, jsonLike bool) string { switch attr.Type() { @@ -102,25 +136,171 @@ func AttributeValueToString(attr pdata.AttributeValue, jsonLike bool) string { return strconv.FormatInt(attr.IntVal(), 10) case pdata.AttributeValueMAP: - // OpenCensus attributes cannot represent maps natively. Convert the - // map to a JSON-like string. - var sb strings.Builder - sb.WriteString("{") - m := attr.MapVal() - first := true - m.ForEach(func(k string, v pdata.AttributeValue) { - if !first { - sb.WriteString(",") - } - first = false - sb.WriteString(fmt.Sprintf("%q:%s", k, AttributeValueToString(v, true))) - }) - sb.WriteString("}") - return sb.String() + jsonStr, _ := json.Marshal(AttributeMapToMap(attr.MapVal())) + return string(jsonStr) + + case pdata.AttributeValueARRAY: + jsonStr, _ := json.Marshal(AttributeArrayToSlice(attr.ArrayVal())) + return string(jsonStr) default: return fmt.Sprintf("", attr.Type()) } +} + +// AttributeMapToMap converts an OTLP AttributeMap to a standard go map +func AttributeMapToMap(attrMap pdata.AttributeMap) map[string]interface{} { + rawMap := make(map[string]interface{}) + attrMap.ForEach(func(k string, v pdata.AttributeValue) { + switch v.Type() { + case pdata.AttributeValueSTRING: + rawMap[k] = v.StringVal() + case pdata.AttributeValueINT: + rawMap[k] = v.IntVal() + case pdata.AttributeValueDOUBLE: + rawMap[k] = v.DoubleVal() + case pdata.AttributeValueBOOL: + rawMap[k] = v.BoolVal() + case pdata.AttributeValueNULL: + rawMap[k] = nil + case pdata.AttributeValueMAP: + rawMap[k] = AttributeMapToMap(v.MapVal()) + case pdata.AttributeValueARRAY: + rawMap[k] = AttributeArrayToSlice(v.ArrayVal()) + } + }) + return rawMap +} + +func AttributeArrayToSlice(attrArray pdata.AnyValueArray) []interface{} { + rawSlice := make([]interface{}, 0, attrArray.Len()) + for i := 0; i < attrArray.Len(); i++ { + v := attrArray.At(i) + switch v.Type() { + case pdata.AttributeValueSTRING: + rawSlice = append(rawSlice, v.StringVal()) + case pdata.AttributeValueINT: + rawSlice = append(rawSlice, v.IntVal()) + case pdata.AttributeValueDOUBLE: + rawSlice = append(rawSlice, v.DoubleVal()) + case pdata.AttributeValueBOOL: + rawSlice = append(rawSlice, v.BoolVal()) + case pdata.AttributeValueNULL: + rawSlice = append(rawSlice, nil) + default: + rawSlice = append(rawSlice, "") + } + } + return rawSlice +} - // TODO: Add support for ARRAY type. +// UpsertStringToAttributeMap upserts a string value to the specified key as it's native OTLP type +func UpsertStringToAttributeMap(key string, val string, dest pdata.AttributeMap, omitSimpleTypes bool) { + switch DetermineValueType(val, omitSimpleTypes) { + case pdata.AttributeValueINT: + iVal, _ := strconv.ParseInt(val, 10, 64) + dest.UpsertInt(key, iVal) + case pdata.AttributeValueDOUBLE: + fVal, _ := strconv.ParseFloat(val, 64) + dest.UpsertDouble(key, fVal) + case pdata.AttributeValueBOOL: + bVal, _ := strconv.ParseBool(val) + dest.UpsertBool(key, bVal) + case pdata.AttributeValueMAP: + var attrs map[string]interface{} + err := json.Unmarshal([]byte(val), &attrs) + if err == nil { + attrMap := pdata.NewAttributeValueMap() + jsonMapToAttributeMap(attrs, attrMap.MapVal()) + dest.Upsert(key, attrMap) + } else { + dest.UpsertString(key, "") + } + case pdata.AttributeValueARRAY: + var jArray []interface{} + err := json.Unmarshal([]byte(val), &jArray) + if err == nil { + attrArr := pdata.NewAttributeValueArray() + jsonArrayToAttributeArray(jArray, attrArr.ArrayVal()) + dest.Upsert(key, attrArr) + } else { + dest.UpsertString(key, "") + } + default: + dest.UpsertString(key, val) + } +} + +// DetermineValueType returns the native OTLP attribute type the string translates to. +func DetermineValueType(value string, omitSimpleTypes bool) pdata.AttributeValueType { + if omitSimpleTypes { + for _, desc := range complexAttrValDescriptions { + if desc.regex.MatchString(value) { + return desc.attrType + } + } + } else { + for _, desc := range attrValDescriptions { + if desc.regex.MatchString(value) { + return desc.attrType + } + } + } + return pdata.AttributeValueSTRING +} + +func jsonMapToAttributeMap(attrs map[string]interface{}, dest pdata.AttributeMap) { + for key, val := range attrs { + if val == nil { + dest.Upsert(key, pdata.NewAttributeValueNull()) + continue + } + if s, ok := val.(string); ok { + dest.UpsertString(key, s) + } else if d, ok := val.(float64); ok { + if math.Mod(d, 1.0) == 0.0 { + dest.UpsertInt(key, int64(d)) + } else { + dest.UpsertDouble(key, d) + } + } else if b, ok := val.(bool); ok { + dest.UpsertBool(key, b) + } else if m, ok := val.(map[string]interface{}); ok { + value := pdata.NewAttributeValueMap() + jsonMapToAttributeMap(m, value.MapVal()) + dest.Upsert(key, value) + } else if a, ok := val.([]interface{}); ok { + value := pdata.NewAttributeValueArray() + jsonArrayToAttributeArray(a, value.ArrayVal()) + dest.Upsert(key, value) + } + } +} + +func jsonArrayToAttributeArray(jArray []interface{}, dest pdata.AnyValueArray) { + for _, val := range jArray { + if val == nil { + av := pdata.NewAttributeValueNull() + dest.Append(av) + continue + } + if s, ok := val.(string); ok { + av := pdata.NewAttributeValueString(s) + dest.Append(av) + } else if d, ok := val.(float64); ok { + if math.Mod(d, 1.0) == 0.0 { + av := pdata.NewAttributeValueInt(int64(d)) + dest.Append(av) + } else { + av := pdata.NewAttributeValueDouble(d) + dest.Append(av) + } + } else if b, ok := val.(bool); ok { + av := pdata.NewAttributeValueBool(b) + dest.Append(av) + } else { + av := pdata.NewAttributeValueString("") + dest.Append(av) + } + } } diff --git a/translator/trace/protospan_translation_test.go b/translator/trace/protospan_translation_test.go new file mode 100644 index 00000000000..11d96f8a53c --- /dev/null +++ b/translator/trace/protospan_translation_test.go @@ -0,0 +1,195 @@ +// Copyright The 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 tracetranslator + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/consumer/pdata" +) + +func TestAttributeValueToString(t *testing.T) { + tests := []struct { + name string + input pdata.AttributeValue + jsonLike bool + expected string + }{ + { + name: "string", + input: pdata.NewAttributeValueString("string value"), + jsonLike: false, + expected: "string value", + }, + { + name: "json string", + input: pdata.NewAttributeValueString("string value"), + jsonLike: true, + expected: "\"string value\"", + }, + { + name: "int64", + input: pdata.NewAttributeValueInt(42), + jsonLike: false, + expected: "42", + }, + { + name: "float64", + input: pdata.NewAttributeValueDouble(1.61803399), + jsonLike: false, + expected: "1.61803399", + }, + { + name: "boolean", + input: pdata.NewAttributeValueBool(true), + jsonLike: false, + expected: "true", + }, + { + name: "null", + input: pdata.NewAttributeValueNull(), + jsonLike: false, + expected: "", + }, + { + name: "null", + input: pdata.NewAttributeValueNull(), + jsonLike: true, + expected: "null", + }, + { + name: "map", + input: pdata.NewAttributeValueMap(), + jsonLike: false, + expected: "{}", + }, + { + name: "array", + input: pdata.NewAttributeValueArray(), + jsonLike: false, + expected: "[]", + }, + { + name: "array", + input: pdata.NewAttributeValue(), + jsonLike: false, + expected: "", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := AttributeValueToString(test.input, test.jsonLike) + assert.Equal(t, test.expected, actual) + dest := pdata.NewAttributeMap() + key := "keyOne" + UpsertStringToAttributeMap(key, actual, dest, false) + val, ok := dest.Get(key) + assert.True(t, ok) + if !test.jsonLike { + switch test.input.Type() { + case pdata.AttributeValueINT, pdata.AttributeValueDOUBLE, pdata.AttributeValueBOOL: + assert.EqualValues(t, test.input, val) + case pdata.AttributeValueARRAY: + assert.NotNil(t, val) + default: + assert.Equal(t, test.expected, val.StringVal()) + } + } + }) + } +} + +func TestAttributeMapToStringAndBack(t *testing.T) { + expected := pdata.NewAttributeValueMap() + attrMap := expected.MapVal() + attrMap.UpsertString("strKey", "strVal") + attrMap.UpsertInt("intKey", 7) + attrMap.UpsertDouble("floatKey", 18.6) + attrMap.UpsertBool("boolKey", false) + attrMap.Upsert("nullKey", pdata.NewAttributeValueNull()) + attrMap.Upsert("mapKey", constructTestAttributeSubmap()) + attrMap.Upsert("arrKey", constructTestAttributeSubarray()) + strVal := AttributeValueToString(expected, false) + dest := pdata.NewAttributeMap() + UpsertStringToAttributeMap("parent", strVal, dest, false) + actual, ok := dest.Get("parent") + assert.True(t, ok) + compareMaps(t, attrMap, actual.MapVal()) +} + +func TestAttributeArrayToStringAndBack(t *testing.T) { + expected := pdata.NewAttributeValueArray() + attrArr := expected.ArrayVal() + strAV := pdata.NewAttributeValueString("strVal") + attrArr.Append(strAV) + intAV := pdata.NewAttributeValueInt(7) + attrArr.Append(intAV) + floatAV := pdata.NewAttributeValueDouble(18.6) + attrArr.Append(floatAV) + boolAV := pdata.NewAttributeValueBool(false) + attrArr.Append(boolAV) + nullAV := pdata.NewAttributeValueNull() + attrArr.Append(nullAV) + strVal := AttributeValueToString(expected, false) + dest := pdata.NewAttributeMap() + UpsertStringToAttributeMap("parent", strVal, dest, false) + actual, ok := dest.Get("parent") + assert.True(t, ok) + compareArrays(t, attrArr, actual.ArrayVal()) +} + +func compareMaps(t *testing.T, expected pdata.AttributeMap, actual pdata.AttributeMap) { + expected.ForEach(func(k string, e pdata.AttributeValue) { + a, ok := actual.Get(k) + assert.True(t, ok) + if ok { + if e.Type() == pdata.AttributeValueMAP { + compareMaps(t, e.MapVal(), a.MapVal()) + } else { + assert.Equal(t, e, a) + } + } + }) +} + +func compareArrays(t *testing.T, expected pdata.AnyValueArray, actual pdata.AnyValueArray) { + for i := 0; i < expected.Len(); i++ { + e := expected.At(i) + a := actual.At(i) + if e.Type() == pdata.AttributeValueMAP { + compareMaps(t, e.MapVal(), a.MapVal()) + } else { + assert.Equal(t, e, a) + } + } +} + +func constructTestAttributeSubmap() pdata.AttributeValue { + value := pdata.NewAttributeValueMap() + value.MapVal().UpsertString("keyOne", "valOne") + value.MapVal().UpsertString("keyTwo", "valTwo") + return value +} + +func constructTestAttributeSubarray() pdata.AttributeValue { + value := pdata.NewAttributeValueArray() + a1 := pdata.NewAttributeValueString("strOne") + value.ArrayVal().Append(a1) + a2 := pdata.NewAttributeValueString("strTwo") + value.ArrayVal().Append(a2) + return value +} diff --git a/translator/trace/zipkin/traces_to_zipkinv2.go b/translator/trace/zipkin/traces_to_zipkinv2.go index 8d78451888c..44d9b67a4a7 100644 --- a/translator/trace/zipkin/traces_to_zipkinv2.go +++ b/translator/trace/zipkin/traces_to_zipkinv2.go @@ -205,8 +205,7 @@ func spanEventsToZipkinAnnotations(events pdata.SpanEventSlice, zs *zipkinmodel. Value: event.Name(), } } else { - rawMap := attributeMapToMap(event.Attributes()) - jsonStr, err := json.Marshal(rawMap) + jsonStr, err := json.Marshal(tracetranslator.AttributeMapToMap(event.Attributes())) if err != nil { return err } @@ -227,8 +226,7 @@ func spanLinksToZipkinTags(links pdata.SpanLinkSlice, zTags map[string]string) e link := links.At(i) if !link.IsNil() { key := fmt.Sprintf("otlp.link.%d", i) - rawMap := attributeMapToMap(link.Attributes()) - jsonStr, err := json.Marshal(rawMap) + jsonStr, err := json.Marshal(tracetranslator.AttributeMapToMap(link.Attributes())) if err != nil { return err } @@ -239,40 +237,10 @@ func spanLinksToZipkinTags(links pdata.SpanLinkSlice, zTags map[string]string) e return nil } -func attributeMapToMap(attrMap pdata.AttributeMap) map[string]interface{} { - rawMap := make(map[string]interface{}) - attrMap.ForEach(func(k string, v pdata.AttributeValue) { - switch v.Type() { - case pdata.AttributeValueSTRING: - rawMap[k] = v.StringVal() - case pdata.AttributeValueINT: - rawMap[k] = v.IntVal() - case pdata.AttributeValueDOUBLE: - rawMap[k] = v.DoubleVal() - case pdata.AttributeValueBOOL: - rawMap[k] = v.BoolVal() - case pdata.AttributeValueNULL: - rawMap[k] = nil - } - }) - return rawMap -} - func attributeMapToStringMap(attrMap pdata.AttributeMap) map[string]string { rawMap := make(map[string]string) attrMap.ForEach(func(k string, v pdata.AttributeValue) { - switch v.Type() { - case pdata.AttributeValueSTRING: - rawMap[k] = v.StringVal() - case pdata.AttributeValueINT: - rawMap[k] = strconv.FormatInt(v.IntVal(), 10) - case pdata.AttributeValueDOUBLE: - rawMap[k] = strconv.FormatFloat(v.DoubleVal(), 'f', -1, 64) - case pdata.AttributeValueBOOL: - rawMap[k] = strconv.FormatBool(v.BoolVal()) - case pdata.AttributeValueNULL: - rawMap[k] = "" - } + rawMap[k] = tracetranslator.AttributeValueToString(v, false) }) return rawMap } diff --git a/translator/trace/zipkin/zipkinv1_to_protospan.go b/translator/trace/zipkin/zipkinv1_to_protospan.go index 412c911d12f..a2bf05e114a 100644 --- a/translator/trace/zipkin/zipkinv1_to_protospan.go +++ b/translator/trace/zipkin/zipkinv1_to_protospan.go @@ -253,7 +253,7 @@ func zipkinV1BinAnnotationsToOCAttributes(binAnnotations []*binaryAnnotation) (a func parseAnnotationValue(value string) *tracepb.AttributeValue { pbAttrib := &tracepb.AttributeValue{} - switch determineValueType(value) { + switch tracetranslator.DetermineValueType(value, false) { case pdata.AttributeValueINT: iValue, _ := strconv.ParseInt(value, 10, 64) pbAttrib.Value = &tracepb.AttributeValue_IntValue{IntValue: iValue} diff --git a/translator/trace/zipkin/zipkinv2_to_traces.go b/translator/trace/zipkin/zipkinv2_to_traces.go index 6172c962a45..8d01e0c517b 100644 --- a/translator/trace/zipkin/zipkinv2_to_traces.go +++ b/translator/trace/zipkin/zipkinv2_to_traces.go @@ -19,7 +19,6 @@ import ( "encoding/json" "fmt" "math" - "regexp" "sort" "strconv" "strings" @@ -49,30 +48,6 @@ func getNonSpanAttributes() map[string]struct{} { return attrs } -type AttrValDescript struct { - regex *regexp.Regexp - attrType pdata.AttributeValueType -} - -var attrValDescriptions = getAttrValDescripts() - -func getAttrValDescripts() []*AttrValDescript { - descriptions := make([]*AttrValDescript, 0, 5) - descriptions = append(descriptions, constructAttrValDescript("^$", pdata.AttributeValueNULL)) - descriptions = append(descriptions, constructAttrValDescript(`^-?\d+$`, pdata.AttributeValueINT)) - descriptions = append(descriptions, constructAttrValDescript(`^-?\d+\.\d+$`, pdata.AttributeValueDOUBLE)) - descriptions = append(descriptions, constructAttrValDescript(`^(true|false)$`, pdata.AttributeValueBOOL)) - return descriptions -} - -func constructAttrValDescript(regex string, attrType pdata.AttributeValueType) *AttrValDescript { - regexc, _ := regexp.Compile(regex) - return &AttrValDescript{ - regex: regexc, - attrType: attrType, - } -} - // Custom Sort on type byOTLPTypes []*zipkinmodel.SpanModel @@ -363,32 +338,12 @@ func tagsToAttributeMap(tags map[string]string, dest pdata.AttributeMap) error { if _, ok := nonSpanAttributes[key]; ok { continue } - switch determineValueType(val) { - case pdata.AttributeValueINT: - iVal, _ := strconv.ParseInt(val, 10, 64) - dest.UpsertInt(key, iVal) - case pdata.AttributeValueDOUBLE: - fVal, _ := strconv.ParseFloat(val, 64) - dest.UpsertDouble(key, fVal) - case pdata.AttributeValueBOOL: - bVal, _ := strconv.ParseBool(val) - dest.UpsertBool(key, bVal) - default: - dest.UpsertString(key, val) - } + dest.UpsertString(key, val) + // TODO add translation to native OTLP types if configured to do so } return parseErr } -func determineValueType(value string) pdata.AttributeValueType { - for _, desc := range attrValDescriptions { - if desc.regex.MatchString(value) { - return desc.attrType - } - } - return pdata.AttributeValueSTRING -} - func populateResourceFromZipkinSpan(tags map[string]string, localServiceName string, resource pdata.Resource) { if tracetranslator.ResourceNotSet == localServiceName { return