From 7c7c9490fbc684445024c3293cdc4c43f45c673d Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Tue, 21 Oct 2025 10:00:59 +0200 Subject: [PATCH] Pyroscope: Add error source (#112645) * set downstream errors in pyroscope * Update pkg/tsdb/grafana-pyroscope-datasource/instance.go Co-authored-by: Gareth * Update pkg/tsdb/grafana-pyroscope-datasource/instance.go Co-authored-by: Gareth * more error source --------- Co-authored-by: Gareth --- .../grafana-pyroscope-datasource/instance.go | 26 +++++++++---------- .../pyroscopeClient.go | 19 +++++++------- .../grafana-pyroscope-datasource/query.go | 2 +- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/pkg/tsdb/grafana-pyroscope-datasource/instance.go b/pkg/tsdb/grafana-pyroscope-datasource/instance.go index b0e964d4d18..e2fe25c94ef 100644 --- a/pkg/tsdb/grafana-pyroscope-datasource/instance.go +++ b/pkg/tsdb/grafana-pyroscope-datasource/instance.go @@ -93,7 +93,7 @@ func (d *PyroscopeDatasource) profileTypes(ctx context.Context, req *backend.Cal u, err := url.Parse(req.URL) if err != nil { ctxLogger.Error("Failed to parse URL", "error", err, "function", logEntrypoint()) - return err + return backend.DownstreamErrorf("URL could not be parsed: %w", err) } query := u.Query() @@ -102,13 +102,13 @@ func (d *PyroscopeDatasource) profileTypes(ctx context.Context, req *backend.Cal start, err = strconv.ParseInt(query.Get("start"), 10, 64) if err != nil { ctxLogger.Error("Failed to parse start as int", "error", err, "function", logEntrypoint()) - return err + return backend.DownstreamError(fmt.Errorf("failed to parse start as int: %w", err)) } end, err = strconv.ParseInt(query.Get("end"), 10, 64) if err != nil { ctxLogger.Error("Failed to parse end as int", "error", err, "function", logEntrypoint()) - return err + return backend.DownstreamError(fmt.Errorf("failed to parse end as int: %w", err)) } } else { // Make sure to pass a valid time range to the client as v2 will not work without it. @@ -140,7 +140,7 @@ func (d *PyroscopeDatasource) labelNames(ctx context.Context, req *backend.CallR u, err := url.Parse(req.URL) if err != nil { ctxLogger.Error("Failed to parse URL", "error", err, "function", logEntrypoint()) - return err + return backend.DownstreamError(fmt.Errorf("URL could not be parsed: %w", err)) } query := u.Query() @@ -150,13 +150,13 @@ func (d *PyroscopeDatasource) labelNames(ctx context.Context, req *backend.CallR matchers, err := parser.ParseMetricSelector(labelSelector) if err != nil { ctxLogger.Error("Could not parse label selector", "error", err, "function", logEntrypoint()) - return fmt.Errorf("failed parsing label selector: %v", err) + return backend.DownstreamError(fmt.Errorf("failed parsing label selector: %v", err)) } labelNames, err := d.client.LabelNames(ctx, labelSelector, start, end) if err != nil { ctxLogger.Error("Received error from client", "error", err, "function", logEntrypoint()) - return fmt.Errorf("error calling LabelNames: %v", err) + return backend.DownstreamError(fmt.Errorf("error calling LabelNames: %v", err)) } finalLabels := make([]string, 0) @@ -194,7 +194,7 @@ func (d *PyroscopeDatasource) labelValues(ctx context.Context, req *backend.Call u, err := url.Parse(req.URL) if err != nil { ctxLogger.Error("Failed to parse URL", "error", err, "function", logEntrypoint()) - return err + return backend.DownstreamError(fmt.Errorf("URL could not be parsed: %w", err)) } query := u.Query() @@ -205,13 +205,13 @@ func (d *PyroscopeDatasource) labelValues(ctx context.Context, req *backend.Call res, err := d.client.LabelValues(ctx, label, query.Get("query"), start, end) if err != nil { ctxLogger.Error("Received error from client", "error", err, "function", logEntrypoint()) - return fmt.Errorf("error calling LabelValues: %v", err) + return backend.DownstreamError(fmt.Errorf("error calling LabelValues: %v", err)) } data, err := json.Marshal(res) if err != nil { ctxLogger.Error("Failed to marshal response", "error", err, "function", logEntrypoint()) - return err + return backend.DownstreamErrorf("failed to marshall response: %w", err) } err = sender.Send(&backend.CallResourceResponse{Body: data, Status: 200}) @@ -299,8 +299,8 @@ func (d *PyroscopeDatasource) CheckHealth(ctx context.Context, _ *backend.CheckH }, nil } -// SubscribeStream is called when a client wants to connect to a stream. This callback -// allows sending the first message. +// SubscribeStream is called when a client wants to connect to a stream. +// This callback allows sending the first message. func (d *PyroscopeDatasource) SubscribeStream(_ context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) { logger.Debug("Subscribing stream called", "function", logEntrypoint()) @@ -314,8 +314,8 @@ func (d *PyroscopeDatasource) SubscribeStream(_ context.Context, req *backend.Su }, nil } -// RunStream is called once for any open channel. Results are shared with everyone -// subscribed to the same channel. +// RunStream is called once for any open channel. +// Results are shared with everyone subscribed to the same channel. func (d *PyroscopeDatasource) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error { ctxLogger := logger.FromContext(ctx) ctxLogger.Debug("Running stream", "path", req.Path, "function", logEntrypoint()) diff --git a/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient.go b/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient.go index e742af31d30..da5d9309130 100644 --- a/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient.go +++ b/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient.go @@ -6,6 +6,7 @@ import ( "net/http" "strings" + "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/tracing" typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" @@ -82,7 +83,7 @@ func (c *PyroscopeClient) ProfileTypes(ctx context.Context, start int64, end int logger.Error("Received error from client", "error", err, "function", logEntrypoint()) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return nil, err + return nil, backend.DownstreamError(fmt.Errorf("received error from client while getting profile types: %w", err)) } if res.Msg.ProfileTypes == nil { // Let's make sure we send at least empty array if we don't have any types @@ -117,7 +118,7 @@ func (c *PyroscopeClient) GetSeries(ctx context.Context, profileTypeID string, l logger.Error("Received error from client", "error", err, "function", logEntrypoint()) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return nil, err + return nil, backend.DownstreamError(fmt.Errorf("received error from client while getting series: %w", err)) } series := make([]*Series, len(resp.Msg.Series)) @@ -173,7 +174,7 @@ func (c *PyroscopeClient) GetProfile(ctx context.Context, profileTypeID, labelSe logger.Error("Received error from client", "error", err, "function", logEntrypoint()) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return nil, err + return nil, backend.DownstreamError(fmt.Errorf("received error from client while getting profile: %w", err)) } if resp.Msg.Flamegraph == nil { @@ -181,7 +182,7 @@ func (c *PyroscopeClient) GetProfile(ctx context.Context, profileTypeID, labelSe return nil, nil } - return profileQuery(ctx, err, span, resp.Msg.Flamegraph, profileTypeID) + return profileQuery(resp.Msg.Flamegraph, profileTypeID) } func (c *PyroscopeClient) GetSpanProfile(ctx context.Context, profileTypeID, labelSelector string, spanSelector []string, start, end int64, maxNodes *int64) (*ProfileResponse, error) { @@ -202,7 +203,7 @@ func (c *PyroscopeClient) GetSpanProfile(ctx context.Context, profileTypeID, lab if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return nil, err + return nil, backend.DownstreamError(fmt.Errorf("received error from client while getting span profile: %w", err)) } if resp.Msg.Flamegraph == nil { @@ -210,10 +211,10 @@ func (c *PyroscopeClient) GetSpanProfile(ctx context.Context, profileTypeID, lab return nil, nil } - return profileQuery(ctx, err, span, resp.Msg.Flamegraph, profileTypeID) + return profileQuery(resp.Msg.Flamegraph, profileTypeID) } -func profileQuery(ctx context.Context, err error, span trace.Span, flamegraph *querierv1.FlameGraph, profileTypeID string) (*ProfileResponse, error) { +func profileQuery(flamegraph *querierv1.FlameGraph, profileTypeID string) (*ProfileResponse, error) { levels := make([]*Level, len(flamegraph.Levels)) for i, level := range flamegraph.Levels { levels[i] = &Level{ @@ -256,7 +257,7 @@ func (c *PyroscopeClient) LabelNames(ctx context.Context, labelSelector string, logger.Error("Received error from client", "error", err, "function", logEntrypoint()) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return nil, fmt.Errorf("error sending LabelNames request %v", err) + return nil, backend.DownstreamError(fmt.Errorf("error sending LabelNames request %v", err)) } if resp.Msg.Names == nil { @@ -286,7 +287,7 @@ func (c *PyroscopeClient) LabelValues(ctx context.Context, label string, labelSe logger.Error("Received error from client", "error", err, "function", logEntrypoint()) span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return nil, err + return nil, backend.DownstreamError(fmt.Errorf("received error from client while getting label values: %w", err)) } if resp.Msg.Names == nil { return []string{}, nil diff --git a/pkg/tsdb/grafana-pyroscope-datasource/query.go b/pkg/tsdb/grafana-pyroscope-datasource/query.go index a9d3449c7ed..1acf3bcee37 100644 --- a/pkg/tsdb/grafana-pyroscope-datasource/query.go +++ b/pkg/tsdb/grafana-pyroscope-datasource/query.go @@ -542,7 +542,7 @@ func seriesToDataFrames(resp *SeriesResponse, withAnnotations bool, stepDuration if len(annotations) > 0 { frame, err := annotation.CreateAnnotationFrame(annotations) if err != nil { - return nil, err + return nil, backend.DownstreamError(fmt.Errorf("error creating annotation frame: %w", err)) } frames = append(frames, frame) }