Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* [FEATURE] TraceQL support for link attribute querying [#3814](https://github.com/grafana/tempo/pull/3814) (@ie-pham)
* [FEATURE] TraceQL support for event scope and event:name intrinsic [#3708](https://github.com/grafana/tempo/pull/3708) (@stoewer)
* [FEATURE] TraecQL support for event attributes [#3708](https://github.com/grafana/tempo/pull/3748) (@ie-pham)
* [FEATURE] Autocomplete support for events and links [#3846](https://github.com/grafana/tempo/pull/3846) (@ie-pham)
* [FEATURE] Flush and query RF1 blocks for TraceQL metric queries [#3628](https://github.com/grafana/tempo/pull/3628) [#3691](https://github.com/grafana/tempo/pull/3691) [#3723](https://github.com/grafana/tempo/pull/3723) (@mapno)
* [FEATURE] Add new compare() metrics function [#3695](https://github.com/grafana/tempo/pull/3695) (@mdisibio)
* [FEATURE] Add a `q` parameter to `/api/v2/serach/tags` for tag name filtering [#3822](https://github.com/grafana/tempo/pull/3822) (@joe-elliott)
Expand Down
2 changes: 1 addition & 1 deletion integration/e2e/multi_tenant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func TestMultiTenantSearch(t *testing.T) {

tagsV2Resp, err := apiClient.SearchTagsV2()
require.NoError(t, err)
require.Equal(t, 3, len(tagsV2Resp.GetScopes()))
require.Equal(t, 4, len(tagsV2Resp.GetScopes())) // resource, span, event, intrinsics
for _, s := range tagsV2Resp.Scopes {
require.NotEmpty(t, s.Tags)
}
Expand Down
7 changes: 7 additions & 0 deletions modules/ingester/instance_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,13 @@ func (i *instance) SearchTagValuesV2(ctx context.Context, req *tempopb.SearchTag
if err != nil {
return nil, err
}
if tag == traceql.IntrinsicLinkTraceIDAttribute ||
tag == traceql.IntrinsicLinkSpanIDAttribute ||
tag == traceql.IntrinsicSpanIDAttribute ||
tag == traceql.IntrinsicTraceIDAttribute {
// do not return tag values for IDs
return &tempopb.SearchTagValuesV2Response{}, nil
}

query := traceql.ExtractMatchers(req.Query)

Expand Down
90 changes: 76 additions & 14 deletions modules/ingester/instance_search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/grafana/tempo/pkg/model/trace"
"github.com/grafana/tempo/pkg/tempopb"
v1 "github.com/grafana/tempo/pkg/tempopb/common/v1"
trace_v1 "github.com/grafana/tempo/pkg/tempopb/trace/v1"
"github.com/grafana/tempo/pkg/util"
"github.com/grafana/tempo/pkg/util/test"
"github.com/grafana/tempo/tempodb/backend"
Expand All @@ -39,7 +40,7 @@ func TestInstanceSearch(t *testing.T) {

tagKey := foo
tagValue := bar
ids, _ := writeTracesForSearch(t, i, "", tagKey, tagValue, false)
ids, _, _, _ := writeTracesForSearch(t, i, "", tagKey, tagValue, false, false)

req := &tempopb.SearchRequest{
Tags: map[string]string{},
Expand Down Expand Up @@ -174,7 +175,7 @@ func TestInstanceSearchWithStartAndEnd(t *testing.T) {

tagKey := foo
tagValue := bar
ids, _ := writeTracesForSearch(t, i, "", tagKey, tagValue, false)
ids, _, _, _ := writeTracesForSearch(t, i, "", tagKey, tagValue, false, false)

search := func(req *tempopb.SearchRequest, start, end uint32) *tempopb.SearchResponse {
req.Start = start
Expand Down Expand Up @@ -251,7 +252,7 @@ func TestInstanceSearchTags(t *testing.T) {
tagKey := "foo"
tagValue := bar

_, expectedTagValues := writeTracesForSearch(t, i, "", tagKey, tagValue, true)
_, expectedTagValues, _, _ := writeTracesForSearch(t, i, "", tagKey, tagValue, true, false)

userCtx := user.InjectOrgID(context.Background(), "fake")

Expand Down Expand Up @@ -290,6 +291,15 @@ func testSearchTagsAndValues(t *testing.T, ctx context.Context, i *instance, tag
tagName,
) // tags are added to h the spans and not resources so they should not be returned

// added the same span tag to both event and link
sr, err = i.SearchTags(ctx, "event")
require.NoError(t, err)
assert.Contains(t, sr.TagNames, tagName)

sr, err = i.SearchTags(ctx, "link")
require.NoError(t, err)
assert.Contains(t, sr.TagNames, tagName)

srv, err := i.SearchTagValues(ctx, tagName)
require.NoError(t, err)

Expand All @@ -311,28 +321,28 @@ func TestInstanceSearchTagAndValuesV2(t *testing.T) {
queryThatDoesNotMatch = `{ resource.service.name = "aaaaa" }`
)

_, expectedTagValues := writeTracesForSearch(t, i, spanName, tagKey, tagValue, true)
_, _ = writeTracesForSearch(t, i, "other-"+spanName, tagKey, otherTagValue, true)
_, expectedTagValues, expectedEventTagValues, expectedLinkTagValues := writeTracesForSearch(t, i, spanName, tagKey, tagValue, true, true)
_, _, _, _ = writeTracesForSearch(t, i, "other-"+spanName, tagKey, otherTagValue, true, false)

userCtx := user.InjectOrgID(context.Background(), "fake")

// Test after appending to WAL
testSearchTagsAndValuesV2(t, userCtx, i, tagKey, queryThatMatches, expectedTagValues) // Matches the expected tag values
testSearchTagsAndValuesV2(t, userCtx, i, tagKey, queryThatDoesNotMatch, []string{}) // Does not match the expected tag values
testSearchTagsAndValuesV2(t, userCtx, i, tagKey, queryThatMatches, expectedTagValues, expectedEventTagValues, expectedLinkTagValues) // Matches the expected tag values
testSearchTagsAndValuesV2(t, userCtx, i, tagKey, queryThatDoesNotMatch, []string{}, []string{}, []string{}) // Does not match the expected tag values

// Test after cutting new headblock
blockID, err := i.CutBlockIfReady(0, 0, true)
require.NoError(t, err)
assert.NotEqual(t, blockID, uuid.Nil)

testSearchTagsAndValuesV2(t, userCtx, i, tagKey, queryThatMatches, expectedTagValues)
testSearchTagsAndValuesV2(t, userCtx, i, tagKey, queryThatMatches, expectedTagValues, expectedEventTagValues, expectedLinkTagValues)

// Test after completing a block
err = i.CompleteBlock(blockID)
require.NoError(t, err)
require.NoError(t, i.ClearCompletingBlock(blockID)) // Clear the completing block

testSearchTagsAndValuesV2(t, userCtx, i, tagKey, queryThatMatches, expectedTagValues)
testSearchTagsAndValuesV2(t, userCtx, i, tagKey, queryThatMatches, expectedTagValues, expectedEventTagValues, expectedLinkTagValues)
}

// nolint:revive,unparam
Expand All @@ -342,6 +352,8 @@ func testSearchTagsAndValuesV2(
i *instance,
tagName, query string,
expectedTagValues []string,
expectedEventTagValues []string,
expectedLinkTagValues []string,
) {
tagsResp, err := i.SearchTags(ctx, "none")
require.NoError(t, err)
Expand All @@ -361,6 +373,40 @@ func testSearchTagsAndValuesV2(
sort.Strings(expectedTagValues)
assert.Contains(t, tagsResp.TagNames, tagName)
assert.Equal(t, expectedTagValues, tagValues)

// Test with event and link

tagValuesResp, err = i.SearchTagValuesV2(ctx, &tempopb.SearchTagValuesRequest{
TagName: fmt.Sprintf("event.%s", tagName),
Query: query,
})
require.NoError(t, err)

tagValues = make([]string, 0, len(tagValuesResp.TagValues))
for _, v := range tagValuesResp.TagValues {
tagValues = append(tagValues, v.Value)
}

sort.Strings(tagValues)
sort.Strings(expectedEventTagValues)
assert.Contains(t, tagsResp.TagNames, tagName)
assert.Equal(t, expectedEventTagValues, tagValues)

tagValuesResp, err = i.SearchTagValuesV2(ctx, &tempopb.SearchTagValuesRequest{
TagName: fmt.Sprintf("link.%s", tagName),
Query: query,
})
require.NoError(t, err)

tagValues = make([]string, 0, len(tagValuesResp.TagValues))
for _, v := range tagValuesResp.TagValues {
tagValues = append(tagValues, v.Value)
}

sort.Strings(tagValues)
sort.Strings(expectedLinkTagValues)
assert.Contains(t, tagsResp.TagNames, tagName)
assert.Equal(t, expectedLinkTagValues, tagValues)
}

// TestInstanceSearchTagsSpecialCases tess that SearchTags errors on an unknown scope and
Expand Down Expand Up @@ -409,7 +455,7 @@ func TestInstanceSearchMaxBytesPerTagValuesQueryReturnsPartial(t *testing.T) {
tagKey := foo
tagValue := bar

_, _ = writeTracesForSearch(t, i, "", tagKey, tagValue, true)
_, _, _, _ = writeTracesForSearch(t, i, "", tagKey, tagValue, true, false)

userCtx := user.InjectOrgID(context.Background(), "fake")
resp, err := i.SearchTagValues(userCtx, tagKey)
Expand Down Expand Up @@ -440,15 +486,15 @@ func TestInstanceSearchMaxBlocksPerTagValuesQueryReturnsPartial(t *testing.T) {
tagKey := foo
tagValue := bar

_, _ = writeTracesForSearch(t, i, "", tagKey, tagValue, true)
_, _, _, _ = writeTracesForSearch(t, i, "", tagKey, tagValue, true, false)

// Cut the headblock
blockID, err := i.CutBlockIfReady(0, 0, true)
require.NoError(t, err)
assert.NotEqual(t, blockID, uuid.Nil)

// Write more traces
_, _ = writeTracesForSearch(t, i, "", tagKey, "another-"+bar, true)
_, _, _, _ = writeTracesForSearch(t, i, "", tagKey, "another-"+bar, true, false)

userCtx := user.InjectOrgID(context.Background(), "fake")

Expand Down Expand Up @@ -479,14 +525,16 @@ func TestInstanceSearchMaxBlocksPerTagValuesQueryReturnsPartial(t *testing.T) {
// ids expected to be returned from a tag search and strings expected to
// be returned from a tag value search
// nolint:revive,unparam
func writeTracesForSearch(t *testing.T, i *instance, spanName, tagKey, tagValue string, postFixValue bool) ([][]byte, []string) {
func writeTracesForSearch(t *testing.T, i *instance, spanName, tagKey, tagValue string, postFixValue bool, includeEventLink bool) ([][]byte, []string, []string, []string) {
// This matches the encoding for live traces, since
// we are pushing to the instance directly it must match.
dec := model.MustNewSegmentDecoder(model.CurrentEncoding)

numTraces := 100
ids := make([][]byte, 0, numTraces)
expectedTagValues := make([]string, 0, numTraces)
expectedEventTagValues := make([]string, 0, numTraces)
expectedLinkTagValues := make([]string, 0, numTraces)

now := time.Now()
for j := 0; j < numTraces; j++ {
Expand All @@ -499,7 +547,15 @@ func writeTracesForSearch(t *testing.T, i *instance, spanName, tagKey, tagValue
tv = tv + strconv.Itoa(j)
}
kv := &v1.KeyValue{Key: tagKey, Value: &v1.AnyValue{Value: &v1.AnyValue_StringValue{StringValue: tv}}}
eTv := "event-" + tv
lTv := "link-" + tv
eventKv := &v1.KeyValue{Key: tagKey, Value: &v1.AnyValue{Value: &v1.AnyValue_StringValue{StringValue: eTv}}}
linkKv := &v1.KeyValue{Key: tagKey, Value: &v1.AnyValue{Value: &v1.AnyValue_StringValue{StringValue: lTv}}}
expectedTagValues = append(expectedTagValues, tv)
if includeEventLink {
expectedEventTagValues = append(expectedEventTagValues, eTv)
expectedLinkTagValues = append(expectedLinkTagValues, lTv)
}
ids = append(ids, id)

testTrace := test.MakeTrace(10, id)
Expand All @@ -514,6 +570,12 @@ func writeTracesForSearch(t *testing.T, i *instance, spanName, tagKey, tagValue
}
}
testTrace.Batches[0].ScopeSpans[0].Spans[0].Attributes = append(testTrace.Batches[0].ScopeSpans[0].Spans[0].Attributes, kv)
// add link and event
event := &trace_v1.Span_Event{Name: "event-name", Attributes: []*v1.KeyValue{eventKv}}
link := &trace_v1.Span_Link{TraceId: id, SpanId: id, Attributes: []*v1.KeyValue{linkKv}}
testTrace.Batches[0].ScopeSpans[0].Spans[0].Events = append(testTrace.Batches[0].ScopeSpans[0].Spans[0].Events, event)
testTrace.Batches[0].ScopeSpans[0].Spans[0].Links = append(testTrace.Batches[0].ScopeSpans[0].Spans[0].Links, link)

trace.SortTrace(testTrace)

// // Print trace as json string
Expand All @@ -534,7 +596,7 @@ func writeTracesForSearch(t *testing.T, i *instance, spanName, tagKey, tagValue
err := i.CutCompleteTraces(0, true)
require.NoError(t, err)

return ids, expectedTagValues
return ids, expectedTagValues, expectedEventTagValues, expectedLinkTagValues
}

func TestInstanceSearchNoData(t *testing.T) {
Expand Down
5 changes: 1 addition & 4 deletions pkg/traceql/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,10 +303,7 @@ func (e *Engine) asTraceSearchMetadata(spanset *Spanset) *tempopb.TraceSearchMet
attribute.Intrinsic == IntrinsicTraceRootService ||
attribute.Intrinsic == IntrinsicTraceRootSpan ||
attribute.Intrinsic == IntrinsicTraceID ||
attribute.Intrinsic == IntrinsicSpanID ||
attribute.Intrinsic == IntrinsicEventName ||
attribute.Intrinsic == IntrinsicLinkTraceID ||
attribute.Intrinsic == IntrinsicLinkSpanID {
attribute.Intrinsic == IntrinsicSpanID {

continue
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/traceql/enum_attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ var (
IntrinsicNestedSetLeftAttribute = NewIntrinsic(IntrinsicNestedSetLeft)
IntrinsicNestedSetRightAttribute = NewIntrinsic(IntrinsicNestedSetRight)
IntrinsicNestedSetParentAttribute = NewIntrinsic(IntrinsicNestedSetParent)
IntrinsicLinkTraceIDAttribute = NewIntrinsic(IntrinsicLinkTraceID)
IntrinsicLinkSpanIDAttribute = NewIntrinsic(IntrinsicLinkSpanID)
)

func (i Intrinsic) String() string {
Expand Down
4 changes: 4 additions & 0 deletions pkg/traceql/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ func ParseIdentifier(s string) (Attribute, error) {
return NewScopedAttribute(AttributeScopeResource, false, strings.TrimPrefix(s, "resource.")), nil
case strings.HasPrefix(s, "span."):
return NewScopedAttribute(AttributeScopeSpan, false, strings.TrimPrefix(s, "span.")), nil
case strings.HasPrefix(s, "event."):
return NewScopedAttribute(AttributeScopeEvent, false, strings.TrimPrefix(s, "event.")), nil
case strings.HasPrefix(s, "link."):
return NewScopedAttribute(AttributeScopeLink, false, strings.TrimPrefix(s, "link.")), nil
default:
return Attribute{}, fmt.Errorf("tag name is not valid intrinsic or scoped attribute: %s", s)
}
Expand Down
Loading