* 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>
170 lines
5.2 KiB
Go
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}
|
|
}
|