mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Pyroscope: Improve label suggestions in query editor (#78861)
* Send sanitized selectors to the Pyroscope backend for LabelNames and LabelValues
* Clean LabelNames response to remove already used labels
* Improve performance after major changes
* Fix import order
* Further improve rendering performance
* Fix frontend tests
* Fix fake pyroscope client signature
* Bump pyroscope/api dependency to include start/end in LabelNames/LabelValues
* Fix issue with old queries running when using the run button
* Add generated file
* Make code more readable, add a few comments
* Format with prettier
* Fix error when assigning data
* Revert "Add generated file"
This reverts commit c4f33727b8.
* Remove leftover code
* Simplify query editor internal state objects
* Move label selector validation up, improve label filtering
* Simplify query editor state, switch to debounce to reduce rerenders
* Revert cosmetic change
This commit is contained in:
committed by
GitHub
parent
2d66d0de61
commit
1c53561521
8
go.mod
8
go.mod
@@ -91,7 +91,7 @@ require (
|
|||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/prometheus/alertmanager v0.25.0 // @grafana/alerting-squad-backend
|
github.com/prometheus/alertmanager v0.25.0 // @grafana/alerting-squad-backend
|
||||||
github.com/prometheus/client_golang v1.17.0 // @grafana/alerting-squad-backend
|
github.com/prometheus/client_golang v1.17.0 // @grafana/alerting-squad-backend
|
||||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // @grafana/backend-platform
|
github.com/prometheus/client_model v0.5.0 // @grafana/backend-platform
|
||||||
github.com/prometheus/common v0.45.0 // @grafana/alerting-squad-backend
|
github.com/prometheus/common v0.45.0 // @grafana/alerting-squad-backend
|
||||||
github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 // @grafana/alerting-squad-backend
|
github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 // @grafana/alerting-squad-backend
|
||||||
github.com/robfig/cron/v3 v3.0.1 // @grafana/backend-platform
|
github.com/robfig/cron/v3 v3.0.1 // @grafana/backend-platform
|
||||||
@@ -222,7 +222,7 @@ require (
|
|||||||
golang.org/x/text v0.14.0 // @grafana/backend-platform
|
golang.org/x/text v0.14.0 // @grafana/backend-platform
|
||||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect; @grafana/backend-platform
|
google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a // indirect; @grafana/backend-platform
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -290,7 +290,7 @@ require (
|
|||||||
|
|
||||||
require github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 // @grafana/sharing-squad
|
require github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 // @grafana/sharing-squad
|
||||||
|
|
||||||
require github.com/grafana/pyroscope/api v0.2.1 // @grafana/observability-traces-and-profiling
|
require github.com/grafana/pyroscope/api v0.3.0 // @grafana/observability-traces-and-profiling
|
||||||
|
|
||||||
require github.com/apache/arrow/go/v13 v13.0.0 // @grafana/observability-metrics
|
require github.com/apache/arrow/go/v13 v13.0.0 // @grafana/observability-metrics
|
||||||
|
|
||||||
@@ -409,7 +409,7 @@ require (
|
|||||||
go.uber.org/zap v1.24.0 // indirect
|
go.uber.org/zap v1.24.0 // indirect
|
||||||
golang.org/x/term v0.15.0 // indirect
|
golang.org/x/term v0.15.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
||||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
|
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
|
|||||||
15
go.sum
15
go.sum
@@ -1820,8 +1820,8 @@ github.com/grafana/kindsys v0.0.0-20230508162304-452481b63482 h1:1YNoeIhii4UIIQp
|
|||||||
github.com/grafana/kindsys v0.0.0-20230508162304-452481b63482/go.mod h1:GNcfpy5+SY6RVbNGQW264gC0r336Dm+0zgQ5vt6+M8Y=
|
github.com/grafana/kindsys v0.0.0-20230508162304-452481b63482/go.mod h1:GNcfpy5+SY6RVbNGQW264gC0r336Dm+0zgQ5vt6+M8Y=
|
||||||
github.com/grafana/prometheus-alertmanager v0.25.1-0.20231027171310-70c52bf65758 h1:ATUhvJSJwzdzhnmzUI92fxVFqyqmcnzJ47wtHTK3LW4=
|
github.com/grafana/prometheus-alertmanager v0.25.1-0.20231027171310-70c52bf65758 h1:ATUhvJSJwzdzhnmzUI92fxVFqyqmcnzJ47wtHTK3LW4=
|
||||||
github.com/grafana/prometheus-alertmanager v0.25.1-0.20231027171310-70c52bf65758/go.mod h1:MmLemcsGjpbOwEeT3k7K+gnvIImXgkatCfVX6sOtx80=
|
github.com/grafana/prometheus-alertmanager v0.25.1-0.20231027171310-70c52bf65758/go.mod h1:MmLemcsGjpbOwEeT3k7K+gnvIImXgkatCfVX6sOtx80=
|
||||||
github.com/grafana/pyroscope/api v0.2.1 h1:V/GSrwSN5HgA4Ijf/2SN9Sib55E/xObswaCMkdOOsxs=
|
github.com/grafana/pyroscope/api v0.3.0 h1:WcVKNZ8JlriJnD28wTkZray0wGo8dGkizSJXnbG7Gd8=
|
||||||
github.com/grafana/pyroscope/api v0.2.1/go.mod h1:vNO/Rym3pwNIN4y/f0ACrk5iR7DdWlsdfZGSZE+XChU=
|
github.com/grafana/pyroscope/api v0.3.0/go.mod h1:JggA80ToAAUACYGfwL49XoFk5aN5ecHp4pNIZhlk9Uc=
|
||||||
github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A=
|
github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A=
|
||||||
github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db h1:7aN5cccjIqCLTzedH7MZzRZt5/lsAHch6Z3L2ZGn5FA=
|
github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db h1:7aN5cccjIqCLTzedH7MZzRZt5/lsAHch6Z3L2ZGn5FA=
|
||||||
github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A=
|
github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A=
|
||||||
@@ -2557,8 +2557,9 @@ github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T
|
|||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||||
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
|
|
||||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||||
|
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||||
|
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
@@ -3970,12 +3971,12 @@ google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoR
|
|||||||
google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
|
google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
|
||||||
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
|
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
|
||||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
|
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
|
||||||
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0=
|
google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a h1:fwgW9j3vHirt4ObdHoYNwuO24BEZjSzbh+zPaNWoiY8=
|
||||||
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
|
google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:EMfReVxb80Dq1hhioy0sOsY9jCE46YDgHlJ7fWVUWRE=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU=
|
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0=
|
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a h1:a2MQQVoTo96JC9PMGtGBymLp7+/RzpFc2yX/9WfFg1c=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc=
|
||||||
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
@@ -14,6 +16,8 @@ import (
|
|||||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||||
"github.com/grafana/grafana/pkg/infra/httpclient"
|
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
|
"github.com/prometheus/prometheus/model/labels"
|
||||||
|
"github.com/prometheus/prometheus/promql/parser"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
@@ -27,8 +31,8 @@ var (
|
|||||||
|
|
||||||
type ProfilingClient interface {
|
type ProfilingClient interface {
|
||||||
ProfileTypes(context.Context) ([]*ProfileType, error)
|
ProfileTypes(context.Context) ([]*ProfileType, error)
|
||||||
LabelNames(ctx context.Context) ([]string, error)
|
LabelNames(ctx context.Context, labelSelector string, start int64, end int64) ([]string, error)
|
||||||
LabelValues(ctx context.Context, label string) ([]string, error)
|
LabelValues(ctx context.Context, label string, labelSelector string, start int64, end int64) ([]string, error)
|
||||||
GetSeries(ctx context.Context, profileTypeID string, labelSelector string, start int64, end int64, groupBy []string, step float64) (*SeriesResponse, error)
|
GetSeries(ctx context.Context, profileTypeID string, labelSelector string, start int64, end int64, groupBy []string, step float64) (*SeriesResponse, error)
|
||||||
GetProfile(ctx context.Context, profileTypeID string, labelSelector string, start int64, end int64, maxNodes *int64) (*ProfileResponse, error)
|
GetProfile(ctx context.Context, profileTypeID string, labelSelector string, start int64, end int64, maxNodes *int64) (*ProfileResponse, error)
|
||||||
GetSpanProfile(ctx context.Context, profileTypeID string, labelSelector string, spanSelector []string, start int64, end int64, maxNodes *int64) (*ProfileResponse, error)
|
GetSpanProfile(ctx context.Context, profileTypeID string, labelSelector string, spanSelector []string, start int64, end int64, maxNodes *int64) (*ProfileResponse, error)
|
||||||
@@ -105,17 +109,45 @@ func (d *PyroscopeDatasource) profileTypes(ctx context.Context, req *backend.Cal
|
|||||||
|
|
||||||
func (d *PyroscopeDatasource) labelNames(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
func (d *PyroscopeDatasource) labelNames(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||||
ctxLogger := logger.FromContext(ctx)
|
ctxLogger := logger.FromContext(ctx)
|
||||||
res, err := d.client.LabelNames(ctx)
|
|
||||||
|
u, err := url.Parse(req.URL)
|
||||||
|
if err != nil {
|
||||||
|
ctxLogger.Error("Failed to parse URL", "error", err, "function", logEntrypoint())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
query := u.Query()
|
||||||
|
|
||||||
|
start, _ := strconv.ParseInt(query.Get("start"), 10, 64)
|
||||||
|
end, _ := strconv.ParseInt(query.Get("end"), 10, 64)
|
||||||
|
labelSelector := query.Get("query")
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
labelNames, err := d.client.LabelNames(ctx, labelSelector, start, end)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctxLogger.Error("Received error from client", "error", err, "function", logEntrypoint())
|
ctxLogger.Error("Received error from client", "error", err, "function", logEntrypoint())
|
||||||
return fmt.Errorf("error calling LabelNames: %v", err)
|
return fmt.Errorf("error calling LabelNames: %v", err)
|
||||||
}
|
}
|
||||||
data, err := json.Marshal(res)
|
|
||||||
|
finalLabels := make([]string, 0)
|
||||||
|
for _, label := range labelNames {
|
||||||
|
if slices.ContainsFunc(matchers, func(m *labels.Matcher) bool {
|
||||||
|
return m.Name == label
|
||||||
|
}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
finalLabels = append(finalLabels, label)
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonResponse, err := json.Marshal(finalLabels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctxLogger.Error("Failed to marshal response", "error", err, "function", logEntrypoint())
|
ctxLogger.Error("Failed to marshal response", "error", err, "function", logEntrypoint())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = sender.Send(&backend.CallResourceResponse{Body: data, Headers: req.Headers, Status: 200})
|
err = sender.Send(&backend.CallResourceResponse{Body: jsonResponse, Headers: req.Headers, Status: 200})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctxLogger.Error("Failed to send response", "error", err, "function", logEntrypoint())
|
ctxLogger.Error("Failed to send response", "error", err, "function", logEntrypoint())
|
||||||
return err
|
return err
|
||||||
@@ -139,7 +171,11 @@ func (d *PyroscopeDatasource) labelValues(ctx context.Context, req *backend.Call
|
|||||||
}
|
}
|
||||||
query := u.Query()
|
query := u.Query()
|
||||||
|
|
||||||
res, err := d.client.LabelValues(ctx, query["label"][0])
|
start, _ := strconv.ParseInt(query.Get("start"), 10, 64)
|
||||||
|
end, _ := strconv.ParseInt(query.Get("end"), 10, 64)
|
||||||
|
label := query.Get("label")
|
||||||
|
|
||||||
|
res, err := d.client.LabelValues(ctx, label, query.Get("query"), start, end)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctxLogger.Error("Received error from client", "error", err, "function", logEntrypoint())
|
ctxLogger.Error("Received error from client", "error", err, "function", logEntrypoint())
|
||||||
return fmt.Errorf("error calling LabelValues: %v", err)
|
return fmt.Errorf("error calling LabelValues: %v", err)
|
||||||
|
|||||||
@@ -238,10 +238,14 @@ func getUnits(profileTypeID string) string {
|
|||||||
return unit
|
return unit
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PyroscopeClient) LabelNames(ctx context.Context) ([]string, error) {
|
func (c *PyroscopeClient) LabelNames(ctx context.Context, labelSelector string, start int64, end int64) ([]string, error) {
|
||||||
ctx, span := tracing.DefaultTracer().Start(ctx, "datasource.pyroscope.LabelNames")
|
ctx, span := tracing.DefaultTracer().Start(ctx, "datasource.pyroscope.LabelNames")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
resp, err := c.connectClient.LabelNames(ctx, connect.NewRequest(&typesv1.LabelNamesRequest{}))
|
resp, err := c.connectClient.LabelNames(ctx, connect.NewRequest(&typesv1.LabelNamesRequest{
|
||||||
|
Matchers: []string{labelSelector},
|
||||||
|
Start: start,
|
||||||
|
End: end,
|
||||||
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Received error from client", "error", err, "function", logEntrypoint())
|
logger.Error("Received error from client", "error", err, "function", logEntrypoint())
|
||||||
span.RecordError(err)
|
span.RecordError(err)
|
||||||
@@ -259,10 +263,15 @@ func (c *PyroscopeClient) LabelNames(ctx context.Context) ([]string, error) {
|
|||||||
return filtered, nil
|
return filtered, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PyroscopeClient) LabelValues(ctx context.Context, label string) ([]string, error) {
|
func (c *PyroscopeClient) LabelValues(ctx context.Context, label string, labelSelector string, start int64, end int64) ([]string, error) {
|
||||||
ctx, span := tracing.DefaultTracer().Start(ctx, "datasource.pyroscope.LabelValues")
|
ctx, span := tracing.DefaultTracer().Start(ctx, "datasource.pyroscope.LabelValues")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
resp, err := c.connectClient.LabelValues(ctx, connect.NewRequest(&typesv1.LabelValuesRequest{Name: label}))
|
resp, err := c.connectClient.LabelValues(ctx, connect.NewRequest(&typesv1.LabelValuesRequest{
|
||||||
|
Name: label,
|
||||||
|
Matchers: []string{labelSelector},
|
||||||
|
Start: start,
|
||||||
|
End: end,
|
||||||
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Received error from client", "error", err, "function", logEntrypoint())
|
logger.Error("Received error from client", "error", err, "function", logEntrypoint())
|
||||||
span.RecordError(err)
|
span.RecordError(err)
|
||||||
|
|||||||
@@ -288,11 +288,11 @@ func (f *FakeClient) ProfileTypes(ctx context.Context) ([]*ProfileType, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeClient) LabelValues(ctx context.Context, label string) ([]string, error) {
|
func (f *FakeClient) LabelValues(ctx context.Context, label string, labelSelector string, start int64, end int64) ([]string, error) {
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeClient) LabelNames(ctx context.Context) ([]string, error) {
|
func (f *FakeClient) LabelNames(ctx context.Context, labelSelector string, start int64, end int64) ([]string, error) {
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function LabelsEditor(props: Props) {
|
|||||||
<CodeEditor
|
<CodeEditor
|
||||||
value={props.value}
|
value={props.value}
|
||||||
language={langId}
|
language={langId}
|
||||||
onBlur={props.onChange}
|
onChange={props.onChange}
|
||||||
containerStyles={styles.queryField}
|
containerStyles={styles.queryField}
|
||||||
monacoOptions={{
|
monacoOptions={{
|
||||||
folding: false,
|
folding: false,
|
||||||
|
|||||||
@@ -111,6 +111,8 @@ function setupDs() {
|
|||||||
},
|
},
|
||||||
] as ProfileTypeMessage[]);
|
] as ProfileTypeMessage[]);
|
||||||
|
|
||||||
|
ds.getLabelNames = jest.fn().mockResolvedValue(['label_one']);
|
||||||
|
|
||||||
return ds;
|
return ds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import deepEqual from 'fast-deep-equal';
|
import deepEqual from 'fast-deep-equal';
|
||||||
import React, { useCallback, useEffect } from 'react';
|
import { debounce } from 'lodash';
|
||||||
import { useAsync } from 'react-use';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { CoreApp, QueryEditorProps, TimeRange } from '@grafana/data';
|
import { CoreApp, QueryEditorProps, TimeRange } from '@grafana/data';
|
||||||
import { LoadingPlaceholder } from '@grafana/ui';
|
import { LoadingPlaceholder } from '@grafana/ui';
|
||||||
|
|
||||||
import { normalizeQuery, PyroscopeDataSource } from '../datasource';
|
import { normalizeQuery, PyroscopeDataSource } from '../datasource';
|
||||||
import { PyroscopeDataSourceOptions, ProfileTypeMessage, Query } from '../types';
|
import { ProfileTypeMessage, PyroscopeDataSourceOptions, Query } from '../types';
|
||||||
|
|
||||||
import { EditorRow } from './EditorRow';
|
import { EditorRow } from './EditorRow';
|
||||||
import { EditorRows } from './EditorRows';
|
import { EditorRows } from './EditorRows';
|
||||||
@@ -17,6 +17,8 @@ import { QueryOptions } from './QueryOptions';
|
|||||||
|
|
||||||
export type Props = QueryEditorProps<PyroscopeDataSource, Query, PyroscopeDataSourceOptions>;
|
export type Props = QueryEditorProps<PyroscopeDataSource, Query, PyroscopeDataSourceOptions>;
|
||||||
|
|
||||||
|
const labelSelectorRegex = /(\w+)\s*=\s*("[^,"]+")/g;
|
||||||
|
|
||||||
export function QueryEditor(props: Props) {
|
export function QueryEditor(props: Props) {
|
||||||
const { onChange, onRunQuery, datasource, query, range, app } = props;
|
const { onChange, onRunQuery, datasource, query, range, app } = props;
|
||||||
|
|
||||||
@@ -115,33 +117,62 @@ function useLabels(
|
|||||||
// Round to nearest 5 seconds. If the range is something like last 1h then every render the range values change slightly
|
// Round to nearest 5 seconds. If the range is something like last 1h then every render the range values change slightly
|
||||||
// and what ever has range as dependency is rerun. So this effectively debounces the queries.
|
// and what ever has range as dependency is rerun. So this effectively debounces the queries.
|
||||||
const unpreciseRange = {
|
const unpreciseRange = {
|
||||||
to: Math.ceil((range?.to.valueOf() || 0) / 5000) * 5000,
|
to: Math.ceil((range?.to.valueOf() || 0) / 10000) * 10000,
|
||||||
from: Math.floor((range?.from.valueOf() || 0) / 5000) * 5000,
|
from: Math.floor((range?.from.valueOf() || 0) / 10000) * 10000,
|
||||||
};
|
};
|
||||||
|
|
||||||
const labelsResult = useAsync(() => {
|
// Transforms user input into a valid label selector including the profile type.
|
||||||
return datasource.getLabelNames(query.profileTypeId + query.labelSelector, unpreciseRange.from, unpreciseRange.to);
|
// It can optionally remove a label, used to support editing existing label values.
|
||||||
}, [datasource, query.profileTypeId, query.labelSelector, unpreciseRange.to, unpreciseRange.from]);
|
const createSelector = (rawInput: string, profileTypeId: string, labelToRemove: string): string => {
|
||||||
|
let labels: string[] = [`__profile_type__=\"${profileTypeId}\"`];
|
||||||
|
let match;
|
||||||
|
while ((match = labelSelectorRegex.exec(rawInput)) !== null) {
|
||||||
|
if (match[1] && match[2]) {
|
||||||
|
if (match[1] === labelToRemove) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
labels.push(`${match[1]}=${match[2]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `{${labels.join(',')}}`;
|
||||||
|
};
|
||||||
|
|
||||||
// Create a function with range and query already baked in so we don't have to send those everywhere
|
const [labels, setLabels] = useState(() => ['']);
|
||||||
const getLabelValues = useCallback(
|
|
||||||
(label: string) => {
|
useEffect(() => {
|
||||||
return datasource.getLabelValues(
|
const fetchData = async () => {
|
||||||
query.profileTypeId + query.labelSelector,
|
const labels = await datasource.getLabelNames(
|
||||||
label,
|
createSelector(query.labelSelector, query.profileTypeId, ''),
|
||||||
unpreciseRange.from,
|
unpreciseRange.from,
|
||||||
unpreciseRange.to
|
unpreciseRange.to
|
||||||
);
|
);
|
||||||
|
|
||||||
|
setLabels(labels);
|
||||||
|
};
|
||||||
|
fetchData();
|
||||||
|
}, [query, unpreciseRange.from, unpreciseRange.to, datasource, setLabels]);
|
||||||
|
|
||||||
|
// Create a function with range and query already baked in, so we don't have to send those everywhere
|
||||||
|
const getLabelValues = useCallback(
|
||||||
|
(label: string) => {
|
||||||
|
let labelSelector = createSelector(query.labelSelector, query.profileTypeId, label);
|
||||||
|
return datasource.getLabelValues(labelSelector, label, unpreciseRange.from, unpreciseRange.to);
|
||||||
},
|
},
|
||||||
[query, datasource, unpreciseRange.to, unpreciseRange.from]
|
[datasource, query.labelSelector, query.profileTypeId, unpreciseRange.to, unpreciseRange.from]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onChangeDebounced = debounce((value: string) => {
|
||||||
|
if (onChange) {
|
||||||
|
onChange({ ...query, labelSelector: value });
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
|
||||||
const onLabelSelectorChange = useCallback(
|
const onLabelSelectorChange = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
onChange({ ...query, labelSelector: value });
|
onChangeDebounced(value);
|
||||||
},
|
},
|
||||||
[onChange, query]
|
[onChangeDebounced]
|
||||||
);
|
);
|
||||||
|
|
||||||
return { labels: labelsResult.value, getLabelValues, onLabelSelectorChange };
|
return { labels, getLabelValues, onLabelSelectorChange };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,13 +86,15 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP
|
|||||||
});
|
});
|
||||||
case 'IN_LABEL_VALUE':
|
case 'IN_LABEL_VALUE':
|
||||||
let values = await this.getLabelValues(situation.labelName);
|
let values = await this.getLabelValues(situation.labelName);
|
||||||
return values.map((key) => {
|
return values
|
||||||
return {
|
? values.map((key) => {
|
||||||
label: key,
|
return {
|
||||||
insertText: situation.betweenQuotes ? key : `"${key}"`,
|
label: key,
|
||||||
type: 'LABEL_VALUE',
|
insertText: situation.betweenQuotes ? key : `"${key}"`,
|
||||||
};
|
type: 'LABEL_VALUE',
|
||||||
});
|
};
|
||||||
|
})
|
||||||
|
: [];
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unexpected situation ${situation}`);
|
throw new Error(`Unexpected situation ${situation}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { languages } from 'monaco-editor';
|
|||||||
|
|
||||||
export const languageConfiguration: languages.LanguageConfiguration = {
|
export const languageConfiguration: languages.LanguageConfiguration = {
|
||||||
// the default separators except `@$`
|
// the default separators except `@$`
|
||||||
wordPattern: /(-?\d*\.\d\w*)|([^`~!#%^&*()\-=+\[{\]}\\|;:'",.<>\/?\s]+)/g,
|
wordPattern: /(-?\d*\.\d\w*)|([^`~!#%^&*()=+\[{\]}\\|;:'",<>\/?\s]+)/g,
|
||||||
brackets: [['{', '}']],
|
brackets: [['{', '}']],
|
||||||
autoClosingPairs: [
|
autoClosingPairs: [
|
||||||
{ open: '{', close: '}' },
|
{ open: '{', close: '}' },
|
||||||
|
|||||||
Reference in New Issue
Block a user