mirror of
https://github.com/hanzoai/traces.git
synced 2026-10-30 18:20:41 +00:00
* Change vp5 array handling, to not separate array vs non-array types for dedicated columns. This restores the precondition that all values of the given primitive type (string,int) exist in the column, and read path operations (search, fetch, tag lookup) will work as expected * changelog
940 lines
34 KiB
Go
940 lines
34 KiB
Go
package vparquet5
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"testing"
|
|
|
|
"github.com/grafana/tempo/pkg/collector"
|
|
"github.com/grafana/tempo/pkg/tempopb"
|
|
"github.com/grafana/tempo/pkg/traceql"
|
|
"github.com/grafana/tempo/pkg/util/test"
|
|
"github.com/grafana/tempo/tempodb/backend"
|
|
"github.com/grafana/tempo/tempodb/encoding/common"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestFetchTagNames(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
query string
|
|
expectedSpanValues []string
|
|
expectedResourceValues []string
|
|
expectedEventValues []string
|
|
expectedLinkValues []string
|
|
expectedInstrumentationValues []string
|
|
}{
|
|
{
|
|
name: "no query - fall back to old search",
|
|
query: "{}",
|
|
expectedSpanValues: []string{
|
|
"generic-01-01",
|
|
"generic-01-02",
|
|
"generic-02-01",
|
|
"span-same",
|
|
},
|
|
expectedResourceValues: []string{"generic-01", "generic-02", "resource-same"},
|
|
expectedEventValues: []string{"event-generic-01-01", "event-generic-02-01"},
|
|
expectedLinkValues: []string{"link-generic-01-01", "link-generic-02-01"},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-1", "scope-attr-str-2"},
|
|
},
|
|
{
|
|
name: "matches nothing",
|
|
query: "{span.generic-01-01=`bar`}",
|
|
expectedSpanValues: []string{},
|
|
expectedResourceValues: []string{},
|
|
expectedEventValues: []string{},
|
|
expectedLinkValues: []string{},
|
|
expectedInstrumentationValues: []string{},
|
|
},
|
|
// span
|
|
{
|
|
name: "intrinsic span",
|
|
query: "{statusMessage=`msg-01-01`}",
|
|
expectedSpanValues: []string{"generic-01-01", "span-same"},
|
|
expectedResourceValues: []string{"generic-01", "resource-same"},
|
|
expectedEventValues: []string{"event-generic-01-01"},
|
|
expectedLinkValues: []string{"link-generic-01-01"},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-1"},
|
|
},
|
|
{
|
|
name: "generic span",
|
|
query: "{span.generic-01-01=`foo`}",
|
|
expectedSpanValues: []string{"generic-01-01", "span-same"},
|
|
expectedResourceValues: []string{"generic-01", "resource-same"},
|
|
expectedEventValues: []string{"event-generic-01-01"},
|
|
expectedLinkValues: []string{"link-generic-01-01"},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-1"},
|
|
},
|
|
{
|
|
name: "match two spans",
|
|
query: "{span.span-same=`foo`}",
|
|
expectedSpanValues: []string{"generic-01-01", "span-same", "generic-02-01"},
|
|
expectedResourceValues: []string{"generic-01", "resource-same", "generic-02"},
|
|
expectedEventValues: []string{"event-generic-01-01", "event-generic-02-01"},
|
|
expectedLinkValues: []string{"link-generic-01-01", "link-generic-02-01"},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-1", "scope-attr-str-2"},
|
|
},
|
|
// resource
|
|
{
|
|
name: "generic resource",
|
|
query: "{resource.generic-01=`bar`}",
|
|
expectedSpanValues: []string{"generic-01-01", "generic-01-02", "span-same"},
|
|
expectedResourceValues: []string{"generic-01", "resource-same"},
|
|
expectedEventValues: []string{"event-generic-01-01"},
|
|
expectedLinkValues: []string{"link-generic-01-01"},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-1"},
|
|
},
|
|
{
|
|
name: "match two resources",
|
|
query: "{resource.resource-same=`foo`}",
|
|
expectedSpanValues: []string{"generic-01-01", "generic-01-02", "span-same", "generic-02-01"},
|
|
expectedResourceValues: []string{"generic-01", "resource-same", "generic-02"},
|
|
expectedEventValues: []string{"event-generic-01-01", "event-generic-02-01"},
|
|
expectedLinkValues: []string{"link-generic-01-01", "link-generic-02-01"},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-1", "scope-attr-str-2"},
|
|
},
|
|
// trace level match
|
|
{
|
|
name: "trace",
|
|
query: "{rootName=`root` }",
|
|
expectedSpanValues: []string{"generic-01-01", "generic-01-02", "span-same", "generic-02-01"},
|
|
expectedResourceValues: []string{"generic-01", "resource-same", "generic-02"},
|
|
expectedEventValues: []string{"event-generic-01-01", "event-generic-02-01"},
|
|
expectedLinkValues: []string{"link-generic-01-01", "link-generic-02-01"},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-1", "scope-attr-str-2"},
|
|
},
|
|
// span level nil attr
|
|
{
|
|
name: "span attr = nil",
|
|
query: "{span.generic-01-01=nil }",
|
|
expectedSpanValues: []string{"generic-01-02", "span-same", "generic-02-01"},
|
|
expectedResourceValues: []string{"generic-01", "resource-same", "generic-02"},
|
|
expectedEventValues: []string{"event-generic-02-01"},
|
|
expectedLinkValues: []string{"link-generic-02-01"},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-1", "scope-attr-str-2"},
|
|
},
|
|
// resource level nil attr
|
|
{
|
|
name: "resource attr = nil",
|
|
query: "{resource.generic-01=nil }",
|
|
expectedSpanValues: []string{"span-same", "generic-02-01"},
|
|
expectedResourceValues: []string{"resource-same", "generic-02"},
|
|
expectedEventValues: []string{"event-generic-02-01"},
|
|
expectedLinkValues: []string{"link-generic-02-01"},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-2"},
|
|
},
|
|
// well-known column != nil - regression test for panic caused by mixed types in OtherEntries
|
|
{
|
|
name: "well known resource op none",
|
|
query: "{resource.service.name}",
|
|
expectedSpanValues: []string{"generic-01-01", "generic-01-02", "span-same", "generic-02-01"},
|
|
expectedResourceValues: []string{"generic-01", "resource-same", "generic-02"},
|
|
expectedEventValues: []string{"event-generic-01-01", "event-generic-02-01"},
|
|
expectedLinkValues: []string{"link-generic-01-01", "link-generic-02-01"},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-1", "scope-attr-str-2"},
|
|
},
|
|
{
|
|
name: "well known resource",
|
|
query: "{resource.service.name != nil}",
|
|
expectedSpanValues: []string{"generic-01-01", "generic-01-02", "span-same", "generic-02-01"},
|
|
expectedResourceValues: []string{"generic-01", "resource-same", "generic-02"},
|
|
expectedEventValues: []string{"event-generic-01-01", "event-generic-02-01"},
|
|
expectedLinkValues: []string{"link-generic-01-01", "link-generic-02-01"},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-1", "scope-attr-str-2"},
|
|
},
|
|
|
|
// dedicated columns
|
|
{
|
|
name: "dedicated span op none",
|
|
query: "{span.dedicated.span.1}",
|
|
expectedSpanValues: []string{"generic-01-01", "generic-01-02", "span-same", "generic-02-01"},
|
|
expectedResourceValues: []string{"generic-01", "resource-same", "generic-02"},
|
|
expectedEventValues: []string{"event-generic-01-01", "event-generic-02-01"},
|
|
expectedLinkValues: []string{"link-generic-01-01", "link-generic-02-01"},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-1", "scope-attr-str-2"},
|
|
},
|
|
{
|
|
name: "dedicated span != nil",
|
|
query: "{span.dedicated.span.1 != nil}",
|
|
expectedSpanValues: []string{"generic-01-01", "generic-01-02", "span-same", "generic-02-01"},
|
|
expectedResourceValues: []string{"generic-01", "resource-same", "generic-02"},
|
|
expectedEventValues: []string{"event-generic-01-01", "event-generic-02-01"},
|
|
expectedLinkValues: []string{"link-generic-01-01", "link-generic-02-01"},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-1", "scope-attr-str-2"},
|
|
},
|
|
{
|
|
name: "dedicated span = nil",
|
|
query: "{span.dedicated.span.1 = nil}",
|
|
expectedSpanValues: []string{},
|
|
expectedResourceValues: []string{},
|
|
expectedEventValues: []string{},
|
|
expectedLinkValues: []string{},
|
|
expectedInstrumentationValues: []string{},
|
|
},
|
|
{
|
|
name: "dedicated resource != nil",
|
|
query: "{resource.dedicated.resource.1 != nil}",
|
|
expectedSpanValues: []string{"generic-01-01", "generic-01-02", "span-same", "generic-02-01"},
|
|
expectedResourceValues: []string{"generic-01", "resource-same", "generic-02"},
|
|
expectedEventValues: []string{"event-generic-01-01", "event-generic-02-01"},
|
|
expectedLinkValues: []string{"link-generic-01-01", "link-generic-02-01"},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-1", "scope-attr-str-2"},
|
|
},
|
|
{
|
|
name: "dedicated resource = nil",
|
|
query: "{resource.dedicated.resource.1 = nil}",
|
|
expectedSpanValues: []string{},
|
|
expectedResourceValues: []string{},
|
|
expectedEventValues: []string{},
|
|
expectedLinkValues: []string{},
|
|
expectedInstrumentationValues: []string{},
|
|
},
|
|
{
|
|
name: "dedicated resource op none",
|
|
query: "{resource.dedicated.resource.1}",
|
|
expectedSpanValues: []string{"generic-01-01", "generic-01-02", "span-same", "generic-02-01"},
|
|
expectedResourceValues: []string{"generic-01", "resource-same", "generic-02"},
|
|
expectedEventValues: []string{"event-generic-01-01", "event-generic-02-01"},
|
|
expectedLinkValues: []string{"link-generic-01-01", "link-generic-02-01"},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-1", "scope-attr-str-2"},
|
|
},
|
|
{
|
|
name: "dedicated event != nil",
|
|
query: "{event.dedicated.event.1 != nil}",
|
|
expectedSpanValues: []string{"generic-01-01", "span-same", "generic-02-01"},
|
|
expectedResourceValues: []string{"generic-01", "resource-same", "generic-02"},
|
|
expectedEventValues: []string{"event-generic-01-01", "event-generic-02-01"},
|
|
expectedLinkValues: []string{"link-generic-01-01", "link-generic-02-01"},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-1", "scope-attr-str-2"},
|
|
},
|
|
{
|
|
name: "dedicated event = nil",
|
|
query: "{event.dedicated.event.1 = nil}",
|
|
expectedSpanValues: []string{"generic-01-02"},
|
|
expectedResourceValues: []string{"generic-01", "resource-same"},
|
|
expectedEventValues: []string{},
|
|
expectedLinkValues: []string{},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-1"},
|
|
},
|
|
{
|
|
name: "dedicated event op none",
|
|
query: "{event.dedicated.event.1}",
|
|
expectedSpanValues: []string{"generic-01-01", "generic-01-02", "span-same", "generic-02-01"},
|
|
expectedResourceValues: []string{"generic-01", "resource-same", "generic-02"},
|
|
expectedEventValues: []string{"event-generic-01-01", "event-generic-02-01"},
|
|
expectedLinkValues: []string{"link-generic-01-01", "link-generic-02-01"},
|
|
expectedInstrumentationValues: []string{"scope-attr-str-1", "scope-attr-str-2"},
|
|
},
|
|
}
|
|
|
|
tr := &Trace{
|
|
TraceID: test.ValidTraceID(nil),
|
|
RootServiceName: "tr",
|
|
RootSpanName: "root",
|
|
ResourceSpans: []ResourceSpans{
|
|
{
|
|
Resource: Resource{
|
|
ServiceName: "svc-01",
|
|
Attrs: []Attribute{
|
|
{Key: "generic-01", Value: []string{"bar"}}, // generic
|
|
{Key: "resource-same", Value: []string{"foo"}},
|
|
},
|
|
DedicatedAttributes: DedicatedAttributes{
|
|
String01: []string{"dedicated-01"},
|
|
},
|
|
},
|
|
ScopeSpans: []ScopeSpans{
|
|
{
|
|
Scope: InstrumentationScope{
|
|
Name: "scope-1",
|
|
Version: "version-1",
|
|
DroppedAttributesCount: 1,
|
|
Attrs: []Attribute{
|
|
attr("scope-attr-str-1", "scope-attr-1"),
|
|
},
|
|
},
|
|
SpanCount: 2,
|
|
Spans: []Span{
|
|
{
|
|
SpanID: []byte("0101"),
|
|
Name: "span-01-01",
|
|
StatusMessage: "msg-01-01", // intrinsic
|
|
Attrs: []Attribute{
|
|
{Key: "generic-01-01", Value: []string{"foo"}}, // generic
|
|
{Key: "span-same", Value: []string{"foo"}}, // generic
|
|
},
|
|
DedicatedAttributes: DedicatedAttributes{
|
|
String01: []string{"dedicated-01-01"},
|
|
},
|
|
Events: []Event{
|
|
{
|
|
Name: "event-01-01",
|
|
Attrs: []Attribute{
|
|
{Key: "event-generic-01-01", Value: []string{"foo"}},
|
|
},
|
|
DedicatedAttributes: DedicatedAttributes{
|
|
String01: []string{"dedicated-01-01"},
|
|
},
|
|
},
|
|
},
|
|
Links: []Link{
|
|
{
|
|
SpanID: []byte("0101"),
|
|
Attrs: []Attribute{
|
|
{Key: "link-generic-01-01", Value: []string{"foo"}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
SpanID: []byte("0102"),
|
|
Name: "span-01-02",
|
|
StatusMessage: "msg-01-02", // intrinsic
|
|
Attrs: []Attribute{
|
|
{Key: "generic-01-02", Value: []string{"foo"}}, // generic
|
|
},
|
|
DedicatedAttributes: DedicatedAttributes{
|
|
String01: []string{"dedicated-01-02"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Resource: Resource{
|
|
ServiceName: "svc-02",
|
|
Attrs: []Attribute{
|
|
{Key: "generic-02", Value: []string{"bar"}}, // generic
|
|
{Key: "resource-same", Value: []string{"foo"}},
|
|
},
|
|
DedicatedAttributes: DedicatedAttributes{
|
|
String01: []string{"dedicated-02"},
|
|
},
|
|
},
|
|
ScopeSpans: []ScopeSpans{
|
|
{
|
|
Scope: InstrumentationScope{
|
|
Name: "scope-2",
|
|
Version: "version-2",
|
|
DroppedAttributesCount: 1,
|
|
Attrs: []Attribute{
|
|
attr("scope-attr-str-2", "scope-attr-2"),
|
|
},
|
|
},
|
|
SpanCount: 1,
|
|
Spans: []Span{
|
|
{
|
|
SpanID: []byte("0201"),
|
|
Name: "span-02-01",
|
|
StatusMessage: "msg-02-01", // intrinsic
|
|
Attrs: []Attribute{
|
|
{Key: "generic-02-01", Value: []string{"foo"}}, // generic
|
|
{Key: "span-same", Value: []string{"foo"}}, // generic
|
|
},
|
|
DedicatedAttributes: DedicatedAttributes{
|
|
String01: []string{"dedicated-02-01"},
|
|
},
|
|
Events: []Event{
|
|
{
|
|
Name: "event-02-01",
|
|
Attrs: []Attribute{
|
|
{Key: "event-generic-02-01", Value: []string{"foo"}},
|
|
},
|
|
DedicatedAttributes: DedicatedAttributes{
|
|
String01: []string{"dedicated-02-01"},
|
|
},
|
|
},
|
|
},
|
|
Links: []Link{
|
|
{
|
|
SpanID: []byte("0102"),
|
|
Attrs: []Attribute{
|
|
{Key: "link-generic-02-01", Value: []string{"foo"}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
ctx := context.TODO()
|
|
block := makeBackendBlockWithTraces(t, []*Trace{tr})
|
|
|
|
opts := common.DefaultSearchOptions()
|
|
|
|
for _, tc := range testCases {
|
|
for _, scope := range []traceql.AttributeScope{
|
|
traceql.AttributeScopeSpan,
|
|
traceql.AttributeScopeResource,
|
|
traceql.AttributeScopeNone,
|
|
traceql.AttributeScopeEvent,
|
|
traceql.AttributeScopeLink,
|
|
traceql.AttributeScopeInstrumentation,
|
|
} {
|
|
expectedSpanValues := tc.expectedSpanValues
|
|
expectedResourceValues := tc.expectedResourceValues
|
|
expectedEventValues := tc.expectedEventValues
|
|
expectedLinkValues := tc.expectedLinkValues
|
|
expectedInstrumentationValues := tc.expectedInstrumentationValues
|
|
|
|
// add dedicated and well known columns to expected values. the code currently does not
|
|
// attempt to perfectly filter these, but instead adds them to the return if any values are present
|
|
dedicatedSpanValues := []string{"dedicated.span.1"}
|
|
dedicatedResourceValues := []string{"dedicated.resource.1"}
|
|
dedicatedEventValues := []string{"dedicated.event.1"}
|
|
|
|
wellKnownResourceValues := []string{"service.name"}
|
|
|
|
expectedValues := map[string][]string{}
|
|
if scope == traceql.AttributeScopeSpan || scope == traceql.AttributeScopeNone {
|
|
expectedValues["span"] = append(expectedValues["span"], expectedSpanValues...)
|
|
expectedValues["span"] = append(expectedValues["span"], dedicatedSpanValues...)
|
|
}
|
|
if scope == traceql.AttributeScopeResource || scope == traceql.AttributeScopeNone {
|
|
expectedValues["resource"] = append(expectedValues["resource"], expectedResourceValues...)
|
|
expectedValues["resource"] = append(expectedValues["resource"], wellKnownResourceValues...)
|
|
expectedValues["resource"] = append(expectedValues["resource"], dedicatedResourceValues...)
|
|
}
|
|
if scope == traceql.AttributeScopeEvent || scope == traceql.AttributeScopeNone {
|
|
if len(expectedEventValues) > 0 {
|
|
expectedValues["event"] = append(expectedValues["event"], expectedEventValues...)
|
|
}
|
|
expectedValues["event"] = append(expectedValues["event"], dedicatedEventValues...)
|
|
}
|
|
if scope == traceql.AttributeScopeLink || scope == traceql.AttributeScopeNone {
|
|
if len(expectedLinkValues) > 0 {
|
|
expectedValues["link"] = append(expectedValues["link"], expectedLinkValues...)
|
|
}
|
|
}
|
|
|
|
if scope == traceql.AttributeScopeInstrumentation || scope == traceql.AttributeScopeNone {
|
|
if len(expectedInstrumentationValues) > 0 {
|
|
expectedValues["instrumentation"] = append(expectedValues["instrumentation"], expectedInstrumentationValues...)
|
|
}
|
|
}
|
|
|
|
t.Run(fmt.Sprintf("query: %s %s-%s", tc.name, tc.query, scope), func(t *testing.T) {
|
|
distinctAttrNames := collector.NewScopedDistinctString(0, 0, 0)
|
|
req, err := traceql.ExtractFetchSpansRequest(tc.query)
|
|
require.NoError(t, err)
|
|
|
|
// Build autocomplete request
|
|
autocompleteReq := traceql.FetchTagsRequest{
|
|
Conditions: req.Conditions,
|
|
Scope: scope,
|
|
}
|
|
mc := collector.NewMetricsCollector()
|
|
|
|
err = block.FetchTagNames(ctx, autocompleteReq, func(t string, scope traceql.AttributeScope) bool {
|
|
distinctAttrNames.Collect(scope.String(), t)
|
|
return false
|
|
}, mc.Add, opts)
|
|
require.NoError(t, err)
|
|
// test that callback is recording bytes read
|
|
require.Greater(t, mc.TotalValue(), uint64(100))
|
|
|
|
actualValues := distinctAttrNames.Strings()
|
|
|
|
require.Equal(t, len(expectedValues), len(actualValues))
|
|
for k := range expectedValues {
|
|
actual := actualValues[k]
|
|
sort.Strings(actual)
|
|
expected := expectedValues[k]
|
|
sort.Strings(expected)
|
|
|
|
require.Equal(t, expected, actual, "scope: %s", k)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFetchTagValues(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
tag, query string
|
|
expectedValues []tempopb.TagValue
|
|
tr *Trace
|
|
dc backend.DedicatedColumns
|
|
}{
|
|
{
|
|
name: "intrinsic with no query - match",
|
|
tag: "name",
|
|
query: "{}",
|
|
expectedValues: []tempopb.TagValue{
|
|
stringTagValue("hello"),
|
|
stringTagValue("world"),
|
|
},
|
|
},
|
|
{
|
|
name: "intrinsic with resource attribute - match",
|
|
tag: "name",
|
|
query: `{resource.namespace="namespace"}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("hello")},
|
|
},
|
|
{
|
|
name: "intrinsic with span attribute - match",
|
|
tag: "name",
|
|
query: `{span.foo="def"}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("hello")},
|
|
},
|
|
{
|
|
name: "intrinsic with span attribute and resource attribute - match",
|
|
tag: "name",
|
|
query: `{span.foo="def" && resource.namespace="namespace"}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("hello")},
|
|
},
|
|
{
|
|
name: "intrinsic with intrinsic attribute - match",
|
|
tag: "name",
|
|
query: `{kind=client}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("hello")},
|
|
},
|
|
{
|
|
name: "intrinsic with resource attribute - no match",
|
|
tag: "name",
|
|
query: `{resource.namespace="namespace3"}`,
|
|
expectedValues: []tempopb.TagValue{},
|
|
},
|
|
{
|
|
name: "intrinsic with span attribute - no match",
|
|
tag: "name",
|
|
query: `{span.foo="jkl"}`,
|
|
expectedValues: []tempopb.TagValue{},
|
|
},
|
|
{
|
|
name: "intrinsic with span attribute and resource attribute - no match",
|
|
tag: "name",
|
|
query: `{span.foo="jkl" && resource.namespace="namespace3"}`,
|
|
expectedValues: []tempopb.TagValue{},
|
|
},
|
|
{
|
|
name: "intrinsic with intrinsic attribute - no match",
|
|
tag: "name",
|
|
query: `{kind=internal}`,
|
|
expectedValues: []tempopb.TagValue{},
|
|
},
|
|
{
|
|
name: "resource attribute with no query - match",
|
|
tag: "resource.service.name",
|
|
query: `{}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("myservice"), stringTagValue("service2")},
|
|
},
|
|
{
|
|
name: "resource attribute with resource attribute - match",
|
|
tag: "resource.service.name",
|
|
query: `{resource.namespace="namespace"}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("myservice")},
|
|
},
|
|
{
|
|
name: "resource attribute with span attribute - match",
|
|
tag: "resource.service.name",
|
|
query: `{span.foo="def"}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("myservice")},
|
|
},
|
|
{
|
|
name: "resource attribute with span attribute and resource attribute - match",
|
|
tag: "resource.service.name",
|
|
query: `{span.foo="def" && resource.namespace="namespace"}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("myservice")},
|
|
},
|
|
{
|
|
name: "resource attribute with intrinsic attribute - match",
|
|
tag: "resource.service.name",
|
|
query: `{kind=client}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("myservice")},
|
|
},
|
|
{
|
|
name: "resource attribute with resource attribute - no match",
|
|
tag: "resource.service.name",
|
|
query: `{resource.namespace="namespace3"}`,
|
|
expectedValues: []tempopb.TagValue{},
|
|
},
|
|
{
|
|
name: "resource attribute with span attribute - no match",
|
|
tag: "resource.service.name",
|
|
query: `{span.foo="jkl"}`,
|
|
expectedValues: []tempopb.TagValue{},
|
|
},
|
|
{
|
|
name: "resource attribute with span attribute and resource attribute - no match",
|
|
tag: "resource.service.name",
|
|
query: `{span.foo="jkl" && resource.namespace="namespace3"}`,
|
|
expectedValues: []tempopb.TagValue{},
|
|
},
|
|
{
|
|
name: "resource attribute with intrinsic attribute - no match",
|
|
tag: "resource.service.name",
|
|
query: `{kind=internal}`,
|
|
expectedValues: []tempopb.TagValue{},
|
|
},
|
|
{
|
|
name: "span attribute with no query - match",
|
|
tag: "span.foo",
|
|
query: `{}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("def"), stringTagValue("ghi")},
|
|
},
|
|
{
|
|
name: "span attribute with resource attribute - match",
|
|
tag: "span.foo",
|
|
query: `{resource.namespace="namespace"}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("def")},
|
|
},
|
|
{
|
|
name: "span attribute with span attribute - match",
|
|
tag: "span.foo",
|
|
query: `{span.bar=123}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("def")},
|
|
},
|
|
{
|
|
name: "span attribute with span attribute and resource attribute - match",
|
|
tag: "span.foo",
|
|
query: `{span.bool=false && resource.namespace="namespace"}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("def")},
|
|
},
|
|
{
|
|
name: "span attribute with intrinsic attribute - match",
|
|
tag: "span.foo",
|
|
query: `{kind=client}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("def")},
|
|
},
|
|
{
|
|
name: "span attribute with resource attribute - no match",
|
|
tag: "span.foo",
|
|
query: `{resource.namespace="namespace3"}`,
|
|
expectedValues: []tempopb.TagValue{},
|
|
},
|
|
{
|
|
name: "span attribute with span attribute - no match",
|
|
tag: "span.foo",
|
|
query: `{span.foo="jkl"}`,
|
|
expectedValues: []tempopb.TagValue{},
|
|
},
|
|
{
|
|
name: "span attribute with span attribute and resource attribute - no match",
|
|
tag: "span.foo",
|
|
query: `{span.foo="jkl" && resource.namespace="namespace3"}`,
|
|
expectedValues: []tempopb.TagValue{},
|
|
},
|
|
{
|
|
name: "span attribute with intrinsic attribute - no match",
|
|
tag: "span.foo",
|
|
query: `{kind=internal}`,
|
|
expectedValues: []tempopb.TagValue{},
|
|
},
|
|
{
|
|
name: "trace intrinsic attribute with no query - match",
|
|
tag: "rootName",
|
|
query: `{}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("RootSpan")},
|
|
},
|
|
{
|
|
name: "trace intrinsic attribute with resource attribute - match",
|
|
tag: "rootName",
|
|
query: `{resource.namespace="namespace"}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("RootSpan")},
|
|
},
|
|
{
|
|
name: "trace intrinsic attribute with span attribute - match",
|
|
tag: "rootName",
|
|
query: `{span.foo="def"}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("RootSpan")},
|
|
},
|
|
{
|
|
name: "trace intrinsic attribute with span attribute and resource attribute - match",
|
|
tag: "rootName",
|
|
query: `{span.foo="def" && resource.namespace="namespace"}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("RootSpan")},
|
|
},
|
|
{
|
|
name: "trace intrinsic attribute with intrinsic attribute - match",
|
|
tag: "rootName",
|
|
query: `{kind=client}`,
|
|
expectedValues: []tempopb.TagValue{stringTagValue("RootSpan")},
|
|
},
|
|
{
|
|
name: "trace intrinsic attribute with resource attribute - no match",
|
|
tag: "rootName",
|
|
query: `{resource.namespace="namespace3"}`,
|
|
expectedValues: []tempopb.TagValue{},
|
|
},
|
|
{
|
|
name: "trace intrinsic attribute with span attribute - no match",
|
|
tag: "rootName",
|
|
query: `{span.foo="jkl"}`,
|
|
expectedValues: []tempopb.TagValue{},
|
|
},
|
|
{
|
|
name: "trace intrinsic attribute with span attribute and resource attribute - no match",
|
|
tag: "rootName",
|
|
query: `{span.foo="jkl" && resource.namespace="namespace3"}`,
|
|
expectedValues: []tempopb.TagValue{},
|
|
},
|
|
{
|
|
name: "trace intrinsic attribute with intrinsic attribute - no match",
|
|
tag: "rootName",
|
|
query: `{kind=internal}`,
|
|
expectedValues: []tempopb.TagValue{},
|
|
},
|
|
{
|
|
name: "unscoped attribute - not supported",
|
|
tag: ".service.name",
|
|
query: `{ .namespace="namespace"}`,
|
|
expectedValues: []tempopb.TagValue{intTagValue(123), intTagValue(1234), stringTagValue("myservice"), stringTagValue("service2"), stringTagValue("spanservicename"), stringTagValue("spanservicename2")},
|
|
},
|
|
{
|
|
name: "query with wrong op types - conditions are ignored",
|
|
tag: "status",
|
|
query: `{resource.service.name="myservice" && span.http.status_code=server && resource.namespace=server}`,
|
|
expectedValues: []tempopb.TagValue{
|
|
{Type: "keyword", Value: "error"},
|
|
},
|
|
},
|
|
{
|
|
name: "event attribute - match",
|
|
tag: "event.message",
|
|
query: `{resource.service.name="myservice"}`,
|
|
expectedValues: []tempopb.TagValue{
|
|
stringTagValue("exception"),
|
|
},
|
|
},
|
|
{
|
|
name: "link attribute - match",
|
|
tag: "link.opentracing.ref_type",
|
|
query: `{span.bar=123}`,
|
|
expectedValues: []tempopb.TagValue{
|
|
stringTagValue("child-of"),
|
|
},
|
|
},
|
|
{
|
|
name: "span attr nil with zero match",
|
|
tag: "span.bar",
|
|
query: "{span.foo=nil}",
|
|
expectedValues: []tempopb.TagValue{},
|
|
},
|
|
{
|
|
name: "resource attr nil with one match",
|
|
tag: "resource.foo",
|
|
query: "{resource.asdf=nil}",
|
|
expectedValues: []tempopb.TagValue{stringTagValue("abc2")},
|
|
},
|
|
{
|
|
name: "resource both array and not array",
|
|
tag: "resource.rs",
|
|
query: "{}",
|
|
expectedValues: []tempopb.TagValue{stringTagValue("a"), stringTagValue("b"), stringTagValue("c")},
|
|
tr: func() *Trace { tr, _ := mixedArrayTestTrace(); return tr }(),
|
|
dc: func() backend.DedicatedColumns { _, dc := mixedArrayTestTrace(); return dc }(),
|
|
},
|
|
{
|
|
name: "event both array and not array",
|
|
tag: "event.es",
|
|
query: "{}",
|
|
expectedValues: []tempopb.TagValue{stringTagValue("e"), stringTagValue("f"), stringTagValue("g")},
|
|
tr: func() *Trace { tr, _ := mixedArrayTestTrace(); return tr }(),
|
|
dc: func() backend.DedicatedColumns { _, dc := mixedArrayTestTrace(); return dc }(),
|
|
},
|
|
}
|
|
|
|
var (
|
|
ctx = t.Context()
|
|
opts = common.DefaultSearchOptions()
|
|
)
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(fmt.Sprintf("tag: %s, query: %s", tc.tag, tc.query), func(t *testing.T) {
|
|
req, err := traceql.ExtractFetchSpansRequest(tc.query)
|
|
require.NoError(t, err)
|
|
|
|
tag, err := traceql.ParseIdentifier(tc.tag)
|
|
require.NoError(t, err)
|
|
|
|
tagAtrr, err := traceql.ParseIdentifier(tc.tag)
|
|
require.NoError(t, err)
|
|
|
|
if tc.tr == nil {
|
|
tc.tr = fullyPopulatedTestTrace(common.ID{0})
|
|
}
|
|
if tc.dc == nil {
|
|
tc.dc = test.MakeDedicatedColumns()
|
|
}
|
|
var (
|
|
block = makeBackendBlockWithTracesWithDedicatedColumns(t, []*Trace{tc.tr}, tc.dc)
|
|
mc = collector.NewMetricsCollector()
|
|
distinctValues = collector.NewDistinctValue(1_000_000, 0, 0, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) })
|
|
autocompleteReq = traceql.FetchTagValuesRequest{
|
|
TagName: tag,
|
|
Conditions: append(req.Conditions,
|
|
traceql.Condition{
|
|
Attribute: tagAtrr,
|
|
Op: traceql.OpNone,
|
|
},
|
|
),
|
|
}
|
|
)
|
|
|
|
err = block.FetchTagValues(ctx, autocompleteReq, traceql.MakeCollectTagValueFunc(distinctValues.Collect), mc.Add, opts)
|
|
require.NoError(t, err)
|
|
// test that callback is recording bytes read
|
|
require.Greater(t, mc.TotalValue(), uint64(100))
|
|
|
|
expectedValues := tc.expectedValues
|
|
actualValues := distinctValues.Values()
|
|
sort.Slice(expectedValues, func(i, j int) bool { return tc.expectedValues[i].Value < tc.expectedValues[j].Value })
|
|
sort.Slice(actualValues, func(i, j int) bool { return actualValues[i].Value < actualValues[j].Value })
|
|
require.Equal(t, expectedValues, actualValues)
|
|
})
|
|
}
|
|
}
|
|
|
|
func stringTagValue(v string) tempopb.TagValue { return tempopb.TagValue{Type: "string", Value: v} }
|
|
func intTagValue(v int64) tempopb.TagValue {
|
|
return tempopb.TagValue{Type: "int", Value: fmt.Sprintf("%d", v)}
|
|
}
|
|
|
|
func BenchmarkFetchTagValues(b *testing.B) {
|
|
testCases := []struct {
|
|
tag string
|
|
query string
|
|
}{
|
|
{
|
|
tag: "span.http.url", // well known column
|
|
query: `{resource.namespace="tempo-ops"}`,
|
|
},
|
|
{
|
|
tag: "span.component", // normal column
|
|
query: `{resource.namespace="tempo-ops"}`,
|
|
},
|
|
{
|
|
tag: "span.http.url",
|
|
query: `{resource.namespace="tempo-ops" && span.http.status_code=200}`,
|
|
},
|
|
{
|
|
tag: "resource.namespace",
|
|
query: `{span.http.status_code=200}`,
|
|
},
|
|
// pathologic cases
|
|
/*
|
|
{
|
|
tag: "resource.k8s.node.name",
|
|
query: `{span.http.method="GET"}`,
|
|
},
|
|
{
|
|
tag: "span.sampler.type",
|
|
query: `{span.http.method="GET"}`,
|
|
},
|
|
{
|
|
tag: "span.sampler.type",
|
|
query: `{resource.k8s.node.name>"aaa"}`,
|
|
},
|
|
{
|
|
tag: "resource.k8s.node.name",
|
|
query: `{span.sampler.type>"aaa"}`,
|
|
},
|
|
*/
|
|
}
|
|
|
|
ctx := context.TODO()
|
|
block := blockForBenchmarks(b)
|
|
opts := common.DefaultSearchOptions()
|
|
|
|
for _, tc := range testCases {
|
|
b.Run(fmt.Sprintf("tag: %s, query: %s", tc.tag, tc.query), func(b *testing.B) {
|
|
distinctValues := collector.NewDistinctValue[tempopb.TagValue](1_000_000, 0, 0, func(v tempopb.TagValue) int { return len(v.Type) + len(v.Value) })
|
|
req, err := traceql.ExtractFetchSpansRequest(tc.query)
|
|
require.NoError(b, err)
|
|
|
|
tag, err := traceql.ParseIdentifier(tc.tag)
|
|
require.NoError(b, err)
|
|
|
|
// FetchTagValues expects the tag to be in the conditions with OpNone otherwise it will
|
|
// fall back to the old tag search
|
|
req.Conditions = append(req.Conditions, traceql.Condition{
|
|
Attribute: tag,
|
|
})
|
|
|
|
autocompleteReq := traceql.FetchTagValuesRequest{
|
|
Conditions: req.Conditions,
|
|
TagName: tag,
|
|
}
|
|
mc := collector.NewMetricsCollector()
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
err := block.FetchTagValues(ctx, autocompleteReq, traceql.MakeCollectTagValueFunc(distinctValues.Collect), mc.Add, opts)
|
|
require.NoError(b, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkFetchTags(b *testing.B) {
|
|
testCases := []struct {
|
|
query string
|
|
}{
|
|
{
|
|
query: `{resource.namespace="tempo-ops"}`, // well known/dedicated column
|
|
},
|
|
{
|
|
query: `{resource.k8s.node.name>"h"}`, // generic attribute
|
|
},
|
|
{
|
|
query: `{span.http.status_code=200}`, // well known/dedicated column
|
|
},
|
|
{
|
|
query: `{nestedSetParent=-1}`, // generic attribute
|
|
},
|
|
{
|
|
query: `{rootName="Memcache.Put"}`, // trace level
|
|
},
|
|
// pathological cases
|
|
/*
|
|
{
|
|
query: `{resource.k8s.node.name>"aaa"}`, // generic attribute
|
|
},
|
|
{
|
|
query: `{span.http.method="GET"}`, // well known/dedicated column
|
|
},
|
|
{
|
|
query: `{span.sampler.type>"aaa"}`, // generic attribute
|
|
},
|
|
*/
|
|
}
|
|
|
|
ctx := context.TODO()
|
|
block := blockForBenchmarks(b)
|
|
opts := common.DefaultSearchOptions()
|
|
|
|
for _, tc := range testCases {
|
|
for _, scope := range []traceql.AttributeScope{traceql.AttributeScopeSpan, traceql.AttributeScopeResource, traceql.AttributeScopeNone} {
|
|
b.Run(fmt.Sprintf("query: %s %s", tc.query, scope), func(b *testing.B) {
|
|
distinctStrings := collector.NewScopedDistinctString(1_000_000, 0, 0)
|
|
req, err := traceql.ExtractFetchSpansRequest(tc.query)
|
|
require.NoError(b, err)
|
|
|
|
autocompleteReq := traceql.FetchTagsRequest{
|
|
Conditions: req.Conditions,
|
|
Scope: scope,
|
|
}
|
|
mc := collector.NewMetricsCollector()
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
err := block.FetchTagNames(ctx, autocompleteReq, func(t string, scope traceql.AttributeScope) bool {
|
|
distinctStrings.Collect(scope.String(), t)
|
|
return false
|
|
}, mc.Add, opts)
|
|
require.NoError(b, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|