| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | package tempo | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/grafana/grafana-plugin-sdk-go/backend" | 
					
						
							| 
									
										
										
										
											2023-10-13 20:13:51 +08:00
										 |  |  | 	"github.com/grafana/grafana-plugin-sdk-go/backend/tracing" | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 	"github.com/grafana/grafana-plugin-sdk-go/data" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/tsdb/tempo/kinds/dataquery" | 
					
						
							| 
									
										
										
										
											2023-07-19 00:30:29 +08:00
										 |  |  | 	"go.opentelemetry.io/collector/pdata/ptrace" | 
					
						
							| 
									
										
										
										
											2023-10-13 20:13:51 +08:00
										 |  |  | 	"go.opentelemetry.io/otel/attribute" | 
					
						
							|  |  |  | 	"go.opentelemetry.io/otel/codes" | 
					
						
							|  |  |  | 	"go.opentelemetry.io/otel/trace" | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *Service) getTrace(ctx context.Context, pCtx backend.PluginContext, query backend.DataQuery) (*backend.DataResponse, error) { | 
					
						
							| 
									
										
										
										
											2023-10-31 18:57:17 +08:00
										 |  |  | 	ctxLogger := s.logger.FromContext(ctx) | 
					
						
							|  |  |  | 	ctxLogger.Debug("Getting trace", "function", logEntrypoint()) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 	result := &backend.DataResponse{} | 
					
						
							|  |  |  | 	refID := query.RefID | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-13 20:13:51 +08:00
										 |  |  | 	ctx, span := tracing.DefaultTracer().Start(ctx, "datasource.tempo.getTrace", trace.WithAttributes( | 
					
						
							|  |  |  | 		attribute.String("queryType", query.QueryType), | 
					
						
							|  |  |  | 	)) | 
					
						
							|  |  |  | 	defer span.End() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 	model := &dataquery.TempoQuery{} | 
					
						
							|  |  |  | 	err := json.Unmarshal(query.JSON, model) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-10-31 18:57:17 +08:00
										 |  |  | 		ctxLogger.Error("Failed to unmarshall Tempo query model", "error", err, "function", logEntrypoint()) | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 		return result, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dsInfo, err := s.getDSInfo(ctx, pCtx) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-10-31 18:57:17 +08:00
										 |  |  | 		ctxLogger.Error("Failed to get datasource information", "error", err, "function", logEntrypoint()) | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-09 21:22:39 +08:00
										 |  |  | 	if model.Query == nil || *model.Query == "" { | 
					
						
							| 
									
										
										
										
											2023-10-31 18:57:17 +08:00
										 |  |  | 		err := fmt.Errorf("trace id is required") | 
					
						
							|  |  |  | 		ctxLogger.Error("Failed to validate model query", "error", err, "function", logEntrypoint()) | 
					
						
							|  |  |  | 		return result, err | 
					
						
							| 
									
										
										
										
											2023-10-09 21:22:39 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	request, err := s.createRequest(ctx, dsInfo, *model.Query, query.TimeRange.From.Unix(), query.TimeRange.To.Unix()) | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-10-31 18:57:17 +08:00
										 |  |  | 		ctxLogger.Error("Failed to create request", "error", err, "function", logEntrypoint()) | 
					
						
							| 
									
										
										
										
											2023-10-13 20:13:51 +08:00
										 |  |  | 		span.RecordError(err) | 
					
						
							|  |  |  | 		span.SetStatus(codes.Error, err.Error()) | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 		return result, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	resp, err := dsInfo.HTTPClient.Do(request) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-10-31 18:57:17 +08:00
										 |  |  | 		ctxLogger.Error("Failed to send request to Tempo", "error", err, "function", logEntrypoint()) | 
					
						
							| 
									
										
										
										
											2023-10-13 20:13:51 +08:00
										 |  |  | 		span.RecordError(err) | 
					
						
							|  |  |  | 		span.SetStatus(codes.Error, err.Error()) | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 		return result, fmt.Errorf("failed get to tempo: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer func() { | 
					
						
							|  |  |  | 		if err := resp.Body.Close(); err != nil { | 
					
						
							| 
									
										
										
										
											2023-10-31 18:57:17 +08:00
										 |  |  | 			ctxLogger.Error("Failed to close response body", "error", err, "function", logEntrypoint()) | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	body, err := io.ReadAll(resp.Body) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-10-31 18:57:17 +08:00
										 |  |  | 		ctxLogger.Error("Failed to read response body", "error", err, "function", logEntrypoint()) | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 		return &backend.DataResponse{}, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if resp.StatusCode != http.StatusOK { | 
					
						
							| 
									
										
										
										
											2023-10-31 18:57:17 +08:00
										 |  |  | 		ctxLogger.Error("Failed to get trace", "error", err, "function", logEntrypoint()) | 
					
						
							| 
									
										
										
										
											2023-11-13 21:31:19 +08:00
										 |  |  | 		result.Error = fmt.Errorf("failed to get trace with id: %s Status: %s Body: %s", *model.Query, resp.Status, string(body)) | 
					
						
							| 
									
										
										
										
											2023-10-13 20:13:51 +08:00
										 |  |  | 		span.RecordError(result.Error) | 
					
						
							|  |  |  | 		span.SetStatus(codes.Error, result.Error.Error()) | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 		return result, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-19 00:30:29 +08:00
										 |  |  | 	pbUnmarshaler := ptrace.ProtoUnmarshaler{} | 
					
						
							|  |  |  | 	otTrace, err := pbUnmarshaler.UnmarshalTraces(body) | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-10-31 18:57:17 +08:00
										 |  |  | 		ctxLogger.Error("Failed to convert tempo response to Otlp", "error", err, "function", logEntrypoint()) | 
					
						
							| 
									
										
										
										
											2023-10-13 20:13:51 +08:00
										 |  |  | 		span.RecordError(err) | 
					
						
							|  |  |  | 		span.SetStatus(codes.Error, err.Error()) | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 		return &backend.DataResponse{}, fmt.Errorf("failed to convert tempo response to Otlp: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	frame, err := TraceToFrame(otTrace) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-10-31 18:57:17 +08:00
										 |  |  | 		ctxLogger.Error("Failed to transform trace to data frame", "error", err, "function", logEntrypoint()) | 
					
						
							| 
									
										
										
										
											2023-10-13 20:13:51 +08:00
										 |  |  | 		span.RecordError(err) | 
					
						
							|  |  |  | 		span.SetStatus(codes.Error, err.Error()) | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 		return &backend.DataResponse{}, fmt.Errorf("failed to transform trace %v to data frame: %w", model.Query, err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-10-31 18:57:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 	frame.RefID = refID | 
					
						
							|  |  |  | 	frames := []*data.Frame{frame} | 
					
						
							|  |  |  | 	result.Frames = frames | 
					
						
							| 
									
										
										
										
											2023-10-31 18:57:17 +08:00
										 |  |  | 	ctxLogger.Debug("Successfully got trace", "function", logEntrypoint()) | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 	return result, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *Service) createRequest(ctx context.Context, dsInfo *Datasource, traceID string, start int64, end int64) (*http.Request, error) { | 
					
						
							| 
									
										
										
										
											2023-10-31 18:57:17 +08:00
										 |  |  | 	ctxLogger := s.logger.FromContext(ctx) | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 	var tempoQuery string | 
					
						
							| 
									
										
										
										
											2023-10-31 18:57:17 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 	if start == 0 || end == 0 { | 
					
						
							|  |  |  | 		tempoQuery = fmt.Sprintf("%s/api/traces/%s", dsInfo.URL, traceID) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		tempoQuery = fmt.Sprintf("%s/api/traces/%s?start=%d&end=%d", dsInfo.URL, traceID, start, end) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	req, err := http.NewRequestWithContext(ctx, "GET", tempoQuery, nil) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-10-31 18:57:17 +08:00
										 |  |  | 		ctxLogger.Error("Failed to create request", "error", err, "function", logEntrypoint()) | 
					
						
							| 
									
										
										
										
											2023-07-14 22:10:46 +08:00
										 |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	req.Header.Set("Accept", "application/protobuf") | 
					
						
							|  |  |  | 	return req, nil | 
					
						
							|  |  |  | } |