Send sanitized selectors to the Pyroscope backend for LabelNames and LabelValues
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ export function LabelsEditor(props: Props) {
|
||||
<CodeEditor
|
||||
value={props.value}
|
||||
language={langId}
|
||||
onBlur={props.onChange}
|
||||
onChange={props.onChange}
|
||||
containerStyles={styles.queryField}
|
||||
monacoOptions={{
|
||||
folding: false,
|
||||
|
||||
+35
-6
@@ -1,5 +1,5 @@
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import React, {useCallback, useEffect, useMemo} from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { CoreApp, QueryEditorProps, TimeRange } from '@grafana/data';
|
||||
@@ -119,21 +119,50 @@ function useLabels(
|
||||
from: Math.floor((range?.from.valueOf() || 0) / 5000) * 5000,
|
||||
};
|
||||
|
||||
const createSelector = useCallback(
|
||||
(query: Query, labelToRemove: string): string => {
|
||||
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(
|
||||
|
||||
+2
-2
@@ -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}`);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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: '}' },
|
||||
|
||||
Reference in New Issue
Block a user