Pyroscope: Annotation support for series queries (#104130)
* Pyroscope: Add annotations frame to series response * Adapt to API change, add tests * Run make lint-go * Fix conflicts after rebase * Add annotation via a separate data frame * Process annotations fully at the datasource * Add mod owner for go-humanize * Pyroscope: Annotations in Query Response can be optional --------- Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com>
This commit is contained in:
co-authored by
Piotr Jamróz
parent
ea0e49a6e6
commit
0b8252fd7c
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/live"
|
||||
"github.com/grafana/grafana/pkg/tsdb/grafana-pyroscope-datasource/kinds/dataquery"
|
||||
typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
|
||||
"github.com/xlab/treeprint"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
@@ -94,7 +95,15 @@ func (d *PyroscopeDatasource) query(ctx context.Context, pCtx backend.PluginCont
|
||||
}
|
||||
// add the frames to the response.
|
||||
responseMutex.Lock()
|
||||
response.Frames = append(response.Frames, seriesToDataFrames(seriesResp)...)
|
||||
withAnnotations := qm.Annotations != nil && *qm.Annotations
|
||||
frames, err := seriesToDataFrames(seriesResp, withAnnotations)
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
logger.Error("Querying SelectSeries()", "err", err, "function", logEntrypoint())
|
||||
return err
|
||||
}
|
||||
response.Frames = append(response.Frames, frames...)
|
||||
responseMutex.Unlock()
|
||||
return nil
|
||||
})
|
||||
@@ -411,8 +420,22 @@ func walkTree(tree *ProfileTree, fn func(tree *ProfileTree)) {
|
||||
}
|
||||
}
|
||||
|
||||
func seriesToDataFrames(resp *SeriesResponse) []*data.Frame {
|
||||
type TimedAnnotation struct {
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Annotation *typesv1.ProfileAnnotation `json:"annotation"`
|
||||
}
|
||||
|
||||
func (ta *TimedAnnotation) getKey() string {
|
||||
return ta.Annotation.Key
|
||||
}
|
||||
|
||||
func (ta *TimedAnnotation) getValue() string {
|
||||
return ta.Annotation.Value
|
||||
}
|
||||
|
||||
func seriesToDataFrames(resp *SeriesResponse, withAnnotations bool) ([]*data.Frame, error) {
|
||||
frames := make([]*data.Frame, 0, len(resp.Series))
|
||||
annotations := make([]*TimedAnnotation, 0)
|
||||
|
||||
for _, series := range resp.Series {
|
||||
// We create separate data frames as the series may not have the same length
|
||||
@@ -430,15 +453,32 @@ func seriesToDataFrames(resp *SeriesResponse) []*data.Frame {
|
||||
|
||||
valueField := data.NewField(resp.Label, labels, []float64{})
|
||||
valueField.Config = &data.FieldConfig{Unit: resp.Units}
|
||||
fields = append(fields, valueField)
|
||||
|
||||
for _, point := range series.Points {
|
||||
timeField.Append(time.UnixMilli(point.Timestamp))
|
||||
valueField.Append(point.Value)
|
||||
if withAnnotations {
|
||||
for _, a := range point.Annotations {
|
||||
annotations = append(annotations, &TimedAnnotation{
|
||||
Timestamp: point.Timestamp,
|
||||
Annotation: a,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fields = append(fields, valueField)
|
||||
frame.Fields = fields
|
||||
frames = append(frames, frame)
|
||||
}
|
||||
return frames
|
||||
|
||||
if len(annotations) > 0 {
|
||||
frame, err := createAnnotationFrame(annotations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
frames = append(frames, frame)
|
||||
}
|
||||
|
||||
return frames, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user