diff --git a/pkg/tsdb/grafana-pyroscope-datasource/instance.go b/pkg/tsdb/grafana-pyroscope-datasource/instance.go index 02bec86eadb..b72001d1da7 100644 --- a/pkg/tsdb/grafana-pyroscope-datasource/instance.go +++ b/pkg/tsdb/grafana-pyroscope-datasource/instance.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "net/url" + "strconv" "time" "github.com/grafana/grafana-plugin-sdk-go/backend" @@ -27,8 +28,8 @@ var ( type ProfilingClient interface { ProfileTypes(context.Context) ([]*ProfileType, error) - LabelNames(ctx context.Context) ([]string, error) - LabelValues(ctx context.Context, label string) ([]string, error) + LabelNames(ctx context.Context, labelSelector string, start int64, end int64) ([]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) 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) @@ -105,7 +106,18 @@ func (d *PyroscopeDatasource) profileTypes(ctx context.Context, req *backend.Cal func (d *PyroscopeDatasource) labelNames(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { 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) + + res, err := d.client.LabelNames(ctx, query.Get("query"), start, end) if err != nil { ctxLogger.Error("Received error from client", "error", err, "function", logEntrypoint()) return fmt.Errorf("error calling LabelNames: %v", err) @@ -139,7 +151,11 @@ func (d *PyroscopeDatasource) labelValues(ctx context.Context, req *backend.Call } 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 { ctxLogger.Error("Received error from client", "error", err, "function", logEntrypoint()) return fmt.Errorf("error calling LabelValues: %v", err) diff --git a/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient.go b/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient.go index b7d0bc24bf2..4e36f5a9266 100644 --- a/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient.go +++ b/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient.go @@ -238,10 +238,12 @@ func getUnits(profileTypeID string) string { 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") 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}, + })) if err != nil { logger.Error("Received error from client", "error", err, "function", logEntrypoint()) span.RecordError(err) @@ -259,10 +261,13 @@ func (c *PyroscopeClient) LabelNames(ctx context.Context) ([]string, error) { 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") 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}, + })) if err != nil { logger.Error("Received error from client", "error", err, "function", logEntrypoint()) span.RecordError(err) diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/LabelsEditor.tsx b/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/LabelsEditor.tsx index 7cac836efbe..6c7cee86081 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/LabelsEditor.tsx +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/LabelsEditor.tsx @@ -32,7 +32,7 @@ export function LabelsEditor(props: Props) { { + let labels: string[] = [ + `__profile_type__=\"${query.profileTypeId}\"`, + ] + const regex = /(\w+)\s*=\s*("[^,"]+")/g; + let match; + while ((match = regex.exec(query.labelSelector)) !== null) { + if (match[1] && match[2]) { + if (match[1] === labelToRemove) { + continue; + } + labels.push(`${match[1]}=${match[2]}`); + } + } + return `{${labels.join(',')}}` + }, []); + + const labelSelector = useMemo(() => createSelector(query, ''), [query, createSelector]); + const labelsResult = useAsync(() => { - return datasource.getLabelNames(query.profileTypeId + query.labelSelector, unpreciseRange.from, unpreciseRange.to); - }, [datasource, query.profileTypeId, query.labelSelector, unpreciseRange.to, unpreciseRange.from]); + console.log(labelSelector) + const labelNames = datasource.getLabelNames( + labelSelector, unpreciseRange.from, unpreciseRange.to); + console.log(labelNames) + return labelNames + }, [datasource, query.profileTypeId, labelSelector, unpreciseRange.to, unpreciseRange.from]); + // Create a function with range and query already baked in so we don't have to send those everywhere const getLabelValues = useCallback( (label: string) => { - return datasource.getLabelValues( - query.profileTypeId + query.labelSelector, + let labelSelector = createSelector(query, label); + console.log(labelSelector) + const labelValues = datasource.getLabelValues( + labelSelector, label, unpreciseRange.from, unpreciseRange.to ); + console.log(labelValues) + return labelValues; }, - [query, datasource, unpreciseRange.to, unpreciseRange.from] + [datasource, query, createSelector, unpreciseRange.to, unpreciseRange.from] ); const onLabelSelectorChange = useCallback( diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/autocomplete.ts b/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/autocomplete.ts index 16583d4894a..688dda34f27 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/autocomplete.ts +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/autocomplete.ts @@ -86,13 +86,13 @@ export class CompletionProvider implements monacoTypes.languages.CompletionItemP }); case 'IN_LABEL_VALUE': let values = await this.getLabelValues(situation.labelName); - return values.map((key) => { + return values ? values.map((key) => { return { label: key, insertText: situation.betweenQuotes ? key : `"${key}"`, type: 'LABEL_VALUE', }; - }); + }) : []; default: throw new Error(`Unexpected situation ${situation}`); } diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/pyroscopeql/pyroscopeql.ts b/public/app/plugins/datasource/grafana-pyroscope-datasource/pyroscopeql/pyroscopeql.ts index 66c8bca7fdc..6f39f5aec5c 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/pyroscopeql/pyroscopeql.ts +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/pyroscopeql/pyroscopeql.ts @@ -2,7 +2,7 @@ import type { languages } from 'monaco-editor'; export const languageConfiguration: languages.LanguageConfiguration = { // the default separators except `@$` - wordPattern: /(-?\d*\.\d\w*)|([^`~!#%^&*()\-=+\[{\]}\\|;:'",.<>\/?\s]+)/g, + wordPattern: /(-?\d*\.\d\w*)|([^`~!#%^&*()=+\[{\]}\\|;:'",<>\/?\s]+)/g, brackets: [['{', '}']], autoClosingPairs: [ { open: '{', close: '}' },