Skip to content
Open
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
6 changes: 3 additions & 3 deletions app/vtselect/traces/tempo/tempo.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,11 +387,11 @@ func searchTags(ctx context.Context, cp *tracecommon.CommonParams, traceQLStr st
pipeLimit := limit
switch scope {
case "instrumentation":
scopes = fmt.Sprintf(`| filter name:"%s:"*`, otelpb.InstrumentationScopeAttrPrefix)
scopes = fmt.Sprintf(`| filter name:"%s"*`, otelpb.InstrumentationScopeAttrPrefix)
case "resource":
scopes = fmt.Sprintf(`| filter name:"%s:"*`, otelpb.ResourceAttrPrefix)
scopes = fmt.Sprintf(`| filter name:"%s"*`, otelpb.ResourceAttrPrefix)
case "span":
scopes = fmt.Sprintf(`| filter name:"%s:"*`, otelpb.SpanAttrPrefixField)
scopes = fmt.Sprintf(`| filter name:"%s"*`, otelpb.SpanAttrPrefixField)
case "event":
//scopes = fmt.Sprintf(`| filter name:"%s:"*`, otelpb.EventPrefix+otelpb.EventAttrPrefix)
return nil, errors.New("scope: event is not supported yet")
Expand Down
35 changes: 35 additions & 0 deletions apptest/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,41 @@ func NewTempoAPISearchResponse(t *testing.T, s string) *TempoAPISearchResponse {
return res
}

// TempoAPISearchTagsResponse is an in-memory representation of the
// /select/tempo/api/v2/search/tags response.
type TempoAPISearchTagsResponse struct {
Scopes []TempoTagScope `json:"scopes"`
}

// TempoTagScope is the structure of an item in the `scopes` field of the
// /select/tempo/api/v2/search/tags response.
type TempoTagScope struct {
Name string `json:"name"`
Tags []string `json:"tags"`
}

// Tags returns the tag names of the given scope.
func (r *TempoAPISearchTagsResponse) Tags(scope string) []string {
for _, s := range r.Scopes {
if s.Name == scope {
return s.Tags
}
}
return nil
}

// NewTempoAPISearchTagsResponse is a test helper function that creates a new
// instance of TempoAPISearchTagsResponse by unmarshalling a json string.
func NewTempoAPISearchTagsResponse(t *testing.T, s string) *TempoAPISearchTagsResponse {
t.Helper()

res := &TempoAPISearchTagsResponse{}
if err := json.Unmarshal([]byte(s), res); err != nil {
t.Fatalf("could not unmarshal query response data=\n%s\n: %v", s, err)
}
return res
}

// NewJaegerAPITraceResponse is a test helper function that creates a new
// instance of JaegerAPITraceResponse by unmarshalling a json string.
func NewJaegerAPITraceResponse(t *testing.T, s string) *JaegerAPITraceResponse {
Expand Down
87 changes: 87 additions & 0 deletions apptest/tests/tempo_search_tags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package tests

import (
"testing"
"time"

at "github.com/VictoriaMetrics/VictoriaTraces/apptest"
otelpb "github.com/VictoriaMetrics/VictoriaTraces/lib/protoparser/opentelemetry/pb"
)

// TestSingleTempoSearchTagsByScope verifies that /select/tempo/api/v2/search/tags
// returns the attribute names of the requested scope.
func TestSingleTempoSearchTagsByScope(t *testing.T) {
tc := at.NewTestCase(t)
defer tc.Stop()

sut := tc.MustStartVtsingle("vtsingle", []string{
"-storageDataPath=" + tc.Dir() + "/vtsingle",
"-retentionPeriod=100y",
})

serviceName := "testSearchTagsService"
clusterName := "testCluster"
libLang := "go"
httpMethod := "GET"
spanTime := time.Now().Add(-time.Hour)
req := &otelpb.ExportTraceServiceRequest{
ResourceSpans: []*otelpb.ResourceSpans{
{
Resource: otelpb.Resource{
Attributes: []*otelpb.KeyValue{
{Key: "service.name", Value: &otelpb.AnyValue{StringValue: &serviceName}},
{Key: "k8s.cluster.name", Value: &otelpb.AnyValue{StringValue: &clusterName}},
},
},
ScopeSpans: []*otelpb.ScopeSpans{
{
Scope: otelpb.InstrumentationScope{
Name: "testScope",
Attributes: []*otelpb.KeyValue{
{Key: "library.language", Value: &otelpb.AnyValue{StringValue: &libLang}},
},
},
Spans: []*otelpb.Span{
{
TraceID: "7c2e8d1a4b9f0e3d5a6b7c8d9e0f1a2b",
SpanID: "1a2b3c4d5e6f7a8b",
Name: "testSearchTagsSpan",
StartTimeUnixNano: uint64(spanTime.UnixNano()),
EndTimeUnixNano: uint64(spanTime.Add(time.Millisecond).UnixNano()),
Attributes: []*otelpb.KeyValue{
{Key: "http.method", Value: &otelpb.AnyValue{StringValue: &httpMethod}},
},
},
},
},
},
},
},
}
sut.OTLPHTTPExportTraces(t, req, at.QueryOpts{})
sut.ForceFlush(t)

startTime := spanTime.Add(-time.Hour)
endTime := time.Now()

resourceTags := []string{"k8s.cluster.name", "service.name"}
spanTags := []string{"http.method"}
scopeTags := []string{"library.language"}

f := func(scope string, wantResource, wantSpan, wantScope []string) {
t.Helper()
tc.Assert(&at.AssertOptions{
Msg: "unexpected tags for scope=" + scope + " from /select/tempo/api/v2/search/tags",
Got: func() any {
res := sut.TempoAPISearchTags(t, scope, startTime, endTime)
return [][]string{res.Tags("resource"), res.Tags("span"), res.Tags("instrumentation")}
},
Want: [][]string{wantResource, wantSpan, wantScope},
})
}

f("resource", resourceTags, []string{}, []string{})
f("span", []string{}, spanTags, []string{})
f("instrumentation", []string{}, []string{}, scopeTags)
f("all", resourceTags, spanTags, scopeTags)
}
20 changes: 18 additions & 2 deletions apptest/vtsingle.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ type Vtsingle struct {
jaegerAPITraceURL string
jaegerAPIDependenciesURL string

tempoAPISearchURL string
tempoAPISearchURL string
tempoAPISearchTagsURL string

logsQLQueryURL string

Expand Down Expand Up @@ -84,7 +85,8 @@ func StartVtsingle(instance string, flags, env []string, cli *Client) (*Vtsingle
jaegerAPITraceURL: fmt.Sprintf("http://%s/select/jaeger/api/traces/%%s", stderrExtracts[1]),
jaegerAPIDependenciesURL: fmt.Sprintf("http://%s/select/jaeger/api/dependencies", stderrExtracts[1]),

tempoAPISearchURL: fmt.Sprintf("http://%s/select/tempo/api/search", stderrExtracts[1]),
tempoAPISearchURL: fmt.Sprintf("http://%s/select/tempo/api/search", stderrExtracts[1]),
tempoAPISearchTagsURL: fmt.Sprintf("http://%s/select/tempo/api/v2/search/tags", stderrExtracts[1]),

logsQLQueryURL: fmt.Sprintf("http://%s/select/logsql/query", stderrExtracts[1]),

Expand Down Expand Up @@ -178,6 +180,20 @@ func (app *Vtsingle) TempoAPISearch(t *testing.T, q string, start, end time.Time
return NewTempoAPISearchResponse(t, res)
}

// TempoAPISearchTags is a test helper function that lists attribute names of the given scope
// by sending an HTTP GET request to /select/tempo/api/v2/search/tags Vtsingle endpoint.
func (app *Vtsingle) TempoAPISearchTags(t *testing.T, scope string, start, end time.Time) *TempoAPISearchTagsResponse {
t.Helper()

uv := url.Values{
"scope": {scope},
"start": {strconv.FormatInt(start.Unix(), 10)},
"end": {strconv.FormatInt(end.Unix(), 10)},
}
res, _ := app.cli.Get(t, app.tempoAPISearchTagsURL+"?"+uv.Encode())
return NewTempoAPISearchTagsResponse(t, res)
}

// JaegerAPIDependencies is a test helper function that queries for the dependencies.
// This method is not implemented in Vtsingle and this test is no-op for now.
func (app *Vtsingle) JaegerAPIDependencies(t *testing.T, param JaegerDependenciesParam, opts QueryOpts) *JaegerAPIDependenciesResponse {
Expand Down
1 change: 1 addition & 0 deletions docs/victoriatraces/changelog/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The following `tip` changes can be tested by building VictoriaTraces components

* SECURITY: upgrade Go builder from Go1.26.6 to Go1.27.2. See the list of issues addressed in [Go1.26.7](https://github.com/golang/go/issues?q=milestone%3AGo1.26.7+label%3ACherryPickApproved), [Go1.27.1](https://github.com/golang/go/issues?q=milestone%3AGo1.27.1+label%3ACherryPickApproved) and [Go1.27.2](https://github.com/golang/go/issues?q=milestone%3AGo1.27.2+label%3ACherryPickApproved).

* BUGFIX: [Single-node VictoriaTraces](https://docs.victoriametrics.com/victoriatraces/) and vtselect in [VictoriaTraces cluster](https://docs.victoriametrics.com/victoriatraces/cluster/): return attribute names for `scope=resource`, `scope=span` and `scope=instrumentation` in the Tempo `/api/v2/search/tags` API. Previously these scopes returned empty lists, so TraceQL editors such as Perses showed no attribute names after `resource.` or `span.`. See [issue #268](https://github.com/VictoriaMetrics/VictoriaTraces/issues/268).
* BUGFIX: [Single-node VictoriaTraces](https://docs.victoriametrics.com/victoriatraces/) and vtselect in [VictoriaTraces cluster](https://docs.victoriametrics.com/victoriatraces/cluster/): apply correct time‑range filter and return proper traces from Jaeger and Tempo search APIs when VictoriaTraces runs in a non‑UTC time zone. Previously, searches could return fewer traces or empty results due to incorrect time‑zone conversion for the time‑range filter, especially for time zones west of UTC such as `TZ=America/New_York`, which impacted the end‑time boundary. See [issue #260](https://github.com/VictoriaMetrics/VictoriaTraces/issues/260). Thank @Vandit1604 for [pull request #262](https://github.com/VictoriaMetrics/VictoriaTraces/pull/262).

## [v0.12.0](https://github.com/VictoriaMetrics/VictoriaTraces/releases/tag/v0.12.0)
Expand Down