github.com/kiali/kiali@v1.84.0/tracing/jaeger/grpc_client.go (about)

     1  package jaeger
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"strings"
     8  	"time"
     9  
    10  	"google.golang.org/grpc"
    11  	"google.golang.org/grpc/codes"
    12  	"google.golang.org/grpc/status"
    13  	"google.golang.org/protobuf/types/known/durationpb"
    14  	"google.golang.org/protobuf/types/known/timestamppb"
    15  
    16  	"github.com/kiali/kiali/config"
    17  	"github.com/kiali/kiali/log"
    18  	"github.com/kiali/kiali/models"
    19  	"github.com/kiali/kiali/tracing/jaeger/model"
    20  	jsonConv "github.com/kiali/kiali/tracing/jaeger/model/converter/json"
    21  	jsonModel "github.com/kiali/kiali/tracing/jaeger/model/json"
    22  	"github.com/kiali/kiali/util"
    23  )
    24  
    25  type JaegerGRPCClient struct {
    26  	JaegergRPCClient model.QueryServiceClient
    27  	IgnoreCluster    bool
    28  }
    29  
    30  func NewGRPCJaegerClient(cc model.QueryServiceClient) (jaegerClient *JaegerGRPCClient, err error) {
    31  
    32  	conf := config.Get()
    33  	ctx := context.Background()
    34  
    35  	var jaegerService string
    36  	var ignoreCluster bool
    37  
    38  	if err == nil {
    39  		var services *model.GetServicesResponse
    40  		services, err = cc.GetServices(ctx, &model.GetServicesRequest{})
    41  		if err != nil {
    42  			log.Errorf("Error getting services")
    43  		} else {
    44  			for _, service := range services.Services {
    45  				if !strings.Contains(service, "istio") && !strings.Contains(service, "jaeger") {
    46  					jaegerService = service
    47  					break
    48  				}
    49  			}
    50  			end := time.Now()
    51  			tags := map[string]string{
    52  				"cluster": conf.KubernetesConfig.ClusterName,
    53  			}
    54  			findTracesRQ := &model.FindTracesRequest{
    55  				Query: &model.TraceQueryParameters{
    56  					ServiceName:  jaegerService,
    57  					StartTimeMin: timestamppb.New(end.Add(-10 * time.Minute)),
    58  					StartTimeMax: timestamppb.New(end),
    59  					Tags:         tags,
    60  					DurationMin:  durationpb.New(0),
    61  					SearchDepth:  int32(10),
    62  				},
    63  			}
    64  			stream, err := cc.FindTraces(context.TODO(), findTracesRQ)
    65  			if err != nil {
    66  				err = fmt.Errorf("GetAppTraces, Tracing GRPC client error: %v", err)
    67  				return nil, err
    68  			}
    69  
    70  			tracesMap, err := readSpansStream(stream)
    71  			if tracesMap != nil && err == nil && len(tracesMap) == 0 || err != nil {
    72  				log.Debugf("Error getting query for tracing. cluster tags will be disabled.")
    73  				ignoreCluster = true
    74  			} else {
    75  				ignoreCluster = false
    76  			}
    77  			return &JaegerGRPCClient{JaegergRPCClient: cc, IgnoreCluster: ignoreCluster}, nil
    78  		}
    79  	}
    80  	return nil, err
    81  }
    82  
    83  // FindTraces
    84  func (jc JaegerGRPCClient) FindTraces(ctx context.Context, serviceName string, q models.TracingQuery) (response *model.TracingResponse, err error) {
    85  	jaegerServiceName := serviceName
    86  	r := model.TracingResponse{
    87  		Data:               []jsonModel.Trace{},
    88  		TracingServiceName: jaegerServiceName,
    89  	}
    90  
    91  	var tags = util.CopyStringMap(q.Tags)
    92  	if jc.IgnoreCluster {
    93  		delete(tags, "cluster")
    94  	}
    95  
    96  	findTracesRQ := &model.FindTracesRequest{
    97  		Query: &model.TraceQueryParameters{
    98  			ServiceName:  jaegerServiceName,
    99  			StartTimeMin: timestamppb.New(q.Start),
   100  			StartTimeMax: timestamppb.New(q.End),
   101  			Tags:         tags,
   102  			DurationMin:  durationpb.New(q.MinDuration),
   103  			SearchDepth:  int32(q.Limit),
   104  		},
   105  	}
   106  
   107  	tracesMap, err := jc.queryTraces(ctx, findTracesRQ)
   108  	if jc.IgnoreCluster {
   109  		r.FromAllClusters = true
   110  	}
   111  
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	for _, t := range tracesMap {
   117  		converted := jsonConv.FromDomain(t)
   118  		r.Data = append(r.Data, *converted)
   119  	}
   120  
   121  	return &r, nil
   122  }
   123  
   124  func (jc JaegerGRPCClient) GetTrace(ctx context.Context, strTraceID string) (*model.TracingSingleTrace, error) {
   125  
   126  	traceID, err := model.TraceIDFromString(strTraceID)
   127  	if err != nil {
   128  		return nil, fmt.Errorf("GetTraceDetail, invalid trace ID: %v", err)
   129  	}
   130  	bTraceId := make([]byte, 16)
   131  	_, err = traceID.MarshalTo(bTraceId)
   132  	if err != nil {
   133  		return nil, fmt.Errorf("GetTraceDetail, invalid marshall: %v", err)
   134  	}
   135  	getTraceRQ := &model.GetTraceRequest{TraceId: bTraceId}
   136  
   137  	ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
   138  	defer cancel()
   139  
   140  	stream, err := jc.JaegergRPCClient.GetTrace(ctx, getTraceRQ)
   141  	if err != nil {
   142  		return nil, fmt.Errorf("GetTraceDetail, Tracing GRPC client error: %v", err)
   143  	}
   144  	tracesMap, err := readSpansStream(stream)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  	if trace, ok := tracesMap[traceID]; ok {
   149  		converted := jsonConv.FromDomain(trace)
   150  		return &model.TracingSingleTrace{Data: *converted}, nil
   151  	}
   152  	// Not found
   153  	return nil, nil
   154  }
   155  
   156  // GetServices
   157  func (jc JaegerGRPCClient) GetServices(ctxSrv context.Context) (bool, error) {
   158  	ctx, cancel := context.WithTimeout(ctxSrv, 4*time.Second)
   159  	defer cancel()
   160  
   161  	_, err := jc.JaegergRPCClient.GetServices(ctx, &model.GetServicesRequest{})
   162  	return err == nil, err
   163  }
   164  
   165  // query traces
   166  func (jc JaegerGRPCClient) queryTraces(ctx context.Context, findTracesRQ *model.FindTracesRequest) (map[model.TraceID]*model.Trace, error) {
   167  	ctx, cancel := context.WithTimeout(ctx, time.Duration(config.Get().ExternalServices.Tracing.QueryTimeout)*time.Second)
   168  	defer cancel()
   169  
   170  	stream, err := jc.JaegergRPCClient.FindTraces(ctx, findTracesRQ)
   171  	if err != nil {
   172  		err = fmt.Errorf("GetAppTraces, Tracing GRPC client error: %v", err)
   173  		log.Error(err.Error())
   174  		return nil, err
   175  	}
   176  
   177  	tracesMap, err := readSpansStream(stream)
   178  
   179  	return tracesMap, err
   180  }
   181  
   182  type SpansStreamer interface {
   183  	Recv() (*model.SpansResponseChunk, error)
   184  	grpc.ClientStream
   185  }
   186  
   187  func readSpansStream(stream SpansStreamer) (map[model.TraceID]*model.Trace, error) {
   188  	tracesMap := make(map[model.TraceID]*model.Trace)
   189  	for received, err := stream.Recv(); err != io.EOF; received, err = stream.Recv() {
   190  		if err != nil {
   191  			if status.Code(err) == codes.DeadlineExceeded {
   192  				log.Trace("Tracing GRPC client timeout")
   193  				break
   194  			}
   195  			log.Errorf("jaeger GRPC client, stream error: %v", err)
   196  			return nil, fmt.Errorf("Tracing GRPC client, stream error: %v", err)
   197  		}
   198  		for i, span := range received.Spans {
   199  			traceId := model.TraceID{}
   200  			err := traceId.Unmarshal(span.TraceId)
   201  			if err != nil {
   202  				log.Errorf("Tracing TraceId unmarshall error: %v", err)
   203  				continue
   204  			}
   205  			if trace, ok := tracesMap[traceId]; ok {
   206  				trace.Spans = append(trace.Spans, received.Spans[i])
   207  			} else {
   208  				tracesMap[traceId] = &model.Trace{
   209  					Spans: []*model.Span{received.Spans[i]},
   210  				}
   211  			}
   212  		}
   213  	}
   214  	return tracesMap, nil
   215  }