Files
grafana/pkg/tsdb/jaeger/utils/client_utils.go
Jocelyn Collado-Kuri d0ea82633f Jaeger: Migrate API calls to gRPC endpoint (#113297)
* Jaeger: Migrate Services and Operations to the gRPC Jaeger endpoint (#112384)

* add grpc feature toggle

* move types into types.go

* creates grpc client functions for services and operations

* Call grpc services function when feature flag is enabled for health check

* remove unnecessary double encoding

* check for successful status code before decoding response and return nil in case of successful response

* remove duplicate code

* use variable

* fix error type in testsz

* Jaeger: Migrate search and Trace Search calls to use gRPC endpoint (#112610)

* move all types into types package except for JagerClient

* move all helper functions into utils package

* change return type of search function to be frames and add grpc search functionality

* fix tests

* fix types and the way we check error response from grpc

* change trace name and duration unit conversion

* fix types and add tests

* support queryAttributes

* quick limit implementation in post processing

* add todo for attributes / tags

* make trace functionality ready to support grpc flow

* add functions to process search response for a specific trace and create the Trace frame

* tests for helper funtions

* remove grpc querying for now!

* change logic to be able to process and support multiple resource spans

* remove logic for gRPC from grpc_client.go

* add equivalent fields for logs and references

* add tests for grpcTraceResponse function

* fix types after merge with main

* fix status code checks and return nil for error on successful responses

* enable reading through config flag for trace search

* create sigle key value type since they are similar for OTLP and non OTLP based formats

* reference right type

* convert events and links into references and logs

* add status code, status message and kind to data frame

* fix tests to accomodate new format

* remove unused function and add more tests

* remove edit flag for jsonc golden test files

* add clarifying comment

* fix tests and linting

* fix golden files for testing

* fix typo

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix typo

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix typo

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* add clarifying comment

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* remove unnecessary logging statement

* fix downstream errors

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* use downstreamerrorf where applicable and add missing downstream eror sources.

* tests

---------

Co-authored-by: ismail simsek <ismailsimsek09@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-31 11:19:16 -07:00

214 lines
5.3 KiB
Go

package utils
import (
"encoding/json"
"fmt"
"sort"
"time"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/tsdb/jaeger/types"
)
func TransformSearchResponse(response []types.TraceResponse, dsUID string, dsName string) *data.Frame {
// Create a frame for the traces
frame := data.NewFrame("traces",
data.NewField("traceID", nil, []string{}).SetConfig(&data.FieldConfig{
DisplayName: "Trace ID",
Links: []data.DataLink{
{
Title: "Trace: ${__value.raw}",
URL: "",
Internal: &data.InternalDataLink{
DatasourceUID: dsUID,
DatasourceName: dsName,
Query: map[string]interface{}{
"query": "${__value.raw}",
},
},
},
},
}),
data.NewField("traceName", nil, []string{}).SetConfig(&data.FieldConfig{
DisplayName: "Trace name",
}),
data.NewField("startTime", nil, []time.Time{}).SetConfig(&data.FieldConfig{
DisplayName: "Start time",
}),
data.NewField("duration", nil, []int64{}).SetConfig(&data.FieldConfig{
DisplayName: "Duration",
Unit: "µs",
}),
)
// Set the visualization type to table
frame.Meta = &data.FrameMeta{
PreferredVisualization: "table",
}
// Sort traces by start time in descending order (newest first)
sort.Slice(response, func(i, j int) bool {
rootSpanI := response[i].Spans[0]
rootSpanJ := response[j].Spans[0]
for _, span := range response[i].Spans {
if span.StartTime < rootSpanI.StartTime {
rootSpanI = span
}
}
for _, span := range response[j].Spans {
if span.StartTime < rootSpanJ.StartTime {
rootSpanJ = span
}
}
return rootSpanI.StartTime > rootSpanJ.StartTime
})
// Process each trace
for _, trace := range response {
if len(trace.Spans) == 0 {
continue
}
// Get the root span
rootSpan := trace.Spans[0]
for _, span := range trace.Spans {
if span.StartTime < rootSpan.StartTime {
rootSpan = span
}
}
// Get the service name for the trace
serviceName := ""
if process, ok := trace.Processes[rootSpan.ProcessID]; ok {
serviceName = process.ServiceName
}
// Get the trace name and start time
traceName := fmt.Sprintf("%s: %s", serviceName, rootSpan.OperationName)
startTime := time.Unix(0, rootSpan.StartTime*1000)
// Append the row to the frame
frame.AppendRow(
trace.TraceID,
traceName,
startTime,
rootSpan.Duration,
)
}
return frame
}
func TransformTraceResponse(trace types.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 := []types.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
}