From 5032352c6747d5ed6f6ebd3b75271c9b7d797649 Mon Sep 17 00:00:00 2001 From: Volodymyr Shulakov Date: Thu, 4 Jun 2026 16:29:13 +0300 Subject: [PATCH] fix(tempo): expose intrinsic span fields in /api/v2/search/tags Intrinsic span fields (name, kind, status, duration) have no attribute prefix, so they were skipped during the field_names tag discovery, and requesting scope=intrinsic returned an error. As a result the intrinsic scope of the Tempo /api/v2/search/tags response was always empty and these fields did not appear in the Grafana Traces Drilldown attribute breakdown. Advertise a static "intrinsic" scope listing the intrinsics that VictoriaTraces can filter and group by, and support scope=intrinsic. --- app/vtselect/traces/tempo/tempo.go | 43 ++- app/vtselect/traces/tempo/tempo.qtpl | 14 +- app/vtselect/traces/tempo/tempo.qtpl.go | 375 +++++++++++---------- app/vtselect/traces/tempo/tempo_test.go | 62 ++++ docs/victoriatraces/changelog/CHANGELOG.md | 1 + 5 files changed, 312 insertions(+), 183 deletions(-) create mode 100644 app/vtselect/traces/tempo/tempo_test.go diff --git a/app/vtselect/traces/tempo/tempo.go b/app/vtselect/traces/tempo/tempo.go index 7b0039048..c6eaabbf0 100644 --- a/app/vtselect/traces/tempo/tempo.go +++ b/app/vtselect/traces/tempo/tempo.go @@ -107,7 +107,7 @@ func processSearchTagsRequest(ctx context.Context, w http.ResponseWriter, r *htt // Write results w.Header().Set("Content-Type", "application/json") - WriteSearchTagsResponse(w, result.resourceTagList, result.spanTagList, result.eventTagList, result.linkTagList, result.instrumentationScopeTagList) + WriteSearchTagsResponse(w, result.resourceTagList, result.spanTagList, result.eventTagList, result.linkTagList, result.instrumentationScopeTagList, result.intrinsicTagList) } // processSearchTagValuesRequest handle the Tempo /api/v2/search/tag/*/values API request. @@ -249,7 +249,29 @@ func processQueryV2Request(ctx context.Context, w http.ResponseWriter, r *http.R } type searchTagResult struct { - resourceTagList, spanTagList, eventTagList, linkTagList, instrumentationScopeTagList []string + resourceTagList, spanTagList, eventTagList, linkTagList, instrumentationScopeTagList, intrinsicTagList []string +} + +// intrinsicTags is the static set of intrinsic span attributes advertised under +// the "intrinsic" scope of /api/v2/search/tags, matching Tempo. Intrinsics have +// no attribute prefix (unlike resource_attr:/span_attr:) so they are not +// discovered by the field_names scan; without this list they never appear in +// Grafana Traces Drilldown's attribute breakdown. Only intrinsics that +// VictoriaTraces can filter and group by are listed, so every advertised tag +// works in the breakdown: +// - name -> NameField +// - kind -> KindField +// - status -> StatusCodeField (see TraceQLFieldToVTField) +// - duration -> DurationField +var intrinsicTags = []string{ + otelpb.NameField, + otelpb.KindField, + "status", + otelpb.DurationField, +} + +func intrinsicTagsCopy() []string { + return append([]string{}, intrinsicTags...) } func searchTags(ctx context.Context, cp *tracecommon.CommonParams, traceQLStr string, scope string, start, end, limit int64) (*searchTagResult, error) { @@ -281,7 +303,16 @@ func searchTags(ctx context.Context, cp *tracecommon.CommonParams, traceQLStr st return nil, errors.New("scope: link is not supported yet") //scopes = fmt.Sprintf(`| filter name:"%s:"*`, otelpb.LinkPrefix+otelpb.LinkAttrPrefix) case "intrinsic": - return nil, errors.New("scope: intrinsic is not supported yet") + // Intrinsics are a fixed set independent of ingested data, so there is no + // need to scan field names — return the static list directly. + return &searchTagResult{ + resourceTagList: []string{}, + spanTagList: []string{}, + instrumentationScopeTagList: []string{}, + eventTagList: []string{}, + linkTagList: []string{}, + intrinsicTagList: intrinsicTagsCopy(), + }, nil case "", "all": // todo: this does not align with the doc, but user usually don't expect a result fully match the limit // because they're not really looking for a specific tag name when no scope argument is used. it's likely @@ -314,6 +345,12 @@ func searchTags(ctx context.Context, cp *tracecommon.CommonParams, traceQLStr st instrumentationScopeTagList: []string{}, eventTagList: []string{}, linkTagList: []string{}, + intrinsicTagList: []string{}, + } + // scope=all / empty scope: advertise intrinsics alongside the discovered + // attribute tags so the Drilldown breakdown lists name/kind/status/duration. + if scope == "" || scope == "all" { + result.intrinsicTagList = intrinsicTagsCopy() } for i := range fieldNames { if strings.HasPrefix(fieldNames[i], otelpb.SpanAttrPrefixField) { diff --git a/app/vtselect/traces/tempo/tempo.qtpl b/app/vtselect/traces/tempo/tempo.qtpl index 41a6a5bb9..15fddd7a9 100644 --- a/app/vtselect/traces/tempo/tempo.qtpl +++ b/app/vtselect/traces/tempo/tempo.qtpl @@ -5,7 +5,7 @@ {% stripspace %} -{% func SearchTagsResponse(resourceTagList, spanTagList, eventTagList, linkTagList, instrumentationScopeTagList []string) %} +{% func SearchTagsResponse(resourceTagList, spanTagList, eventTagList, linkTagList, instrumentationScopeTagList, intrinsicTagList []string) %} { {% code sort.Slice(resourceTagList, func(i, j int) bool { return resourceTagList[i] < resourceTagList[j] }) @@ -13,8 +13,20 @@ sort.Slice(eventTagList, func(i, j int) bool { return eventTagList[i] < eventTagList[j] }) sort.Slice(linkTagList, func(i, j int) bool { return linkTagList[i] < linkTagList[j] }) sort.Slice(instrumentationScopeTagList, func(i, j int) bool { return instrumentationScopeTagList[i] < instrumentationScopeTagList[j] }) + sort.Slice(intrinsicTagList, func(i, j int) bool { return intrinsicTagList[i] < intrinsicTagList[j] }) %} "scopes":[ + { + "name": "intrinsic", + "tags": [ + {% if len(intrinsicTagList) > 0 %} + {%q= intrinsicTagList[0] %} + {% for _, tag := range intrinsicTagList[1:] %} + ,{%q= tag %} + {% endfor %} + {% endif %} + ] + }, { "name": "resource", "tags": [ diff --git a/app/vtselect/traces/tempo/tempo.qtpl.go b/app/vtselect/traces/tempo/tempo.qtpl.go index 1cbbedcbc..5636983bb 100644 --- a/app/vtselect/traces/tempo/tempo.qtpl.go +++ b/app/vtselect/traces/tempo/tempo.qtpl.go @@ -24,7 +24,7 @@ var ( ) //line app/vtselect/traces/tempo/tempo.qtpl:8 -func StreamSearchTagsResponse(qw422016 *qt422016.Writer, resourceTagList, spanTagList, eventTagList, linkTagList, instrumentationScopeTagList []string) { +func StreamSearchTagsResponse(qw422016 *qt422016.Writer, resourceTagList, spanTagList, eventTagList, linkTagList, instrumentationScopeTagList, intrinsicTagList []string) { //line app/vtselect/traces/tempo/tempo.qtpl:8 qw422016.N().S(`{`) //line app/vtselect/traces/tempo/tempo.qtpl:11 @@ -33,365 +33,382 @@ func StreamSearchTagsResponse(qw422016 *qt422016.Writer, resourceTagList, spanTa sort.Slice(eventTagList, func(i, j int) bool { return eventTagList[i] < eventTagList[j] }) sort.Slice(linkTagList, func(i, j int) bool { return linkTagList[i] < linkTagList[j] }) sort.Slice(instrumentationScopeTagList, func(i, j int) bool { return instrumentationScopeTagList[i] < instrumentationScopeTagList[j] }) + sort.Slice(intrinsicTagList, func(i, j int) bool { return intrinsicTagList[i] < intrinsicTagList[j] }) -//line app/vtselect/traces/tempo/tempo.qtpl:16 - qw422016.N().S(`"scopes":[{"name": "resource","tags": [`) -//line app/vtselect/traces/tempo/tempo.qtpl:21 - if len(resourceTagList) > 0 { +//line app/vtselect/traces/tempo/tempo.qtpl:17 + qw422016.N().S(`"scopes":[{"name": "intrinsic","tags": [`) //line app/vtselect/traces/tempo/tempo.qtpl:22 - qw422016.N().Q(resourceTagList[0]) + if len(intrinsicTagList) > 0 { //line app/vtselect/traces/tempo/tempo.qtpl:23 + qw422016.N().Q(intrinsicTagList[0]) +//line app/vtselect/traces/tempo/tempo.qtpl:24 + for _, tag := range intrinsicTagList[1:] { +//line app/vtselect/traces/tempo/tempo.qtpl:24 + qw422016.N().S(`,`) +//line app/vtselect/traces/tempo/tempo.qtpl:25 + qw422016.N().Q(tag) +//line app/vtselect/traces/tempo/tempo.qtpl:26 + } +//line app/vtselect/traces/tempo/tempo.qtpl:27 + } +//line app/vtselect/traces/tempo/tempo.qtpl:27 + qw422016.N().S(`]},{"name": "resource","tags": [`) +//line app/vtselect/traces/tempo/tempo.qtpl:33 + if len(resourceTagList) > 0 { +//line app/vtselect/traces/tempo/tempo.qtpl:34 + qw422016.N().Q(resourceTagList[0]) +//line app/vtselect/traces/tempo/tempo.qtpl:35 for _, tag := range resourceTagList[1:] { -//line app/vtselect/traces/tempo/tempo.qtpl:23 +//line app/vtselect/traces/tempo/tempo.qtpl:35 qw422016.N().S(`,`) -//line app/vtselect/traces/tempo/tempo.qtpl:24 +//line app/vtselect/traces/tempo/tempo.qtpl:36 qw422016.N().Q(tag) -//line app/vtselect/traces/tempo/tempo.qtpl:25 +//line app/vtselect/traces/tempo/tempo.qtpl:37 } -//line app/vtselect/traces/tempo/tempo.qtpl:26 +//line app/vtselect/traces/tempo/tempo.qtpl:38 } -//line app/vtselect/traces/tempo/tempo.qtpl:26 +//line app/vtselect/traces/tempo/tempo.qtpl:38 qw422016.N().S(`]},{"name": "span","tags": [`) -//line app/vtselect/traces/tempo/tempo.qtpl:32 +//line app/vtselect/traces/tempo/tempo.qtpl:44 if len(spanTagList) > 0 { -//line app/vtselect/traces/tempo/tempo.qtpl:33 +//line app/vtselect/traces/tempo/tempo.qtpl:45 qw422016.N().Q(spanTagList[0]) -//line app/vtselect/traces/tempo/tempo.qtpl:34 +//line app/vtselect/traces/tempo/tempo.qtpl:46 for _, tag := range spanTagList[1:] { -//line app/vtselect/traces/tempo/tempo.qtpl:34 +//line app/vtselect/traces/tempo/tempo.qtpl:46 qw422016.N().S(`,`) -//line app/vtselect/traces/tempo/tempo.qtpl:35 +//line app/vtselect/traces/tempo/tempo.qtpl:47 qw422016.N().Q(tag) -//line app/vtselect/traces/tempo/tempo.qtpl:36 +//line app/vtselect/traces/tempo/tempo.qtpl:48 } -//line app/vtselect/traces/tempo/tempo.qtpl:37 +//line app/vtselect/traces/tempo/tempo.qtpl:49 } -//line app/vtselect/traces/tempo/tempo.qtpl:37 +//line app/vtselect/traces/tempo/tempo.qtpl:49 qw422016.N().S(`]},{"name": "event","tags": [`) -//line app/vtselect/traces/tempo/tempo.qtpl:43 +//line app/vtselect/traces/tempo/tempo.qtpl:55 if len(eventTagList) > 0 { -//line app/vtselect/traces/tempo/tempo.qtpl:44 +//line app/vtselect/traces/tempo/tempo.qtpl:56 qw422016.N().Q(eventTagList[0]) -//line app/vtselect/traces/tempo/tempo.qtpl:45 +//line app/vtselect/traces/tempo/tempo.qtpl:57 for _, tag := range eventTagList[1:] { -//line app/vtselect/traces/tempo/tempo.qtpl:45 +//line app/vtselect/traces/tempo/tempo.qtpl:57 qw422016.N().S(`,`) -//line app/vtselect/traces/tempo/tempo.qtpl:46 +//line app/vtselect/traces/tempo/tempo.qtpl:58 qw422016.N().Q(tag) -//line app/vtselect/traces/tempo/tempo.qtpl:47 +//line app/vtselect/traces/tempo/tempo.qtpl:59 } -//line app/vtselect/traces/tempo/tempo.qtpl:48 +//line app/vtselect/traces/tempo/tempo.qtpl:60 } -//line app/vtselect/traces/tempo/tempo.qtpl:48 +//line app/vtselect/traces/tempo/tempo.qtpl:60 qw422016.N().S(`]},{"name": "link","tags": [`) -//line app/vtselect/traces/tempo/tempo.qtpl:54 +//line app/vtselect/traces/tempo/tempo.qtpl:66 if len(linkTagList) > 0 { -//line app/vtselect/traces/tempo/tempo.qtpl:55 +//line app/vtselect/traces/tempo/tempo.qtpl:67 qw422016.N().Q(linkTagList[0]) -//line app/vtselect/traces/tempo/tempo.qtpl:56 +//line app/vtselect/traces/tempo/tempo.qtpl:68 for _, tag := range linkTagList[1:] { -//line app/vtselect/traces/tempo/tempo.qtpl:56 +//line app/vtselect/traces/tempo/tempo.qtpl:68 qw422016.N().S(`,`) -//line app/vtselect/traces/tempo/tempo.qtpl:57 +//line app/vtselect/traces/tempo/tempo.qtpl:69 qw422016.N().Q(tag) -//line app/vtselect/traces/tempo/tempo.qtpl:58 +//line app/vtselect/traces/tempo/tempo.qtpl:70 } -//line app/vtselect/traces/tempo/tempo.qtpl:59 +//line app/vtselect/traces/tempo/tempo.qtpl:71 } -//line app/vtselect/traces/tempo/tempo.qtpl:59 +//line app/vtselect/traces/tempo/tempo.qtpl:71 qw422016.N().S(`]},{"name": "instrumentation","tags": [`) -//line app/vtselect/traces/tempo/tempo.qtpl:65 +//line app/vtselect/traces/tempo/tempo.qtpl:77 if len(instrumentationScopeTagList) > 0 { -//line app/vtselect/traces/tempo/tempo.qtpl:66 +//line app/vtselect/traces/tempo/tempo.qtpl:78 qw422016.N().Q(instrumentationScopeTagList[0]) -//line app/vtselect/traces/tempo/tempo.qtpl:67 +//line app/vtselect/traces/tempo/tempo.qtpl:79 for _, tag := range instrumentationScopeTagList[1:] { -//line app/vtselect/traces/tempo/tempo.qtpl:67 +//line app/vtselect/traces/tempo/tempo.qtpl:79 qw422016.N().S(`,`) -//line app/vtselect/traces/tempo/tempo.qtpl:68 +//line app/vtselect/traces/tempo/tempo.qtpl:80 qw422016.N().Q(tag) -//line app/vtselect/traces/tempo/tempo.qtpl:69 +//line app/vtselect/traces/tempo/tempo.qtpl:81 } -//line app/vtselect/traces/tempo/tempo.qtpl:70 +//line app/vtselect/traces/tempo/tempo.qtpl:82 } -//line app/vtselect/traces/tempo/tempo.qtpl:70 +//line app/vtselect/traces/tempo/tempo.qtpl:82 qw422016.N().S(`]}],"metrics": {"inspectedBytes": "0"}}`) -//line app/vtselect/traces/tempo/tempo.qtpl:78 +//line app/vtselect/traces/tempo/tempo.qtpl:90 } -//line app/vtselect/traces/tempo/tempo.qtpl:78 -func WriteSearchTagsResponse(qq422016 qtio422016.Writer, resourceTagList, spanTagList, eventTagList, linkTagList, instrumentationScopeTagList []string) { -//line app/vtselect/traces/tempo/tempo.qtpl:78 +//line app/vtselect/traces/tempo/tempo.qtpl:90 +func WriteSearchTagsResponse(qq422016 qtio422016.Writer, resourceTagList, spanTagList, eventTagList, linkTagList, instrumentationScopeTagList, intrinsicTagList []string) { +//line app/vtselect/traces/tempo/tempo.qtpl:90 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vtselect/traces/tempo/tempo.qtpl:78 - StreamSearchTagsResponse(qw422016, resourceTagList, spanTagList, eventTagList, linkTagList, instrumentationScopeTagList) -//line app/vtselect/traces/tempo/tempo.qtpl:78 +//line app/vtselect/traces/tempo/tempo.qtpl:90 + StreamSearchTagsResponse(qw422016, resourceTagList, spanTagList, eventTagList, linkTagList, instrumentationScopeTagList, intrinsicTagList) +//line app/vtselect/traces/tempo/tempo.qtpl:90 qt422016.ReleaseWriter(qw422016) -//line app/vtselect/traces/tempo/tempo.qtpl:78 +//line app/vtselect/traces/tempo/tempo.qtpl:90 } -//line app/vtselect/traces/tempo/tempo.qtpl:78 -func SearchTagsResponse(resourceTagList, spanTagList, eventTagList, linkTagList, instrumentationScopeTagList []string) string { -//line app/vtselect/traces/tempo/tempo.qtpl:78 +//line app/vtselect/traces/tempo/tempo.qtpl:90 +func SearchTagsResponse(resourceTagList, spanTagList, eventTagList, linkTagList, instrumentationScopeTagList, intrinsicTagList []string) string { +//line app/vtselect/traces/tempo/tempo.qtpl:90 qb422016 := qt422016.AcquireByteBuffer() -//line app/vtselect/traces/tempo/tempo.qtpl:78 - WriteSearchTagsResponse(qb422016, resourceTagList, spanTagList, eventTagList, linkTagList, instrumentationScopeTagList) -//line app/vtselect/traces/tempo/tempo.qtpl:78 +//line app/vtselect/traces/tempo/tempo.qtpl:90 + WriteSearchTagsResponse(qb422016, resourceTagList, spanTagList, eventTagList, linkTagList, instrumentationScopeTagList, intrinsicTagList) +//line app/vtselect/traces/tempo/tempo.qtpl:90 qs422016 := string(qb422016.B) -//line app/vtselect/traces/tempo/tempo.qtpl:78 +//line app/vtselect/traces/tempo/tempo.qtpl:90 qt422016.ReleaseByteBuffer(qb422016) -//line app/vtselect/traces/tempo/tempo.qtpl:78 +//line app/vtselect/traces/tempo/tempo.qtpl:90 return qs422016 -//line app/vtselect/traces/tempo/tempo.qtpl:78 +//line app/vtselect/traces/tempo/tempo.qtpl:90 } -//line app/vtselect/traces/tempo/tempo.qtpl:80 +//line app/vtselect/traces/tempo/tempo.qtpl:92 func StreamSearchTagValuesResponse(qw422016 *qt422016.Writer, tagValueList []string) { -//line app/vtselect/traces/tempo/tempo.qtpl:80 +//line app/vtselect/traces/tempo/tempo.qtpl:92 qw422016.N().S(`{`) -//line app/vtselect/traces/tempo/tempo.qtpl:83 +//line app/vtselect/traces/tempo/tempo.qtpl:95 sort.Slice(tagValueList, func(i, j int) bool { return tagValueList[i] < tagValueList[j] }) -//line app/vtselect/traces/tempo/tempo.qtpl:84 +//line app/vtselect/traces/tempo/tempo.qtpl:96 qw422016.N().S(`"tagValues":[`) -//line app/vtselect/traces/tempo/tempo.qtpl:86 +//line app/vtselect/traces/tempo/tempo.qtpl:98 if len(tagValueList) > 0 { -//line app/vtselect/traces/tempo/tempo.qtpl:86 +//line app/vtselect/traces/tempo/tempo.qtpl:98 qw422016.N().S(`{"type": "string","value":`) -//line app/vtselect/traces/tempo/tempo.qtpl:89 +//line app/vtselect/traces/tempo/tempo.qtpl:101 qw422016.N().Q(tagValueList[0]) -//line app/vtselect/traces/tempo/tempo.qtpl:89 +//line app/vtselect/traces/tempo/tempo.qtpl:101 qw422016.N().S(`}`) -//line app/vtselect/traces/tempo/tempo.qtpl:91 +//line app/vtselect/traces/tempo/tempo.qtpl:103 } -//line app/vtselect/traces/tempo/tempo.qtpl:92 +//line app/vtselect/traces/tempo/tempo.qtpl:104 if len(tagValueList) > 1 { -//line app/vtselect/traces/tempo/tempo.qtpl:93 +//line app/vtselect/traces/tempo/tempo.qtpl:105 for _, tag := range tagValueList[1:] { -//line app/vtselect/traces/tempo/tempo.qtpl:93 +//line app/vtselect/traces/tempo/tempo.qtpl:105 qw422016.N().S(`,{"type": "string","value":`) -//line app/vtselect/traces/tempo/tempo.qtpl:96 +//line app/vtselect/traces/tempo/tempo.qtpl:108 qw422016.N().Q(tag) -//line app/vtselect/traces/tempo/tempo.qtpl:96 +//line app/vtselect/traces/tempo/tempo.qtpl:108 qw422016.N().S(`}`) -//line app/vtselect/traces/tempo/tempo.qtpl:98 +//line app/vtselect/traces/tempo/tempo.qtpl:110 } -//line app/vtselect/traces/tempo/tempo.qtpl:99 +//line app/vtselect/traces/tempo/tempo.qtpl:111 } -//line app/vtselect/traces/tempo/tempo.qtpl:99 +//line app/vtselect/traces/tempo/tempo.qtpl:111 qw422016.N().S(`],"metrics": {"inspectedBytes": "0"}}`) -//line app/vtselect/traces/tempo/tempo.qtpl:105 +//line app/vtselect/traces/tempo/tempo.qtpl:117 } -//line app/vtselect/traces/tempo/tempo.qtpl:105 +//line app/vtselect/traces/tempo/tempo.qtpl:117 func WriteSearchTagValuesResponse(qq422016 qtio422016.Writer, tagValueList []string) { -//line app/vtselect/traces/tempo/tempo.qtpl:105 +//line app/vtselect/traces/tempo/tempo.qtpl:117 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vtselect/traces/tempo/tempo.qtpl:105 +//line app/vtselect/traces/tempo/tempo.qtpl:117 StreamSearchTagValuesResponse(qw422016, tagValueList) -//line app/vtselect/traces/tempo/tempo.qtpl:105 +//line app/vtselect/traces/tempo/tempo.qtpl:117 qt422016.ReleaseWriter(qw422016) -//line app/vtselect/traces/tempo/tempo.qtpl:105 +//line app/vtselect/traces/tempo/tempo.qtpl:117 } -//line app/vtselect/traces/tempo/tempo.qtpl:105 +//line app/vtselect/traces/tempo/tempo.qtpl:117 func SearchTagValuesResponse(tagValueList []string) string { -//line app/vtselect/traces/tempo/tempo.qtpl:105 +//line app/vtselect/traces/tempo/tempo.qtpl:117 qb422016 := qt422016.AcquireByteBuffer() -//line app/vtselect/traces/tempo/tempo.qtpl:105 +//line app/vtselect/traces/tempo/tempo.qtpl:117 WriteSearchTagValuesResponse(qb422016, tagValueList) -//line app/vtselect/traces/tempo/tempo.qtpl:105 +//line app/vtselect/traces/tempo/tempo.qtpl:117 qs422016 := string(qb422016.B) -//line app/vtselect/traces/tempo/tempo.qtpl:105 +//line app/vtselect/traces/tempo/tempo.qtpl:117 qt422016.ReleaseByteBuffer(qb422016) -//line app/vtselect/traces/tempo/tempo.qtpl:105 +//line app/vtselect/traces/tempo/tempo.qtpl:117 return qs422016 -//line app/vtselect/traces/tempo/tempo.qtpl:105 +//line app/vtselect/traces/tempo/tempo.qtpl:117 } -//line app/vtselect/traces/tempo/tempo.qtpl:107 +//line app/vtselect/traces/tempo/tempo.qtpl:119 func StreamSearchResponse(qw422016 *qt422016.Writer, summaryList []traceSummary) { -//line app/vtselect/traces/tempo/tempo.qtpl:107 +//line app/vtselect/traces/tempo/tempo.qtpl:119 qw422016.N().S(`{"traces": [`) -//line app/vtselect/traces/tempo/tempo.qtpl:110 +//line app/vtselect/traces/tempo/tempo.qtpl:122 if len(summaryList) > 0 { -//line app/vtselect/traces/tempo/tempo.qtpl:111 +//line app/vtselect/traces/tempo/tempo.qtpl:123 streamsummaryJson(qw422016, summaryList[0]) -//line app/vtselect/traces/tempo/tempo.qtpl:112 +//line app/vtselect/traces/tempo/tempo.qtpl:124 for _, summary := range summaryList[1:] { -//line app/vtselect/traces/tempo/tempo.qtpl:112 +//line app/vtselect/traces/tempo/tempo.qtpl:124 qw422016.N().S(`,`) -//line app/vtselect/traces/tempo/tempo.qtpl:113 +//line app/vtselect/traces/tempo/tempo.qtpl:125 streamsummaryJson(qw422016, summary) -//line app/vtselect/traces/tempo/tempo.qtpl:114 +//line app/vtselect/traces/tempo/tempo.qtpl:126 } -//line app/vtselect/traces/tempo/tempo.qtpl:115 +//line app/vtselect/traces/tempo/tempo.qtpl:127 } -//line app/vtselect/traces/tempo/tempo.qtpl:115 +//line app/vtselect/traces/tempo/tempo.qtpl:127 qw422016.N().S(`]}`) -//line app/vtselect/traces/tempo/tempo.qtpl:118 +//line app/vtselect/traces/tempo/tempo.qtpl:130 } -//line app/vtselect/traces/tempo/tempo.qtpl:118 +//line app/vtselect/traces/tempo/tempo.qtpl:130 func WriteSearchResponse(qq422016 qtio422016.Writer, summaryList []traceSummary) { -//line app/vtselect/traces/tempo/tempo.qtpl:118 +//line app/vtselect/traces/tempo/tempo.qtpl:130 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vtselect/traces/tempo/tempo.qtpl:118 +//line app/vtselect/traces/tempo/tempo.qtpl:130 StreamSearchResponse(qw422016, summaryList) -//line app/vtselect/traces/tempo/tempo.qtpl:118 +//line app/vtselect/traces/tempo/tempo.qtpl:130 qt422016.ReleaseWriter(qw422016) -//line app/vtselect/traces/tempo/tempo.qtpl:118 +//line app/vtselect/traces/tempo/tempo.qtpl:130 } -//line app/vtselect/traces/tempo/tempo.qtpl:118 +//line app/vtselect/traces/tempo/tempo.qtpl:130 func SearchResponse(summaryList []traceSummary) string { -//line app/vtselect/traces/tempo/tempo.qtpl:118 +//line app/vtselect/traces/tempo/tempo.qtpl:130 qb422016 := qt422016.AcquireByteBuffer() -//line app/vtselect/traces/tempo/tempo.qtpl:118 +//line app/vtselect/traces/tempo/tempo.qtpl:130 WriteSearchResponse(qb422016, summaryList) -//line app/vtselect/traces/tempo/tempo.qtpl:118 +//line app/vtselect/traces/tempo/tempo.qtpl:130 qs422016 := string(qb422016.B) -//line app/vtselect/traces/tempo/tempo.qtpl:118 +//line app/vtselect/traces/tempo/tempo.qtpl:130 qt422016.ReleaseByteBuffer(qb422016) -//line app/vtselect/traces/tempo/tempo.qtpl:118 +//line app/vtselect/traces/tempo/tempo.qtpl:130 return qs422016 -//line app/vtselect/traces/tempo/tempo.qtpl:118 +//line app/vtselect/traces/tempo/tempo.qtpl:130 } -//line app/vtselect/traces/tempo/tempo.qtpl:120 +//line app/vtselect/traces/tempo/tempo.qtpl:132 func streamsummaryJson(qw422016 *qt422016.Writer, summary traceSummary) { -//line app/vtselect/traces/tempo/tempo.qtpl:120 +//line app/vtselect/traces/tempo/tempo.qtpl:132 qw422016.N().S(`{"traceID":`) -//line app/vtselect/traces/tempo/tempo.qtpl:122 +//line app/vtselect/traces/tempo/tempo.qtpl:134 qw422016.N().Q(summary.traceID) -//line app/vtselect/traces/tempo/tempo.qtpl:122 +//line app/vtselect/traces/tempo/tempo.qtpl:134 qw422016.N().S(`,"rootServiceName":`) -//line app/vtselect/traces/tempo/tempo.qtpl:123 +//line app/vtselect/traces/tempo/tempo.qtpl:135 qw422016.N().Q(summary.rootServiceName) -//line app/vtselect/traces/tempo/tempo.qtpl:123 +//line app/vtselect/traces/tempo/tempo.qtpl:135 qw422016.N().S(`,"rootTraceName":`) -//line app/vtselect/traces/tempo/tempo.qtpl:124 +//line app/vtselect/traces/tempo/tempo.qtpl:136 qw422016.N().Q(summary.rootTraceName) -//line app/vtselect/traces/tempo/tempo.qtpl:124 +//line app/vtselect/traces/tempo/tempo.qtpl:136 qw422016.N().S(`,"startTimeUnixNano":`) -//line app/vtselect/traces/tempo/tempo.qtpl:125 +//line app/vtselect/traces/tempo/tempo.qtpl:137 qw422016.N().DL(summary.startTimeUnixNano) -//line app/vtselect/traces/tempo/tempo.qtpl:125 +//line app/vtselect/traces/tempo/tempo.qtpl:137 qw422016.N().S(`,"durationMs":`) -//line app/vtselect/traces/tempo/tempo.qtpl:126 +//line app/vtselect/traces/tempo/tempo.qtpl:138 if summary.endTimeUnixNano > 0 && summary.startTimeUnixNano > 0 { -//line app/vtselect/traces/tempo/tempo.qtpl:126 +//line app/vtselect/traces/tempo/tempo.qtpl:138 qw422016.N().DL((summary.endTimeUnixNano - summary.startTimeUnixNano) / 1e6) -//line app/vtselect/traces/tempo/tempo.qtpl:126 +//line app/vtselect/traces/tempo/tempo.qtpl:138 } else { -//line app/vtselect/traces/tempo/tempo.qtpl:126 +//line app/vtselect/traces/tempo/tempo.qtpl:138 qw422016.N().S(`0`) -//line app/vtselect/traces/tempo/tempo.qtpl:126 +//line app/vtselect/traces/tempo/tempo.qtpl:138 } -//line app/vtselect/traces/tempo/tempo.qtpl:126 +//line app/vtselect/traces/tempo/tempo.qtpl:138 qw422016.N().S(`,"spanSet":`) -//line app/vtselect/traces/tempo/tempo.qtpl:127 +//line app/vtselect/traces/tempo/tempo.qtpl:139 streamspanSetJSON(qw422016, summary) -//line app/vtselect/traces/tempo/tempo.qtpl:127 +//line app/vtselect/traces/tempo/tempo.qtpl:139 qw422016.N().S(`,"spanSets":[`) -//line app/vtselect/traces/tempo/tempo.qtpl:128 +//line app/vtselect/traces/tempo/tempo.qtpl:140 streamspanSetJSON(qw422016, summary) -//line app/vtselect/traces/tempo/tempo.qtpl:128 +//line app/vtselect/traces/tempo/tempo.qtpl:140 qw422016.N().S(`]}`) -//line app/vtselect/traces/tempo/tempo.qtpl:130 +//line app/vtselect/traces/tempo/tempo.qtpl:142 } -//line app/vtselect/traces/tempo/tempo.qtpl:130 +//line app/vtselect/traces/tempo/tempo.qtpl:142 func writesummaryJson(qq422016 qtio422016.Writer, summary traceSummary) { -//line app/vtselect/traces/tempo/tempo.qtpl:130 +//line app/vtselect/traces/tempo/tempo.qtpl:142 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vtselect/traces/tempo/tempo.qtpl:130 +//line app/vtselect/traces/tempo/tempo.qtpl:142 streamsummaryJson(qw422016, summary) -//line app/vtselect/traces/tempo/tempo.qtpl:130 +//line app/vtselect/traces/tempo/tempo.qtpl:142 qt422016.ReleaseWriter(qw422016) -//line app/vtselect/traces/tempo/tempo.qtpl:130 +//line app/vtselect/traces/tempo/tempo.qtpl:142 } -//line app/vtselect/traces/tempo/tempo.qtpl:130 +//line app/vtselect/traces/tempo/tempo.qtpl:142 func summaryJson(summary traceSummary) string { -//line app/vtselect/traces/tempo/tempo.qtpl:130 +//line app/vtselect/traces/tempo/tempo.qtpl:142 qb422016 := qt422016.AcquireByteBuffer() -//line app/vtselect/traces/tempo/tempo.qtpl:130 +//line app/vtselect/traces/tempo/tempo.qtpl:142 writesummaryJson(qb422016, summary) -//line app/vtselect/traces/tempo/tempo.qtpl:130 +//line app/vtselect/traces/tempo/tempo.qtpl:142 qs422016 := string(qb422016.B) -//line app/vtselect/traces/tempo/tempo.qtpl:130 +//line app/vtselect/traces/tempo/tempo.qtpl:142 qt422016.ReleaseByteBuffer(qb422016) -//line app/vtselect/traces/tempo/tempo.qtpl:130 +//line app/vtselect/traces/tempo/tempo.qtpl:142 return qs422016 -//line app/vtselect/traces/tempo/tempo.qtpl:130 +//line app/vtselect/traces/tempo/tempo.qtpl:142 } -//line app/vtselect/traces/tempo/tempo.qtpl:132 +//line app/vtselect/traces/tempo/tempo.qtpl:144 func streamspanSetJSON(qw422016 *qt422016.Writer, summary traceSummary) { -//line app/vtselect/traces/tempo/tempo.qtpl:132 +//line app/vtselect/traces/tempo/tempo.qtpl:144 qw422016.N().S(`{"spans":[`) -//line app/vtselect/traces/tempo/tempo.qtpl:135 +//line app/vtselect/traces/tempo/tempo.qtpl:147 if summary.rootSpanID != "" { -//line app/vtselect/traces/tempo/tempo.qtpl:135 +//line app/vtselect/traces/tempo/tempo.qtpl:147 qw422016.N().S(`{"spanID":`) -//line app/vtselect/traces/tempo/tempo.qtpl:137 +//line app/vtselect/traces/tempo/tempo.qtpl:149 qw422016.N().Q(summary.rootSpanID) -//line app/vtselect/traces/tempo/tempo.qtpl:137 +//line app/vtselect/traces/tempo/tempo.qtpl:149 qw422016.N().S(`,"startTimeUnixNano":`) -//line app/vtselect/traces/tempo/tempo.qtpl:138 +//line app/vtselect/traces/tempo/tempo.qtpl:150 qw422016.N().Q(strconv.FormatInt(summary.rootStartTimeUnixNano, 10)) -//line app/vtselect/traces/tempo/tempo.qtpl:138 +//line app/vtselect/traces/tempo/tempo.qtpl:150 qw422016.N().S(`,"durationNanos":`) -//line app/vtselect/traces/tempo/tempo.qtpl:139 +//line app/vtselect/traces/tempo/tempo.qtpl:151 qw422016.N().Q(strconv.FormatInt(summary.rootEndTimeUnixNano-summary.rootStartTimeUnixNano, 10)) -//line app/vtselect/traces/tempo/tempo.qtpl:139 +//line app/vtselect/traces/tempo/tempo.qtpl:151 qw422016.N().S(`,"attributes":[{"key":"service.name","value":{"stringValue":`) -//line app/vtselect/traces/tempo/tempo.qtpl:141 +//line app/vtselect/traces/tempo/tempo.qtpl:153 qw422016.N().Q(summary.rootServiceName) -//line app/vtselect/traces/tempo/tempo.qtpl:141 +//line app/vtselect/traces/tempo/tempo.qtpl:153 qw422016.N().S(`}},{"key":"nestedSetParent","value":{"intValue":"-1"}}]}`) -//line app/vtselect/traces/tempo/tempo.qtpl:145 +//line app/vtselect/traces/tempo/tempo.qtpl:157 } -//line app/vtselect/traces/tempo/tempo.qtpl:145 +//line app/vtselect/traces/tempo/tempo.qtpl:157 qw422016.N().S(`],"matched":`) -//line app/vtselect/traces/tempo/tempo.qtpl:147 +//line app/vtselect/traces/tempo/tempo.qtpl:159 if summary.rootSpanID != "" { -//line app/vtselect/traces/tempo/tempo.qtpl:147 +//line app/vtselect/traces/tempo/tempo.qtpl:159 qw422016.N().S(`1`) -//line app/vtselect/traces/tempo/tempo.qtpl:147 +//line app/vtselect/traces/tempo/tempo.qtpl:159 } else { -//line app/vtselect/traces/tempo/tempo.qtpl:147 +//line app/vtselect/traces/tempo/tempo.qtpl:159 qw422016.N().S(`0`) -//line app/vtselect/traces/tempo/tempo.qtpl:147 +//line app/vtselect/traces/tempo/tempo.qtpl:159 } -//line app/vtselect/traces/tempo/tempo.qtpl:147 +//line app/vtselect/traces/tempo/tempo.qtpl:159 qw422016.N().S(`}`) -//line app/vtselect/traces/tempo/tempo.qtpl:149 +//line app/vtselect/traces/tempo/tempo.qtpl:161 } -//line app/vtselect/traces/tempo/tempo.qtpl:149 +//line app/vtselect/traces/tempo/tempo.qtpl:161 func writespanSetJSON(qq422016 qtio422016.Writer, summary traceSummary) { -//line app/vtselect/traces/tempo/tempo.qtpl:149 +//line app/vtselect/traces/tempo/tempo.qtpl:161 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vtselect/traces/tempo/tempo.qtpl:149 +//line app/vtselect/traces/tempo/tempo.qtpl:161 streamspanSetJSON(qw422016, summary) -//line app/vtselect/traces/tempo/tempo.qtpl:149 +//line app/vtselect/traces/tempo/tempo.qtpl:161 qt422016.ReleaseWriter(qw422016) -//line app/vtselect/traces/tempo/tempo.qtpl:149 +//line app/vtselect/traces/tempo/tempo.qtpl:161 } -//line app/vtselect/traces/tempo/tempo.qtpl:149 +//line app/vtselect/traces/tempo/tempo.qtpl:161 func spanSetJSON(summary traceSummary) string { -//line app/vtselect/traces/tempo/tempo.qtpl:149 +//line app/vtselect/traces/tempo/tempo.qtpl:161 qb422016 := qt422016.AcquireByteBuffer() -//line app/vtselect/traces/tempo/tempo.qtpl:149 +//line app/vtselect/traces/tempo/tempo.qtpl:161 writespanSetJSON(qb422016, summary) -//line app/vtselect/traces/tempo/tempo.qtpl:149 +//line app/vtselect/traces/tempo/tempo.qtpl:161 qs422016 := string(qb422016.B) -//line app/vtselect/traces/tempo/tempo.qtpl:149 +//line app/vtselect/traces/tempo/tempo.qtpl:161 qt422016.ReleaseByteBuffer(qb422016) -//line app/vtselect/traces/tempo/tempo.qtpl:149 +//line app/vtselect/traces/tempo/tempo.qtpl:161 return qs422016 -//line app/vtselect/traces/tempo/tempo.qtpl:149 +//line app/vtselect/traces/tempo/tempo.qtpl:161 } diff --git a/app/vtselect/traces/tempo/tempo_test.go b/app/vtselect/traces/tempo/tempo_test.go new file mode 100644 index 000000000..9ecead976 --- /dev/null +++ b/app/vtselect/traces/tempo/tempo_test.go @@ -0,0 +1,62 @@ +package tempo + +import ( + "context" + "encoding/json" + "slices" + "testing" +) + +// Intrinsic span fields have no attribute prefix, so they are not discovered by +// the field_names scan. They must be advertised explicitly under the "intrinsic" +// scope of /api/v2/search/tags, otherwise they never appear in the Grafana +// Traces Drilldown attribute breakdown. +func TestSearchTagsIntrinsicScope(t *testing.T) { + res, err := searchTags(context.Background(), nil, "{}", "intrinsic", 0, 0, 100) + if err != nil { + t.Fatalf("searchTags(scope=intrinsic): %s", err) + } + for _, want := range []string{"name", "kind", "status", "duration"} { + if !slices.Contains(res.intrinsicTagList, want) { + t.Fatalf("intrinsic scope missing %q; got %v", want, res.intrinsicTagList) + } + } + // scope=intrinsic must not leak into other scopes. + if len(res.spanTagList) != 0 || len(res.resourceTagList) != 0 { + t.Fatalf("intrinsic scope must not populate span/resource lists; got span=%v resource=%v", + res.spanTagList, res.resourceTagList) + } +} + +// The /api/v2/search/tags response must expose an "intrinsic" scope carrying the +// intrinsic tag names, matching Tempo's response shape. +func TestSearchTagsResponseIncludesIntrinsicScope(t *testing.T) { + out := SearchTagsResponse(nil, nil, nil, nil, nil, []string{"name", "kind", "status", "duration"}) + + var parsed struct { + Scopes []struct { + Name string `json:"name"` + Tags []string `json:"tags"` + } `json:"scopes"` + } + if err := json.Unmarshal([]byte(out), &parsed); err != nil { + t.Fatalf("response is not valid JSON: %s\n%s", err, out) + } + + var intrinsic []string + found := false + for _, sc := range parsed.Scopes { + if sc.Name == "intrinsic" { + found = true + intrinsic = sc.Tags + } + } + if !found { + t.Fatalf("response missing intrinsic scope: %s", out) + } + for _, want := range []string{"name", "kind", "status", "duration"} { + if !slices.Contains(intrinsic, want) { + t.Fatalf("intrinsic scope missing %q; got %v", want, intrinsic) + } + } +} diff --git a/docs/victoriatraces/changelog/CHANGELOG.md b/docs/victoriatraces/changelog/CHANGELOG.md index c552cfc0b..9351392b6 100644 --- a/docs/victoriatraces/changelog/CHANGELOG.md +++ b/docs/victoriatraces/changelog/CHANGELOG.md @@ -15,6 +15,7 @@ The following `tip` changes can be tested by building VictoriaTraces components * FEATURE: [Single-node VictoriaTraces](https://docs.victoriametrics.com/victoriatraces/) and vtstorage in [VictoriaTraces cluster](https://docs.victoriametrics.com/victoriatraces/cluster/): expose filesystem type for storage paths via the `vm_fs_info` metric. This helps identify filesystem-specific issues during troubleshooting. See [#164](https://github.com/VictoriaMetrics/VictoriaTraces/issues/164) for details. * BUGFIX: [Single-node VictoriaTraces](https://docs.victoriametrics.com/victoriatraces/) and vtinsert in [VictoriaTraces cluster](https://docs.victoriametrics.com/victoriatraces/cluster/): response empty partial success correctly when error message for OTLP ingestion is empty. Thank @immanuwell for [the pull request #170](https://github.com/VictoriaMetrics/VictoriaTraces/pull/170). +* BUGFIX: [Single-node VictoriaTraces](https://docs.victoriametrics.com/victoriatraces/) and vtselect in [VictoriaTraces cluster](https://docs.victoriametrics.com/victoriatraces/cluster/): return intrinsic span fields (`name`, `kind`, `status`, `duration`) under the `intrinsic` scope of the Tempo `/api/v2/search/tags` endpoint, and support `scope=intrinsic`. Previously these fields were missing (they have no attribute prefix and were skipped during tag discovery, and `scope=intrinsic` returned an error), so they did not appear in the [Grafana Traces Drilldown](https://grafana.com/docs/grafana-cloud/visualizations/simplified-exploration/traces/) attribute breakdown. Thank @vshulakov-sh for [the pull request #175](https://github.com/VictoriaMetrics/VictoriaTraces/pull/175). ## [v0.9.2](https://github.com/VictoriaMetrics/VictoriaTraces/releases/tag/v0.9.2)