Send sanitized selectors to the Pyroscope backend for LabelNames and LabelValues

This commit is contained in:
Aleksandar Petrov
2023-11-29 18:04:17 -04:00
parent 86311e3a33
commit ca5e8abd4d
6 changed files with 68 additions and 18 deletions
@@ -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)
@@ -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,
@@ -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(
@@ -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}`);
}
@@ -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: '}' },