Files
grafana/pkg/tsdb/jaeger/querydata.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

170 lines
5.2 KiB
Go

package jaeger
import (
"context"
"encoding/json"
"fmt"
"sort"
"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 {
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()
logger := dsInfo.JaegerClient.logger.FromContext(ctx)
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
}
// Handle "Upload" query type
if query.QueryType == "upload" {
logger.Debug("upload query type is not supported in backend mode")
response.Responses[q.RefID] = backend.DataResponse{
Error: fmt.Errorf("unsupported query type %s. only available in frontend mode", query.QueryType),
ErrorSource: backend.ErrorSourceDownstream,
}
continue
}
cfg := backend.GrafanaConfigFromContext(ctx)
useGrpc := cfg.FeatureToggles().IsEnabled("jaegerEnableGrpcEndpoint")
// Handle "Search" query type
if query.QueryType == "search" {
// 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
}
response.Responses[q.RefID] = backend.DataResponse{
Frames: data.Frames{frames},
}
}
// No query type means traceID query
if query.QueryType == "" {
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
}
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)
continue
}
if len(dependencies.Errors) > 0 {
response.Responses[q.RefID] = backend.ErrorResponseWithErrorSource(backend.DownstreamError(fmt.Errorf("error while fetching dependencies, code: %v, message: %v", dependencies.Errors[0].Code, dependencies.Errors[0].Msg)))
continue
}
frames := transformDependenciesResponse(dependencies, q.RefID)
response.Responses[q.RefID] = backend.DataResponse{
Frames: frames,
}
}
}
return response, nil
}
func transformDependenciesResponse(dependencies types.DependenciesResponse, refID string) []*data.Frame {
// Create nodes frame
nodesFrame := data.NewFrame(refID+"_nodes",
data.NewField("id", nil, []string{}),
data.NewField("title", nil, []string{}),
)
nodesFrame.Meta = &data.FrameMeta{
PreferredVisualization: "nodeGraph",
}
// Create edges frame
mainStatField := data.NewField("mainstat", nil, []int64{})
mainStatField.Config = &data.FieldConfig{
DisplayName: "Call count",
}
edgesFrame := data.NewFrame(refID+"_edges",
data.NewField("id", nil, []string{}),
data.NewField("source", nil, []string{}),
data.NewField("target", nil, []string{}),
mainStatField,
)
edgesFrame.Meta = &data.FrameMeta{
PreferredVisualization: "nodeGraph",
}
// Return early if there are no dependencies
if len(dependencies.Data) == 0 {
return []*data.Frame{nodesFrame, edgesFrame}
}
// Create a map to store unique service nodes
servicesByName := make(map[string]bool)
// Process each dependency
for _, dependency := range dependencies.Data {
// Add services to the map to track unique services
servicesByName[dependency.Parent] = true
servicesByName[dependency.Child] = true
// Add edge data
edgesFrame.AppendRow(
dependency.Parent+"--"+dependency.Child,
dependency.Parent,
dependency.Child,
int64(dependency.CallCount),
)
}
// Convert map keys to slice and sort them - this is to ensure the returned nodes are in a consistent order
services := make([]string, 0, len(servicesByName))
for service := range servicesByName {
services = append(services, service)
}
sort.Strings(services)
// Add node data in sorted order
for _, service := range services {
nodesFrame.AppendRow(
service,
service,
)
}
return []*data.Frame{nodesFrame, edgesFrame}
}