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>
This commit is contained in:
co-authored by
Copilot
ismail simsek
parent
64da716a2e
commit
d0ea82633f
+14
-263
@@ -5,10 +5,10 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/tsdb/jaeger/types"
|
||||
)
|
||||
|
||||
type JaegerQuery struct {
|
||||
@@ -46,14 +46,16 @@ func queryData(ctx context.Context, dsInfo *datasourceInfo, req *backend.QueryDa
|
||||
continue
|
||||
}
|
||||
|
||||
cfg := backend.GrafanaConfigFromContext(ctx)
|
||||
useGrpc := cfg.FeatureToggles().IsEnabled("jaegerEnableGrpcEndpoint")
|
||||
// Handle "Search" query type
|
||||
if query.QueryType == "search" {
|
||||
traces, err := dsInfo.JaegerClient.Search(&query, q.TimeRange.From.UnixMicro(), q.TimeRange.To.UnixMicro())
|
||||
// TODO: enable routing to gRPC when ready, currently pending on: https://github.com/jaegertracing/jaeger/issues/7594
|
||||
frames, err := dsInfo.JaegerClient.Search(&query, q.TimeRange.From.UnixMicro(), q.TimeRange.To.UnixMicro())
|
||||
if err != nil {
|
||||
response.Responses[q.RefID] = backend.ErrorResponseWithErrorSource(err)
|
||||
continue
|
||||
}
|
||||
frames := transformSearchResponse(traces, dsInfo)
|
||||
response.Responses[q.RefID] = backend.DataResponse{
|
||||
Frames: data.Frames{frames},
|
||||
}
|
||||
@@ -61,18 +63,24 @@ func queryData(ctx context.Context, dsInfo *datasourceInfo, req *backend.QueryDa
|
||||
|
||||
// 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())
|
||||
var frame *data.Frame
|
||||
var err error
|
||||
if useGrpc {
|
||||
frame, err = dsInfo.JaegerClient.GrpcTrace(ctx, query.Query, q.TimeRange.From, q.TimeRange.To, q.RefID)
|
||||
} else {
|
||||
frame, err = dsInfo.JaegerClient.Trace(ctx, query.Query, q.TimeRange.From.UnixMilli(), q.TimeRange.To.UnixMilli(), q.RefID)
|
||||
}
|
||||
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},
|
||||
}
|
||||
}
|
||||
|
||||
if query.QueryType == "dependencyGraph" {
|
||||
// TODO: enable routing to gRPC when ready, currently pending on: https://github.com/jaegertracing/jaeger/issues/7595
|
||||
dependencies, err := dsInfo.JaegerClient.Dependencies(ctx, q.TimeRange.From.UnixMilli(), q.TimeRange.To.UnixMilli())
|
||||
if err != nil {
|
||||
response.Responses[q.RefID] = backend.ErrorResponseWithErrorSource(err)
|
||||
@@ -93,209 +101,7 @@ func queryData(ctx context.Context, dsInfo *datasourceInfo, req *backend.QueryDa
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func transformSearchResponse(response []TraceResponse, dsInfo *datasourceInfo) *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: dsInfo.JaegerClient.settings.UID,
|
||||
DatasourceName: dsInfo.JaegerClient.settings.Name,
|
||||
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 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
|
||||
}
|
||||
|
||||
func transformDependenciesResponse(dependencies DependenciesResponse, refID string) []*data.Frame {
|
||||
func transformDependenciesResponse(dependencies types.DependenciesResponse, refID string) []*data.Frame {
|
||||
// Create nodes frame
|
||||
nodesFrame := data.NewFrame(refID+"_nodes",
|
||||
data.NewField("id", nil, []string{}),
|
||||
@@ -361,58 +167,3 @@ func transformDependenciesResponse(dependencies DependenciesResponse, refID stri
|
||||
|
||||
return []*data.Frame{nodesFrame, edgesFrame}
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user