Jaeger: Run traceID query through backend with feature toggle enabled (#103472)
* Jaeger: Run traceID query through backend with feature toggle enabled * Reorganize * Update comments and test * Fix snapshot test * Only pass time params if enabled in cofig * Rename applyVariables to applyTemplateVariables * Rename transformResponse to transformTraceResponse * Update comment
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package jaeger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -11,9 +12,10 @@ import (
|
||||
)
|
||||
|
||||
type JaegerClient struct {
|
||||
logger log.Logger
|
||||
url string
|
||||
httpClient *http.Client
|
||||
logger log.Logger
|
||||
url string
|
||||
httpClient *http.Client
|
||||
traceIdTimeEnabled bool
|
||||
}
|
||||
|
||||
type ServicesResponse struct {
|
||||
@@ -24,11 +26,12 @@ type ServicesResponse struct {
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
func New(url string, hc *http.Client, logger log.Logger) (JaegerClient, error) {
|
||||
func New(url string, hc *http.Client, logger log.Logger, traceIdTimeEnabled bool) (JaegerClient, error) {
|
||||
client := JaegerClient{
|
||||
logger: logger,
|
||||
url: url,
|
||||
httpClient: hc,
|
||||
logger: logger,
|
||||
url: url,
|
||||
httpClient: hc,
|
||||
traceIdTimeEnabled: traceIdTimeEnabled,
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
@@ -88,3 +91,70 @@ func (j *JaegerClient) Operations(s string) ([]string, error) {
|
||||
operations = response.Data
|
||||
return operations, err
|
||||
}
|
||||
|
||||
func (j *JaegerClient) Trace(ctx context.Context, traceID string, start, end int64) (TraceResponse, error) {
|
||||
logger := j.logger.FromContext(ctx)
|
||||
var response TracesResponse
|
||||
trace := TraceResponse{}
|
||||
|
||||
if traceID == "" {
|
||||
return trace, backend.DownstreamError(fmt.Errorf("traceID is empty"))
|
||||
}
|
||||
|
||||
traceUrl, err := url.JoinPath(j.url, "/api/traces", url.QueryEscape(traceID))
|
||||
if err != nil {
|
||||
return trace, backend.DownstreamError(fmt.Errorf("failed to join url: %w", err))
|
||||
}
|
||||
|
||||
// Add time parameters if provided and traceIdTimeEnabled is true
|
||||
if j.traceIdTimeEnabled {
|
||||
if start > 0 || end > 0 {
|
||||
parsedURL, err := url.Parse(traceUrl)
|
||||
if err != nil {
|
||||
return trace, backend.DownstreamError(fmt.Errorf("failed to parse url: %w", err))
|
||||
}
|
||||
|
||||
query := parsedURL.Query()
|
||||
if start > 0 {
|
||||
query.Set("start", fmt.Sprintf("%d", start))
|
||||
}
|
||||
if end > 0 {
|
||||
query.Set("end", fmt.Sprintf("%d", end))
|
||||
}
|
||||
|
||||
parsedURL.RawQuery = query.Encode()
|
||||
traceUrl = parsedURL.String()
|
||||
}
|
||||
}
|
||||
|
||||
res, err := j.httpClient.Get(traceUrl)
|
||||
if err != nil {
|
||||
if backend.IsDownstreamHTTPError(err) {
|
||||
return trace, backend.DownstreamError(err)
|
||||
}
|
||||
return trace, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err = res.Body.Close(); err != nil {
|
||||
logger.Error("Failed to close response body", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if res != nil && res.StatusCode/100 != 2 {
|
||||
err := backend.DownstreamError(fmt.Errorf("request failed: %s", res.Status))
|
||||
if backend.ErrorSourceFromHTTPStatus(res.StatusCode) == backend.ErrorSourceDownstream {
|
||||
return trace, backend.DownstreamError(err)
|
||||
}
|
||||
return trace, err
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
|
||||
return trace, err
|
||||
}
|
||||
|
||||
// We only support one trace at a time
|
||||
// this is how it was implemented in the frontend before
|
||||
trace = response.Data[0]
|
||||
return trace, err
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package jaeger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -58,7 +60,7 @@ func TestJaegerClient_Services(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client, err := New(server.URL, server.Client(), log.NewNullLogger())
|
||||
client, err := New(server.URL, server.Client(), log.NewNullLogger(), false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
services, err := client.Services()
|
||||
@@ -147,7 +149,7 @@ func TestJaegerClient_Operations(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client, err := New(server.URL, server.Client(), log.NewNullLogger())
|
||||
client, err := New(server.URL, server.Client(), log.NewNullLogger(), false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
operations, err := client.Operations(tt.service)
|
||||
@@ -164,3 +166,113 @@ func TestJaegerClient_Operations(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJaegerClient_Trace(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
traceId string
|
||||
traceIdTimeEnabled bool
|
||||
start int64
|
||||
end int64
|
||||
mockResponse string
|
||||
mockStatusCode int
|
||||
mockStatus string
|
||||
expectedURL string
|
||||
expectError bool
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Successful response with time params enabled",
|
||||
traceId: "abc123",
|
||||
traceIdTimeEnabled: true,
|
||||
start: 1000,
|
||||
end: 2000,
|
||||
mockResponse: `{"data":[{"traceID":"abc123"}]}`,
|
||||
mockStatusCode: http.StatusOK,
|
||||
mockStatus: "OK",
|
||||
expectedURL: "/api/traces/abc123?end=2000&start=1000",
|
||||
expectError: false,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Successful response with time params disabled",
|
||||
traceId: "abc123",
|
||||
traceIdTimeEnabled: false,
|
||||
start: 1000,
|
||||
end: 2000,
|
||||
mockResponse: `{"data":[{"traceID":"abc123"}]}`,
|
||||
mockStatusCode: http.StatusOK,
|
||||
mockStatus: "OK",
|
||||
expectedURL: "/api/traces/abc123",
|
||||
expectError: false,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Non-200 response",
|
||||
traceId: "abc123",
|
||||
traceIdTimeEnabled: true,
|
||||
start: 1000,
|
||||
end: 2000,
|
||||
mockResponse: "",
|
||||
mockStatusCode: http.StatusInternalServerError,
|
||||
mockStatus: "Internal Server Error",
|
||||
expectedURL: "/api/traces/abc123?end=2000&start=1000",
|
||||
expectError: true,
|
||||
expectedError: backend.PluginError(errors.New("Internal Server Error")),
|
||||
},
|
||||
{
|
||||
name: "Invalid JSON response",
|
||||
traceId: "abc123",
|
||||
traceIdTimeEnabled: true,
|
||||
start: 1000,
|
||||
end: 2000,
|
||||
mockResponse: `{invalid json`,
|
||||
mockStatusCode: http.StatusOK,
|
||||
mockStatus: "OK",
|
||||
expectedURL: "/api/traces/abc123?end=2000&start=1000",
|
||||
expectError: true,
|
||||
expectedError: &json.SyntaxError{},
|
||||
},
|
||||
{
|
||||
name: "Empty trace ID",
|
||||
traceId: "",
|
||||
traceIdTimeEnabled: true,
|
||||
start: 1000,
|
||||
end: 2000,
|
||||
mockResponse: `{"data":[]}`,
|
||||
mockStatusCode: http.StatusOK,
|
||||
mockStatus: "OK",
|
||||
expectedURL: "",
|
||||
expectError: true,
|
||||
expectedError: backend.DownstreamError(errors.New("traceID is empty")),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var actualURL string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
actualURL = r.URL.String()
|
||||
w.WriteHeader(tt.mockStatusCode)
|
||||
_, _ = w.Write([]byte(tt.mockResponse))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client, err := New(server.URL, server.Client(), log.NewNullLogger(), tt.traceIdTimeEnabled)
|
||||
assert.NoError(t, err)
|
||||
|
||||
trace, err := client.Trace(context.Background(), tt.traceId, tt.start, tt.end)
|
||||
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
if tt.expectedError != nil {
|
||||
assert.IsType(t, tt.expectedError, err)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, trace)
|
||||
}
|
||||
assert.Equal(t, tt.expectedURL, actualURL)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package jaeger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
@@ -29,6 +30,12 @@ type datasourceInfo struct {
|
||||
JaegerClient JaegerClient
|
||||
}
|
||||
|
||||
type datasourceJSONData struct {
|
||||
TraceIdTimeParams struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
} `json:"traceIdTimeParams"`
|
||||
}
|
||||
|
||||
func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.InstanceFactoryFunc {
|
||||
return func(ctx context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
|
||||
httpClientOptions, err := settings.HTTPClientOptions(ctx)
|
||||
@@ -45,8 +52,14 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst
|
||||
return nil, backend.DownstreamError(errors.New("error reading settings: url is empty"))
|
||||
}
|
||||
|
||||
var jsonData datasourceJSONData
|
||||
err = json.Unmarshal(settings.JSONData, &jsonData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading settings: %w", err)
|
||||
}
|
||||
|
||||
logger := logger.FromContext(ctx)
|
||||
jaegerClient, err := New(settings.URL, httpClient, logger)
|
||||
jaegerClient, err := New(settings.URL, httpClient, logger, jsonData.TraceIdTimeParams.Enabled)
|
||||
return &datasourceInfo{JaegerClient: jaegerClient}, err
|
||||
}
|
||||
}
|
||||
@@ -91,3 +104,12 @@ func (s *Service) CallResource(ctx context.Context, req *backend.CallResourceReq
|
||||
handler := httpadapter.New(s.registerResourceRoutes())
|
||||
return handler.CallResource(ctx, req, sender)
|
||||
}
|
||||
|
||||
func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
dsInfo, err := s.getDSInfo(ctx, req.PluginContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return queryData(ctx, dsInfo, req)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package jaeger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDataSourceInstanceSettings_TraceIdTimeEnabled(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
jsonData string
|
||||
expectedEnabled bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "traceIdTimeParams enabled",
|
||||
jsonData: `{
|
||||
"traceIdTimeParams": {
|
||||
"enabled": true
|
||||
}
|
||||
}`,
|
||||
expectedEnabled: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "traceIdTimeParams disabled",
|
||||
jsonData: `{
|
||||
"traceIdTimeParams": {
|
||||
"enabled": false
|
||||
}
|
||||
}`,
|
||||
expectedEnabled: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "traceIdTimeParams not specified",
|
||||
jsonData: `{}`,
|
||||
expectedEnabled: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "traceIdTimeParams without enabled",
|
||||
jsonData: `{"traceIdTimeParams":{}}`,
|
||||
expectedEnabled: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid JSON",
|
||||
jsonData: `{invalid json`,
|
||||
expectedEnabled: false,
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create instance settings
|
||||
settings := backend.DataSourceInstanceSettings{
|
||||
JSONData: []byte(tt.jsonData),
|
||||
URL: "http://localhost:16686",
|
||||
}
|
||||
|
||||
// Create instance factory
|
||||
factory := newInstanceSettings(httpclient.NewProvider())
|
||||
instance, err := factory(context.Background(), settings)
|
||||
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, instance)
|
||||
|
||||
// Get the datasource info
|
||||
dsInfo, ok := instance.(*datasourceInfo)
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, dsInfo)
|
||||
|
||||
// Verify the client's traceIdTimeEnabled parameter
|
||||
assert.Equal(t, tt.expectedEnabled, dsInfo.JaegerClient.traceIdTimeEnabled)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package jaeger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
)
|
||||
|
||||
type jaegerQuery struct {
|
||||
QueryType string `json:"queryType"`
|
||||
Service string `json:"service"`
|
||||
Operation string `json:"operation"`
|
||||
Query string `json:"query"`
|
||||
Tags string `json:"tags"`
|
||||
MinDuration string `json:"minDuration"`
|
||||
MaxDuration string `json:"maxDuration"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
func queryData(ctx context.Context, dsInfo *datasourceInfo, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
response := backend.NewQueryDataResponse()
|
||||
|
||||
for _, q := range req.Queries {
|
||||
var query jaegerQuery
|
||||
|
||||
err := json.Unmarshal(q.JSON, &query)
|
||||
if err != nil {
|
||||
err = backend.DownstreamError(fmt.Errorf("error while parsing the query json. %w", err))
|
||||
response.Responses[q.RefID] = backend.ErrorResponseWithErrorSource(err)
|
||||
continue
|
||||
}
|
||||
|
||||
// No query type means traceID query
|
||||
if query.QueryType == "" {
|
||||
traces, err := dsInfo.JaegerClient.Trace(ctx, query.Query, q.TimeRange.From.UnixMilli(), q.TimeRange.To.UnixMilli())
|
||||
if err != nil {
|
||||
response.Responses[q.RefID] = backend.ErrorResponseWithErrorSource(err)
|
||||
continue
|
||||
}
|
||||
frame := transformTraceResponse(traces, q.RefID)
|
||||
response.Responses[q.RefID] = backend.DataResponse{
|
||||
Frames: []*data.Frame{frame},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// transformTraceResponse converts Jaeger trace data to a Data frame
|
||||
func transformTraceResponse(trace TraceResponse, refID string) *data.Frame {
|
||||
frame := data.NewFrame(refID,
|
||||
data.NewField("traceID", nil, []string{}),
|
||||
data.NewField("spanID", nil, []string{}),
|
||||
data.NewField("parentSpanID", nil, []*string{}),
|
||||
data.NewField("operationName", nil, []string{}),
|
||||
data.NewField("serviceName", nil, []string{}),
|
||||
data.NewField("serviceTags", nil, []json.RawMessage{}),
|
||||
data.NewField("startTime", nil, []float64{}),
|
||||
data.NewField("duration", nil, []float64{}),
|
||||
data.NewField("logs", nil, []json.RawMessage{}),
|
||||
data.NewField("references", nil, []json.RawMessage{}),
|
||||
data.NewField("tags", nil, []json.RawMessage{}),
|
||||
data.NewField("warnings", nil, []json.RawMessage{}),
|
||||
data.NewField("stackTraces", nil, []json.RawMessage{}),
|
||||
)
|
||||
|
||||
// Set metadata for trace visualization
|
||||
frame.Meta = &data.FrameMeta{
|
||||
PreferredVisualization: "trace",
|
||||
Custom: map[string]interface{}{
|
||||
"traceFormat": "jaeger",
|
||||
},
|
||||
}
|
||||
|
||||
// Process each span in the trace
|
||||
for _, span := range trace.Spans {
|
||||
// Find parent span ID
|
||||
var parentSpanID *string
|
||||
for _, ref := range span.References {
|
||||
if ref.RefType == "CHILD_OF" {
|
||||
s := ref.SpanID
|
||||
parentSpanID = &s
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Get service name and tags
|
||||
serviceName := ""
|
||||
serviceTags := json.RawMessage{}
|
||||
if process, ok := trace.Processes[span.ProcessID]; ok {
|
||||
serviceName = process.ServiceName
|
||||
tagsMarshaled, err := json.Marshal(process.Tags)
|
||||
if err == nil {
|
||||
serviceTags = json.RawMessage(tagsMarshaled)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert logs
|
||||
logs := json.RawMessage{}
|
||||
logsMarshaled, err := json.Marshal(span.Logs)
|
||||
if err == nil {
|
||||
logs = json.RawMessage(logsMarshaled)
|
||||
}
|
||||
|
||||
// Convert references (excluding parent)
|
||||
references := json.RawMessage{}
|
||||
filteredRefs := []TraceSpanReference{}
|
||||
for _, ref := range span.References {
|
||||
if parentSpanID == nil || ref.SpanID != *parentSpanID {
|
||||
filteredRefs = append(filteredRefs, ref)
|
||||
}
|
||||
}
|
||||
refsMarshaled, err := json.Marshal(filteredRefs)
|
||||
if err == nil {
|
||||
references = json.RawMessage(refsMarshaled)
|
||||
}
|
||||
|
||||
// Convert tags
|
||||
tags := json.RawMessage{}
|
||||
tagsMarshaled, err := json.Marshal(span.Tags)
|
||||
if err == nil {
|
||||
tags = json.RawMessage(tagsMarshaled)
|
||||
}
|
||||
|
||||
// Convert warnings
|
||||
warnings := json.RawMessage{}
|
||||
warningsMarshaled, err := json.Marshal(span.Warnings)
|
||||
if err == nil {
|
||||
warnings = json.RawMessage(warningsMarshaled)
|
||||
}
|
||||
|
||||
// Convert stack traces
|
||||
stackTraces := json.RawMessage{}
|
||||
stackTracesMarshaled, err := json.Marshal(span.StackTraces)
|
||||
if err == nil {
|
||||
stackTraces = json.RawMessage(stackTracesMarshaled)
|
||||
}
|
||||
|
||||
// Add span to frame
|
||||
frame.AppendRow(
|
||||
span.TraceID,
|
||||
span.SpanID,
|
||||
parentSpanID,
|
||||
span.OperationName,
|
||||
serviceName,
|
||||
serviceTags,
|
||||
float64(span.StartTime)/1000, // Convert microseconds to milliseconds
|
||||
float64(span.Duration)/1000, // Convert microseconds to milliseconds
|
||||
logs,
|
||||
references,
|
||||
tags,
|
||||
warnings,
|
||||
stackTraces,
|
||||
)
|
||||
}
|
||||
|
||||
return frame
|
||||
}
|
||||
|
||||
type TraceKeyValuePair struct {
|
||||
Key string `json:"key"`
|
||||
Type string `json:"type"`
|
||||
Value interface{} `json:"value"`
|
||||
}
|
||||
|
||||
type TraceProcess struct {
|
||||
ServiceName string `json:"serviceName"`
|
||||
Tags []TraceKeyValuePair `json:"tags"`
|
||||
}
|
||||
|
||||
type TraceSpanReference struct {
|
||||
RefType string `json:"refType"`
|
||||
SpanID string `json:"spanID"`
|
||||
TraceID string `json:"traceID"`
|
||||
}
|
||||
|
||||
type TraceLog struct {
|
||||
// Millisecond epoch time
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Fields []TraceKeyValuePair `json:"fields"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Span struct {
|
||||
TraceID string `json:"traceID"`
|
||||
SpanID string `json:"spanID"`
|
||||
ProcessID string `json:"processID"`
|
||||
OperationName string `json:"operationName"`
|
||||
// Times are in microseconds
|
||||
StartTime int64 `json:"startTime"`
|
||||
Duration int64 `json:"duration"`
|
||||
Logs []TraceLog `json:"logs"`
|
||||
References []TraceSpanReference `json:"references"`
|
||||
Tags []TraceKeyValuePair `json:"tags"`
|
||||
Warnings []string `json:"warnings"`
|
||||
Flags int `json:"flags"`
|
||||
StackTraces []string `json:"stackTraces"`
|
||||
}
|
||||
|
||||
type TraceResponse struct {
|
||||
Processes map[string]TraceProcess `json:"processes"`
|
||||
TraceID string `json:"traceID"`
|
||||
Warnings []string `json:"warnings"`
|
||||
Spans []Span `json:"spans"`
|
||||
}
|
||||
|
||||
type TracesResponse struct {
|
||||
Data []TraceResponse `json:"data"`
|
||||
Errors interface{} `json:"errors"` // TODO: Handle errors, but we were not using them in the frontend either
|
||||
Limit int `json:"limit"`
|
||||
Offset int `json:"offset"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package jaeger
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/experimental"
|
||||
)
|
||||
|
||||
func TestTransformTraceResponse(t *testing.T) {
|
||||
t.Run("simple_trace", func(t *testing.T) {
|
||||
trace := TraceResponse{
|
||||
TraceID: "3fa414edcef6ad90",
|
||||
Spans: []Span{
|
||||
{
|
||||
TraceID: "3fa414edcef6ad90",
|
||||
SpanID: "3fa414edcef6ad90",
|
||||
OperationName: "HTTP GET - api_traces_traceid",
|
||||
StartTime: 1605873894680409,
|
||||
Duration: 1049141,
|
||||
Tags: []TraceKeyValuePair{
|
||||
{Key: "sampler.type", Type: "string", Value: "probabilistic"},
|
||||
{Key: "sampler.param", Type: "float64", Value: 1},
|
||||
},
|
||||
Logs: []TraceLog{},
|
||||
ProcessID: "p1",
|
||||
Warnings: nil,
|
||||
Flags: 0,
|
||||
},
|
||||
{
|
||||
TraceID: "3fa414edcef6ad90",
|
||||
SpanID: "0f5c1808567e4403",
|
||||
OperationName: "/tempopb.Querier/FindTraceByID",
|
||||
References: []TraceSpanReference{
|
||||
{
|
||||
RefType: "CHILD_OF",
|
||||
TraceID: "3fa414edcef6ad90",
|
||||
SpanID: "3fa414edcef6ad90",
|
||||
},
|
||||
},
|
||||
StartTime: 1605873894680587,
|
||||
Duration: 1847,
|
||||
Tags: []TraceKeyValuePair{
|
||||
{Key: "component", Type: "string", Value: "gRPC"},
|
||||
{Key: "span.kind", Type: "string", Value: "client"},
|
||||
},
|
||||
Logs: []TraceLog{},
|
||||
ProcessID: "p1",
|
||||
Warnings: nil,
|
||||
Flags: 0,
|
||||
},
|
||||
},
|
||||
Processes: map[string]TraceProcess{
|
||||
"p1": {
|
||||
ServiceName: "tempo-querier",
|
||||
Tags: []TraceKeyValuePair{
|
||||
{Key: "cluster", Type: "string", Value: "ops-tools1"},
|
||||
{Key: "container", Type: "string", Value: "tempo-query"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Warnings: nil,
|
||||
}
|
||||
|
||||
frame := transformTraceResponse(trace, "test")
|
||||
experimental.CheckGoldenJSONFrame(t, "./testdata", "simple_trace.golden", frame, false)
|
||||
})
|
||||
|
||||
t.Run("complex_trace", func(t *testing.T) {
|
||||
trace := TraceResponse{
|
||||
TraceID: "3fa414edcef6ad90",
|
||||
Spans: []Span{
|
||||
{
|
||||
TraceID: "3fa414edcef6ad90",
|
||||
SpanID: "3fa414edcef6ad90",
|
||||
OperationName: "HTTP GET - api_traces_traceid",
|
||||
References: []TraceSpanReference{},
|
||||
StartTime: 1605873894680409,
|
||||
Duration: 1049141,
|
||||
Tags: []TraceKeyValuePair{
|
||||
{Key: "sampler.type", Type: "string", Value: "probabilistic"},
|
||||
{Key: "sampler.param", Type: "float64", Value: 1},
|
||||
{Key: "error", Type: "bool", Value: true},
|
||||
{Key: "http.status_code", Type: "int", Value: 500},
|
||||
},
|
||||
Logs: []TraceLog{
|
||||
{
|
||||
Timestamp: 1605873894681000,
|
||||
Fields: []TraceKeyValuePair{
|
||||
{Key: "event", Type: "string", Value: "error"},
|
||||
{Key: "message", Type: "string", Value: "Internal server error"},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProcessID: "p1",
|
||||
Warnings: []string{"High latency detected", "Error rate above threshold"},
|
||||
Flags: 0,
|
||||
},
|
||||
{
|
||||
TraceID: "3fa414edcef6ad90",
|
||||
SpanID: "0f5c1808567e4403",
|
||||
OperationName: "/tempopb.Querier/FindTraceByID",
|
||||
References: []TraceSpanReference{
|
||||
{
|
||||
RefType: "CHILD_OF",
|
||||
TraceID: "3fa414edcef6ad90",
|
||||
SpanID: "3fa414edcef6ad90",
|
||||
},
|
||||
},
|
||||
StartTime: 1605873894680587,
|
||||
Duration: 1847,
|
||||
Tags: []TraceKeyValuePair{
|
||||
{Key: "component", Type: "string", Value: "gRPC"},
|
||||
{Key: "span.kind", Type: "string", Value: "client"},
|
||||
{Key: "error", Type: "bool", Value: true},
|
||||
{Key: "grpc.status_code", Type: "int", Value: 13},
|
||||
},
|
||||
Logs: []TraceLog{
|
||||
{
|
||||
Timestamp: 1605873894680700,
|
||||
Fields: []TraceKeyValuePair{
|
||||
{Key: "event", Type: "string", Value: "error"},
|
||||
{Key: "message", Type: "string", Value: "gRPC error: INTERNAL"},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProcessID: "p1",
|
||||
Warnings: []string{"gRPC call failed", "Retry attempt 3"},
|
||||
Flags: 0,
|
||||
},
|
||||
{
|
||||
TraceID: "3fa414edcef6ad90",
|
||||
SpanID: "1a2b3c4d5e6f7g8h",
|
||||
OperationName: "db.query",
|
||||
References: []TraceSpanReference{
|
||||
{
|
||||
RefType: "CHILD_OF",
|
||||
TraceID: "3fa414edcef6ad90",
|
||||
SpanID: "0f5c1808567e4403",
|
||||
},
|
||||
},
|
||||
StartTime: 1605873894680800,
|
||||
Duration: 500,
|
||||
Tags: []TraceKeyValuePair{
|
||||
{Key: "db.type", Type: "string", Value: "postgresql"},
|
||||
{Key: "db.statement", Type: "string", Value: "SELECT * FROM traces WHERE id = $1"},
|
||||
{Key: "error", Type: "bool", Value: true},
|
||||
},
|
||||
Logs: []TraceLog{
|
||||
{
|
||||
Timestamp: 1605873894680850,
|
||||
Fields: []TraceKeyValuePair{
|
||||
{Key: "event", Type: "string", Value: "error"},
|
||||
{Key: "message", Type: "string", Value: "Database connection timeout"},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProcessID: "p2",
|
||||
Warnings: []string{"Database connection slow", "Query timeout"},
|
||||
Flags: 0,
|
||||
},
|
||||
},
|
||||
Processes: map[string]TraceProcess{
|
||||
"p1": {
|
||||
ServiceName: "tempo-querier",
|
||||
Tags: []TraceKeyValuePair{
|
||||
{Key: "cluster", Type: "string", Value: "ops-tools1"},
|
||||
{Key: "container", Type: "string", Value: "tempo-query"},
|
||||
{Key: "version", Type: "string", Value: "1.2.3"},
|
||||
},
|
||||
},
|
||||
"p2": {
|
||||
ServiceName: "tempo-storage",
|
||||
Tags: []TraceKeyValuePair{
|
||||
{Key: "cluster", Type: "string", Value: "ops-tools1"},
|
||||
{Key: "container", Type: "string", Value: "tempo-storage"},
|
||||
{Key: "version", Type: "string", Value: "2.0.1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Warnings: []string{"Trace contains errors", "Multiple service failures"},
|
||||
}
|
||||
|
||||
frame := transformTraceResponse(trace, "test")
|
||||
experimental.CheckGoldenJSONFrame(t, "./testdata", "complex_trace.golden", frame, false)
|
||||
})
|
||||
}
|
||||
+375
@@ -0,0 +1,375 @@
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
//
|
||||
// Frame[0] {
|
||||
// "typeVersion": [
|
||||
// 0,
|
||||
// 0
|
||||
// ],
|
||||
// "custom": {
|
||||
// "traceFormat": "jaeger"
|
||||
// },
|
||||
// "preferredVisualisationType": "trace"
|
||||
// }
|
||||
// Name: test
|
||||
// Dimensions: 13 Fields by 3 Rows
|
||||
// +------------------+------------------+--------------------+--------------------------------+-------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------+-----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------+-------------------------+
|
||||
// | Name: traceID | Name: spanID | Name: parentSpanID | Name: operationName | Name: serviceName | Name: serviceTags | Name: startTime | Name: duration | Name: logs | Name: references | Name: tags | Name: warnings | Name: stackTraces |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []*string | Type: []string | Type: []string | Type: []json.RawMessage | Type: []float64 | Type: []float64 | Type: []json.RawMessage | Type: []json.RawMessage | Type: []json.RawMessage | Type: []json.RawMessage | Type: []json.RawMessage |
|
||||
// +------------------+------------------+--------------------+--------------------------------+-------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------+-----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------+-------------------------+
|
||||
// | 3fa414edcef6ad90 | 3fa414edcef6ad90 | null | HTTP GET - api_traces_traceid | tempo-querier | [{"key":"cluster","type":"string","value":"ops-tools1"},{"key":"container","type":"string","value":"tempo-query"},{"key":"version","type":"string","value":"1.2.3"}] | 1.605873894680409e+12 | 1049.141 | [{"timestamp":1605873894681000,"fields":[{"key":"event","type":"string","value":"error"},{"key":"message","type":"string","value":"Internal server error"}],"name":""}] | [] | [{"key":"sampler.type","type":"string","value":"probabilistic"},{"key":"sampler.param","type":"float64","value":1},{"key":"error","type":"bool","value":true},{"key":"http.status_code","type":"int","value":500}] | ["High latency detected","Error rate above threshold"] | null |
|
||||
// | 3fa414edcef6ad90 | 0f5c1808567e4403 | 3fa414edcef6ad90 | /tempopb.Querier/FindTraceByID | tempo-querier | [{"key":"cluster","type":"string","value":"ops-tools1"},{"key":"container","type":"string","value":"tempo-query"},{"key":"version","type":"string","value":"1.2.3"}] | 1.605873894680587e+12 | 1.847 | [{"timestamp":1605873894680700,"fields":[{"key":"event","type":"string","value":"error"},{"key":"message","type":"string","value":"gRPC error: INTERNAL"}],"name":""}] | [] | [{"key":"component","type":"string","value":"gRPC"},{"key":"span.kind","type":"string","value":"client"},{"key":"error","type":"bool","value":true},{"key":"grpc.status_code","type":"int","value":13}] | ["gRPC call failed","Retry attempt 3"] | null |
|
||||
// | 3fa414edcef6ad90 | 1a2b3c4d5e6f7g8h | 0f5c1808567e4403 | db.query | tempo-storage | [{"key":"cluster","type":"string","value":"ops-tools1"},{"key":"container","type":"string","value":"tempo-storage"},{"key":"version","type":"string","value":"2.0.1"}] | 1.6058738946808e+12 | 0.5 | [{"timestamp":1605873894680850,"fields":[{"key":"event","type":"string","value":"error"},{"key":"message","type":"string","value":"Database connection timeout"}],"name":""}] | [] | [{"key":"db.type","type":"string","value":"postgresql"},{"key":"db.statement","type":"string","value":"SELECT * FROM traces WHERE id = $1"},{"key":"error","type":"bool","value":true}] | ["Database connection slow","Query timeout"] | null |
|
||||
// +------------------+------------------+--------------------+--------------------------------+-------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------+-----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------+-------------------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
{
|
||||
"status": 200,
|
||||
"frames": [
|
||||
{
|
||||
"schema": {
|
||||
"name": "test",
|
||||
"meta": {
|
||||
"typeVersion": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"custom": {
|
||||
"traceFormat": "jaeger"
|
||||
},
|
||||
"preferredVisualisationType": "trace"
|
||||
},
|
||||
"fields": [
|
||||
{
|
||||
"name": "traceID",
|
||||
"type": "string",
|
||||
"typeInfo": {
|
||||
"frame": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spanID",
|
||||
"type": "string",
|
||||
"typeInfo": {
|
||||
"frame": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "parentSpanID",
|
||||
"type": "string",
|
||||
"typeInfo": {
|
||||
"frame": "string",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "operationName",
|
||||
"type": "string",
|
||||
"typeInfo": {
|
||||
"frame": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "serviceName",
|
||||
"type": "string",
|
||||
"typeInfo": {
|
||||
"frame": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "serviceTags",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "startTime",
|
||||
"type": "number",
|
||||
"typeInfo": {
|
||||
"frame": "float64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "duration",
|
||||
"type": "number",
|
||||
"typeInfo": {
|
||||
"frame": "float64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "logs",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "references",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "warnings",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stackTraces",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"values": [
|
||||
[
|
||||
"3fa414edcef6ad90",
|
||||
"3fa414edcef6ad90",
|
||||
"3fa414edcef6ad90"
|
||||
],
|
||||
[
|
||||
"3fa414edcef6ad90",
|
||||
"0f5c1808567e4403",
|
||||
"1a2b3c4d5e6f7g8h"
|
||||
],
|
||||
[
|
||||
null,
|
||||
"3fa414edcef6ad90",
|
||||
"0f5c1808567e4403"
|
||||
],
|
||||
[
|
||||
"HTTP GET - api_traces_traceid",
|
||||
"/tempopb.Querier/FindTraceByID",
|
||||
"db.query"
|
||||
],
|
||||
[
|
||||
"tempo-querier",
|
||||
"tempo-querier",
|
||||
"tempo-storage"
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
"key": "cluster",
|
||||
"type": "string",
|
||||
"value": "ops-tools1"
|
||||
},
|
||||
{
|
||||
"key": "container",
|
||||
"type": "string",
|
||||
"value": "tempo-query"
|
||||
},
|
||||
{
|
||||
"key": "version",
|
||||
"type": "string",
|
||||
"value": "1.2.3"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"key": "cluster",
|
||||
"type": "string",
|
||||
"value": "ops-tools1"
|
||||
},
|
||||
{
|
||||
"key": "container",
|
||||
"type": "string",
|
||||
"value": "tempo-query"
|
||||
},
|
||||
{
|
||||
"key": "version",
|
||||
"type": "string",
|
||||
"value": "1.2.3"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"key": "cluster",
|
||||
"type": "string",
|
||||
"value": "ops-tools1"
|
||||
},
|
||||
{
|
||||
"key": "container",
|
||||
"type": "string",
|
||||
"value": "tempo-storage"
|
||||
},
|
||||
{
|
||||
"key": "version",
|
||||
"type": "string",
|
||||
"value": "2.0.1"
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
1605873894680.409,
|
||||
1605873894680.587,
|
||||
1605873894680.8
|
||||
],
|
||||
[
|
||||
1049.141,
|
||||
1.847,
|
||||
0.5
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
"timestamp": 1605873894681000,
|
||||
"fields": [
|
||||
{
|
||||
"key": "event",
|
||||
"type": "string",
|
||||
"value": "error"
|
||||
},
|
||||
{
|
||||
"key": "message",
|
||||
"type": "string",
|
||||
"value": "Internal server error"
|
||||
}
|
||||
],
|
||||
"name": ""
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"timestamp": 1605873894680700,
|
||||
"fields": [
|
||||
{
|
||||
"key": "event",
|
||||
"type": "string",
|
||||
"value": "error"
|
||||
},
|
||||
{
|
||||
"key": "message",
|
||||
"type": "string",
|
||||
"value": "gRPC error: INTERNAL"
|
||||
}
|
||||
],
|
||||
"name": ""
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"timestamp": 1605873894680850,
|
||||
"fields": [
|
||||
{
|
||||
"key": "event",
|
||||
"type": "string",
|
||||
"value": "error"
|
||||
},
|
||||
{
|
||||
"key": "message",
|
||||
"type": "string",
|
||||
"value": "Database connection timeout"
|
||||
}
|
||||
],
|
||||
"name": ""
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
"key": "sampler.type",
|
||||
"type": "string",
|
||||
"value": "probabilistic"
|
||||
},
|
||||
{
|
||||
"key": "sampler.param",
|
||||
"type": "float64",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"key": "error",
|
||||
"type": "bool",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"key": "http.status_code",
|
||||
"type": "int",
|
||||
"value": 500
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"key": "component",
|
||||
"type": "string",
|
||||
"value": "gRPC"
|
||||
},
|
||||
{
|
||||
"key": "span.kind",
|
||||
"type": "string",
|
||||
"value": "client"
|
||||
},
|
||||
{
|
||||
"key": "error",
|
||||
"type": "bool",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"key": "grpc.status_code",
|
||||
"type": "int",
|
||||
"value": 13
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"key": "db.type",
|
||||
"type": "string",
|
||||
"value": "postgresql"
|
||||
},
|
||||
{
|
||||
"key": "db.statement",
|
||||
"type": "string",
|
||||
"value": "SELECT * FROM traces WHERE id = $1"
|
||||
},
|
||||
{
|
||||
"key": "error",
|
||||
"type": "bool",
|
||||
"value": true
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
"High latency detected",
|
||||
"Error rate above threshold"
|
||||
],
|
||||
[
|
||||
"gRPC call failed",
|
||||
"Retry attempt 3"
|
||||
],
|
||||
[
|
||||
"Database connection slow",
|
||||
"Query timeout"
|
||||
]
|
||||
],
|
||||
[
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
//
|
||||
// Frame[0] {
|
||||
// "typeVersion": [
|
||||
// 0,
|
||||
// 0
|
||||
// ],
|
||||
// "custom": {
|
||||
// "traceFormat": "jaeger"
|
||||
// },
|
||||
// "preferredVisualisationType": "trace"
|
||||
// }
|
||||
// Name: test
|
||||
// Dimensions: 13 Fields by 2 Rows
|
||||
// +------------------+------------------+--------------------+--------------------------------+-------------------+--------------------------------------------------------------------------------------------------------------------+-----------------------+-----------------+-------------------------+-------------------------+---------------------------------------------------------------------------------------------------------------------+-------------------------+-------------------------+
|
||||
// | Name: traceID | Name: spanID | Name: parentSpanID | Name: operationName | Name: serviceName | Name: serviceTags | Name: startTime | Name: duration | Name: logs | Name: references | Name: tags | Name: warnings | Name: stackTraces |
|
||||
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||
// | Type: []string | Type: []string | Type: []*string | Type: []string | Type: []string | Type: []json.RawMessage | Type: []float64 | Type: []float64 | Type: []json.RawMessage | Type: []json.RawMessage | Type: []json.RawMessage | Type: []json.RawMessage | Type: []json.RawMessage |
|
||||
// +------------------+------------------+--------------------+--------------------------------+-------------------+--------------------------------------------------------------------------------------------------------------------+-----------------------+-----------------+-------------------------+-------------------------+---------------------------------------------------------------------------------------------------------------------+-------------------------+-------------------------+
|
||||
// | 3fa414edcef6ad90 | 3fa414edcef6ad90 | null | HTTP GET - api_traces_traceid | tempo-querier | [{"key":"cluster","type":"string","value":"ops-tools1"},{"key":"container","type":"string","value":"tempo-query"}] | 1.605873894680409e+12 | 1049.141 | [] | [] | [{"key":"sampler.type","type":"string","value":"probabilistic"},{"key":"sampler.param","type":"float64","value":1}] | null | null |
|
||||
// | 3fa414edcef6ad90 | 0f5c1808567e4403 | 3fa414edcef6ad90 | /tempopb.Querier/FindTraceByID | tempo-querier | [{"key":"cluster","type":"string","value":"ops-tools1"},{"key":"container","type":"string","value":"tempo-query"}] | 1.605873894680587e+12 | 1.847 | [] | [] | [{"key":"component","type":"string","value":"gRPC"},{"key":"span.kind","type":"string","value":"client"}] | null | null |
|
||||
// +------------------+------------------+--------------------+--------------------------------+-------------------+--------------------------------------------------------------------------------------------------------------------+-----------------------+-----------------+-------------------------+-------------------------+---------------------------------------------------------------------------------------------------------------------+-------------------------+-------------------------+
|
||||
//
|
||||
//
|
||||
// 🌟 This was machine generated. Do not edit. 🌟
|
||||
{
|
||||
"status": 200,
|
||||
"frames": [
|
||||
{
|
||||
"schema": {
|
||||
"name": "test",
|
||||
"meta": {
|
||||
"typeVersion": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"custom": {
|
||||
"traceFormat": "jaeger"
|
||||
},
|
||||
"preferredVisualisationType": "trace"
|
||||
},
|
||||
"fields": [
|
||||
{
|
||||
"name": "traceID",
|
||||
"type": "string",
|
||||
"typeInfo": {
|
||||
"frame": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spanID",
|
||||
"type": "string",
|
||||
"typeInfo": {
|
||||
"frame": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "parentSpanID",
|
||||
"type": "string",
|
||||
"typeInfo": {
|
||||
"frame": "string",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "operationName",
|
||||
"type": "string",
|
||||
"typeInfo": {
|
||||
"frame": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "serviceName",
|
||||
"type": "string",
|
||||
"typeInfo": {
|
||||
"frame": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "serviceTags",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "startTime",
|
||||
"type": "number",
|
||||
"typeInfo": {
|
||||
"frame": "float64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "duration",
|
||||
"type": "number",
|
||||
"typeInfo": {
|
||||
"frame": "float64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "logs",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "references",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "warnings",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stackTraces",
|
||||
"type": "other",
|
||||
"typeInfo": {
|
||||
"frame": "json.RawMessage"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"values": [
|
||||
[
|
||||
"3fa414edcef6ad90",
|
||||
"3fa414edcef6ad90"
|
||||
],
|
||||
[
|
||||
"3fa414edcef6ad90",
|
||||
"0f5c1808567e4403"
|
||||
],
|
||||
[
|
||||
null,
|
||||
"3fa414edcef6ad90"
|
||||
],
|
||||
[
|
||||
"HTTP GET - api_traces_traceid",
|
||||
"/tempopb.Querier/FindTraceByID"
|
||||
],
|
||||
[
|
||||
"tempo-querier",
|
||||
"tempo-querier"
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
"key": "cluster",
|
||||
"type": "string",
|
||||
"value": "ops-tools1"
|
||||
},
|
||||
{
|
||||
"key": "container",
|
||||
"type": "string",
|
||||
"value": "tempo-query"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"key": "cluster",
|
||||
"type": "string",
|
||||
"value": "ops-tools1"
|
||||
},
|
||||
{
|
||||
"key": "container",
|
||||
"type": "string",
|
||||
"value": "tempo-query"
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
1605873894680.409,
|
||||
1605873894680.587
|
||||
],
|
||||
[
|
||||
1049.141,
|
||||
1.847
|
||||
],
|
||||
[
|
||||
[],
|
||||
[]
|
||||
],
|
||||
[
|
||||
[],
|
||||
[]
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
"key": "sampler.type",
|
||||
"type": "string",
|
||||
"value": "probabilistic"
|
||||
},
|
||||
{
|
||||
"key": "sampler.param",
|
||||
"type": "float64",
|
||||
"value": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"key": "component",
|
||||
"type": "string",
|
||||
"value": "gRPC"
|
||||
},
|
||||
{
|
||||
"key": "span.kind",
|
||||
"type": "string",
|
||||
"value": "client"
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
null,
|
||||
null
|
||||
],
|
||||
[
|
||||
null,
|
||||
null
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -69,6 +69,16 @@ export class JaegerDatasource extends DataSourceWithBackend<JaegerQuery, JaegerJ
|
||||
}
|
||||
|
||||
query(options: DataQueryRequest<JaegerQuery>): Observable<DataQueryResponse> {
|
||||
// No query type means that the query is a trace ID query
|
||||
// If all targets are trace ID queries, we can use the backend querying
|
||||
const allTargetsTraceIdQuery = options.targets.every((target) => !target.queryType);
|
||||
// We have not migrated the node graph to the backend
|
||||
// If the node graph is disabled, we can use the backend migration
|
||||
const nodeGraphDisabled = !this.nodeGraph?.enabled;
|
||||
if (config.featureToggles.jaegerBackendMigration && allTargetsTraceIdQuery && nodeGraphDisabled) {
|
||||
return super.query(options);
|
||||
}
|
||||
|
||||
// At this moment we expect only one target. In case we somehow change the UI to be able to show multiple
|
||||
// traces at one we need to change this.
|
||||
const target: JaegerQuery = options.targets[0];
|
||||
@@ -131,7 +141,7 @@ export class JaegerDatasource extends DataSourceWithBackend<JaegerQuery, JaegerJ
|
||||
}
|
||||
}
|
||||
|
||||
let jaegerInterpolated = pick(this.applyVariables(target, options.scopedVars), [
|
||||
let jaegerInterpolated = pick(this.applyTemplateVariables(target, options.scopedVars), [
|
||||
'service',
|
||||
'operation',
|
||||
'tags',
|
||||
@@ -176,12 +186,12 @@ export class JaegerDatasource extends DataSourceWithBackend<JaegerQuery, JaegerJ
|
||||
return {
|
||||
...query,
|
||||
datasource: this.getRef(),
|
||||
...this.applyVariables(query, scopedVars),
|
||||
...this.applyTemplateVariables(query, scopedVars),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
applyVariables(query: JaegerQuery, scopedVars: ScopedVars) {
|
||||
applyTemplateVariables(query: JaegerQuery, scopedVars: ScopedVars) {
|
||||
let expandedQuery = { ...query };
|
||||
|
||||
if (query.tags && this.templateSrv.containsTemplate(query.tags)) {
|
||||
|
||||
Reference in New Issue
Block a user