Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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 @@ -14,6 +14,7 @@
* [FEATURE] TraecQL support for event attributes [#3708](https://github.com/grafana/tempo/pull/3748) (@ie-pham)
* [FEATURE] TraceQL support for event:timeSinceStart [#3908](https://github.com/grafana/tempo/pull/3908) (@ie-pham)
* [FEATURE] Autocomplete support for events and links [#3846](https://github.com/grafana/tempo/pull/3846) (@ie-pham)
* [FEATURE] TraceQL support for instrumentation scope [#3967](https://github.com/grafana/tempo/pull/3967) (@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 new api `/api/metrics/query` for instant metrics queries [#3859](https://github.com/grafana/tempo/pull/3859) (@mdisibio)
Expand Down
9 changes: 8 additions & 1 deletion docs/sources/tempo/traceql/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ The following table shows the current available scoped intrinsic fields:
| `event:timeSinceStart` | duration | time of event in relation to the span start time | `{ event:timeSinceStart > 2ms}` |
| `link:spanID` | string | link span id using hex string | `{ link:spanID = "0000000000000001" }` |
| `link:traceID` | string | link trace id using hex string | `{ link:traceID = "1234567890abcde" }` |
| `scope:name` | string | instrumentation scope name | `{ scope:name = "grpc" }` |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the thing at the left of : was a scope , i.e. link is a possible value for scope.
So is very confusing for me that this is called scope , shouldn't it be called instrumentation ?
instrumentation:name instrumentation:version more clearly define what this points at IMHO

Copy link
Copy Markdown
Collaborator

@joe-elliott joe-elliott Aug 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a good call out. we have discussed this with our internal team and they believe that otel does not see this as an instrumentation only field. it just happens to be used that way and they intend for it to be used as a more generic "scope" long term.

agree that scope scope is strange. let me summon @jpkrohling. he may have more insight.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are end-users exposed to "scope scope"? As an OTel user, I immediately thought of InstrumentationScope when I saw "scope:name" here. Unless there's an explicit source of confusion, I'd keep "scope:name".

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are end-users exposed to "scope scope"?

Not directly although there may be some awkward wording in the docs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally scope:name is a generic term that says nothing to me.
I can imagine for people better versed in opentelemetry parlance it is very obvious.
I am very sure that around here I will need to tell people that "scope:name means the name of the instrumentation/area that created the span". or such explanation as I do not think it is clear to more casual users of observability tools.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it help to have these differences called out in the doc to avoid confusion? Or would that just create more confusion?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs will definitely need to be there.
When someone reads a query or an autocomplete with span:duration , that person needs to know almost nothing about tracing to understand what that would mean, without resorting to reading documentation.

I think when someone reads scope:name that person will definitely need to go to documentation to know what would that mean.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than the number of keyboard touches, I have no strong preferences for scope vs. instrumentation. They are both technically correct. If instrumentation is more intuitive to the audience, that certainly wins over scope.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jcarres-mdsol thank you for pointing this out. I've changed the syntax to instrumentation:name and instrumentation:version 🚀

| `scope:version` | string | instrumentation scope version | `{ scope:version = "1.0.0.0" }` |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was one .0 accidentally added?

Suggested change
| `scope:version` | string | instrumentation scope version | `{ scope:version = "1.0.0.0" }` |
| `scope:version` | string | instrumentation scope version | `{ scope:version = "1.0.0" }` |

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch!


`trace:duration`, `trace:rootName`, and `trace:rootService` are trace-level intrinsics and are the same for all spans in the same trace.
Additionally, these intrinsics are significantly more performant because they have to inspect much less data then a span-level intrinsic.
Expand All @@ -111,7 +113,7 @@ This example searches all Kubernetes clusters called `service-name` that have a

### Attribute fields

TraceQL has four different attribute scopes: span attributes, resource attributes, event attributes, and link attributes. By expanding a span in the Grafana UI, you can see both its span attributes (1 in the screenshot) and resource attributes (2 in the screenshot).
TraceQL has five different attribute scopes: span attributes, resource attributes, event attributes, link attributes, instrumentation scope attributes. By expanding a span in the Grafana UI, you can see both its span attributes (1 in the screenshot) and resource attributes (2 in the screenshot).
Comment thread
ie-pham marked this conversation as resolved.

<p align="center"><img src="assets/span-resource-attributes.png" alt="Example of span and resource attributes." /></p>

Expand Down Expand Up @@ -148,6 +150,11 @@ You can search for an attribute in your link:
{ link.opentracing.ref_type = "child_of" }
```

Find instrumentation scope programming language:
```
{ scope.language = "java" }
```

### Unscoped attribute fields

Attributes can be unscoped if you are unsure if the requested attribute exists on the span or resource.
Expand Down
2 changes: 1 addition & 1 deletion integration/e2e/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ func callSearchTagsV2AndAssert(t *testing.T, svc *e2e.HTTPService, scope, query
if scope == "none" || scope == "" || scope == "intrinsic" {
expected.Scopes = append(expected.Scopes, &tempopb.SearchTagsV2Scope{
Name: "intrinsic",
Tags: []string{"duration", "event:name", "event:timeSinceStart", "kind", "name", "rootName", "rootServiceName", "span:duration", "span:kind", "span:name", "span:status", "span:statusMessage", "status", "statusMessage", "trace:duration", "trace:rootName", "trace:rootService", "traceDuration"},
Tags: []string{"duration", "event:name", "event:timeSinceStart", "kind", "name", "rootName", "rootServiceName", "scope:name", "scope:version", "span:duration", "span:kind", "span:name", "span:status", "span:statusMessage", "status", "statusMessage", "trace:duration", "trace:rootName", "trace:rootService", "traceDuration"},
})
}
sort.Slice(expected.Scopes, func(i, j int) bool { return expected.Scopes[i].Name < expected.Scopes[j].Name })
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 @@ -134,7 +134,7 @@ func testSearch(t *testing.T, tenant string, tenantSize int) {

tagsV2Resp, err := apiClient.SearchTagsV2()
require.NoError(t, err)
require.Equal(t, 4, len(tagsV2Resp.GetScopes())) // resource, span, event, intrinsics
require.Equal(t, 4, len(tagsV2Resp.GetScopes())) // resource, span, event, link, scope intrinsics
for _, s := range tagsV2Resp.Scopes {
require.NotEmpty(t, s.Tags)
}
Expand Down
24 changes: 24 additions & 0 deletions modules/ingester/instance_search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,24 @@ func testSearchTagsAndValuesV2(
sort.Strings(expectedLinkTagValues)
assert.Contains(t, tagsResp.TagNames, tagName)
assert.Equal(t, expectedLinkTagValues, tagValues)

// scope scope attr

tagValuesResp, err = i.SearchTagValuesV2(ctx, &tempopb.SearchTagValuesRequest{
TagName: fmt.Sprintf("scope.%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(expectedTagValues)
assert.Contains(t, tagsResp.TagNames, tagName)
assert.Equal(t, expectedTagValues, tagValues)
}

// TestInstanceSearchTagsSpecialCases tess that SearchTags errors on an unknown scope and
Expand All @@ -425,6 +443,7 @@ func TestInstanceSearchTagsSpecialCases(t *testing.T) {
t,
[]string{
"duration", "event:name", "event:timeSinceStart", "kind", "name", "rootName", "rootServiceName",
"scope:name", "scope:version",
"span:duration", "span:kind", "span:name", "span:status", "span:statusMessage", "status", "statusMessage",
"trace:duration", "trace:rootName", "trace:rootService", "traceDuration",
},
Expand Down Expand Up @@ -562,6 +581,11 @@ func writeTracesForSearch(t *testing.T, i *instance, spanName, tagKey, tagValue
// add the time
for _, batch := range testTrace.ResourceSpans {
for _, ils := range batch.ScopeSpans {
ils.Scope = &v1.InstrumentationScope{
Name: "scope-name",
Version: "scope-version",
Attributes: []*v1.KeyValue{kv},
}
for _, span := range ils.Spans {
span.Name = spanName
span.StartTimeUnixNano = uint64(now.UnixNano())
Expand Down
2 changes: 2 additions & 0 deletions pkg/search/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ func GetVirtualIntrinsicValues() []string {
traceql.ScopedIntrinsicTraceDuration.String(),
traceql.IntrinsicEventName.String(),
traceql.IntrinsicEventTimeSinceStart.String(),
traceql.IntrinsicScopeName.String(),
traceql.IntrinsicScopeVersion.String(),
/* these are technically intrinsics that can be requested, but they are not generally of interest to a user
typing a query. for simplicity and clarity we are leaving them out of autocomplete
IntrinsicNestedSetLeft
Expand Down
17 changes: 17 additions & 0 deletions pkg/traceql/enum_attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const (
AttributeScopeSpan
AttributeScopeEvent
AttributeScopeLink
AttributeScopeInstrumentation
AttributeScopeUnknown

none = "none"
Expand All @@ -32,6 +33,8 @@ func (s AttributeScope) String() string {
return "event"
case AttributeScopeLink:
return "link"
case AttributeScopeInstrumentation:
return "scope"
}

return fmt.Sprintf("att(%d).", s)
Expand All @@ -47,6 +50,8 @@ func AttributeScopeFromString(s string) AttributeScope {
return AttributeScopeEvent
case "link":
return AttributeScopeLink
case "scope":
return AttributeScopeInstrumentation
case "":
fallthrough
case none:
Expand Down Expand Up @@ -76,6 +81,8 @@ const (
IntrinsicEventTimeSinceStart
IntrinsicLinkSpanID
IntrinsicLinkTraceID
IntrinsicScopeName
IntrinsicScopeVersion

// not yet implemented in traceql but will be
IntrinsicParent
Expand Down Expand Up @@ -128,6 +135,8 @@ var (
IntrinsicLinkSpanIDAttribute = NewIntrinsic(IntrinsicLinkSpanID)
IntrinsicEventNameAttribute = NewIntrinsic(IntrinsicEventName)
IntrinsicEventTimeSinceStartAttribute = NewIntrinsic(IntrinsicEventTimeSinceStart)
IntrinsicScopeNameAttribute = NewIntrinsic(IntrinsicScopeName)
IntrinsicScopeVersionAttribute = NewIntrinsic(IntrinsicScopeVersion)
)

func (i Intrinsic) String() string {
Expand Down Expand Up @@ -184,6 +193,10 @@ func (i Intrinsic) String() string {
return "trace:duration"
case IntrinsicSpanID:
return "span:id"
case IntrinsicScopeName:
return "scope:name"
case IntrinsicScopeVersion:
return "scope:version"
// below is unimplemented
case IntrinsicSpanStartTime:
return "spanStartTime"
Expand Down Expand Up @@ -251,6 +264,10 @@ func intrinsicFromString(s string) Intrinsic {
return IntrinsicTraceRootService
case "trace:duration":
return IntrinsicTraceDuration
case "scope:name":
return IntrinsicScopeName
case "scope:version":
return IntrinsicScopeVersion
// unimplemented
case "spanStartTime":
return IntrinsicSpanStartTime
Expand Down
27 changes: 16 additions & 11 deletions pkg/traceql/expr.y
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ import (
NIL TRUE FALSE STATUS_ERROR STATUS_OK STATUS_UNSET
KIND_UNSPECIFIED KIND_INTERNAL KIND_SERVER KIND_CLIENT KIND_PRODUCER KIND_CONSUMER
IDURATION CHILDCOUNT NAME STATUS STATUS_MESSAGE PARENT KIND ROOTNAME ROOTSERVICENAME
TIMESINCESTART
ROOTSERVICE TRACEDURATION NESTEDSETLEFT NESTEDSETRIGHT NESTEDSETPARENT ID TRACE_ID SPAN_ID
PARENT_DOT RESOURCE_DOT SPAN_DOT TRACE_COLON SPAN_COLON EVENT_COLON EVENT_DOT LINK_COLON LINK_DOT
ROOTSERVICE TRACEDURATION NESTEDSETLEFT NESTEDSETRIGHT NESTEDSETPARENT ID
TRACE_ID SPAN_ID TIMESINCESTART VERSION
PARENT_DOT RESOURCE_DOT SPAN_DOT TRACE_COLON SPAN_COLON
EVENT_COLON EVENT_DOT LINK_COLON LINK_DOT SCOPE_COLON SCOPE_DOT
COUNT AVG MAX MIN SUM
BY COALESCE SELECT
END_ATTRIBUTE
Expand Down Expand Up @@ -411,15 +412,19 @@ scopedIntrinsicField:
// link:
| LINK_COLON TRACE_ID { $$ = NewIntrinsic(IntrinsicLinkTraceID) }
| LINK_COLON SPAN_ID { $$ = NewIntrinsic(IntrinsicLinkSpanID) }
// scope:
| SCOPE_COLON NAME { $$ = NewIntrinsic(IntrinsicScopeName) }
| SCOPE_COLON VERSION { $$ = NewIntrinsic(IntrinsicScopeVersion) }
;

attributeField:
DOT IDENTIFIER END_ATTRIBUTE { $$ = NewAttribute($2) }
| RESOURCE_DOT IDENTIFIER END_ATTRIBUTE { $$ = NewScopedAttribute(AttributeScopeResource, false, $2) }
| SPAN_DOT IDENTIFIER END_ATTRIBUTE { $$ = NewScopedAttribute(AttributeScopeSpan, false, $2) }
| PARENT_DOT IDENTIFIER END_ATTRIBUTE { $$ = NewScopedAttribute(AttributeScopeNone, true, $2) }
| PARENT_DOT RESOURCE_DOT IDENTIFIER END_ATTRIBUTE { $$ = NewScopedAttribute(AttributeScopeResource, true, $3) }
| PARENT_DOT SPAN_DOT IDENTIFIER END_ATTRIBUTE { $$ = NewScopedAttribute(AttributeScopeSpan, true, $3) }
| EVENT_DOT IDENTIFIER END_ATTRIBUTE { $$ = NewScopedAttribute(AttributeScopeEvent, false, $2) }
| LINK_DOT IDENTIFIER END_ATTRIBUTE { $$ = NewScopedAttribute(AttributeScopeLink, false, $2) }
DOT IDENTIFIER END_ATTRIBUTE { $$ = NewAttribute($2) }
| RESOURCE_DOT IDENTIFIER END_ATTRIBUTE { $$ = NewScopedAttribute(AttributeScopeResource, false, $2) }
| SPAN_DOT IDENTIFIER END_ATTRIBUTE { $$ = NewScopedAttribute(AttributeScopeSpan, false, $2) }
| PARENT_DOT IDENTIFIER END_ATTRIBUTE { $$ = NewScopedAttribute(AttributeScopeNone, true, $2) }
| PARENT_DOT RESOURCE_DOT IDENTIFIER END_ATTRIBUTE { $$ = NewScopedAttribute(AttributeScopeResource, true, $3) }
| PARENT_DOT SPAN_DOT IDENTIFIER END_ATTRIBUTE { $$ = NewScopedAttribute(AttributeScopeSpan, true, $3) }
| EVENT_DOT IDENTIFIER END_ATTRIBUTE { $$ = NewScopedAttribute(AttributeScopeEvent, false, $2) }
| LINK_DOT IDENTIFIER END_ATTRIBUTE { $$ = NewScopedAttribute(AttributeScopeLink, false, $2) }
| SCOPE_DOT IDENTIFIER END_ATTRIBUTE { $$ = NewScopedAttribute(AttributeScopeInstrumentation, false, $2) }
;
Loading