From e64cb8541f33cf1891c74ea64bcaf5e13d5b50c3 Mon Sep 17 00:00:00 2001 From: Bryan Huhta <32787160+bryanhuhta@users.noreply.github.com> Date: Thu, 4 Jan 2024 09:35:47 -0600 Subject: [PATCH] Pyroscope: Send start/end with profile types query (#77523) --- .../grafana-pyroscope-datasource/instance.go | 35 ++++++++++++++++--- .../pyroscopeClient.go | 7 ++-- .../query_test.go | 2 +- .../TraceToProfilesSettings.tsx | 2 +- .../QueryEditor/ProfileTypesCascader.tsx | 13 +++++-- .../QueryEditor/QueryEditor.tsx | 2 +- .../VariableQueryEditor.tsx | 6 ++-- .../VariableSupport.ts | 6 ++-- .../datasource.ts | 9 ++++- 9 files changed, 65 insertions(+), 17 deletions(-) diff --git a/pkg/tsdb/grafana-pyroscope-datasource/instance.go b/pkg/tsdb/grafana-pyroscope-datasource/instance.go index 1ce336fe1b4..e1964cce9fd 100644 --- a/pkg/tsdb/grafana-pyroscope-datasource/instance.go +++ b/pkg/tsdb/grafana-pyroscope-datasource/instance.go @@ -29,7 +29,7 @@ var ( ) type ProfilingClient interface { - ProfileTypes(context.Context) ([]*ProfileType, error) + ProfileTypes(ctx context.Context, start int64, end int64) ([]*ProfileType, 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) @@ -86,7 +86,30 @@ func (d *PyroscopeDatasource) CallResource(ctx context.Context, req *backend.Cal func (d *PyroscopeDatasource) profileTypes(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { ctxLogger := logger.FromContext(ctx) - types, err := d.client.ProfileTypes(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() + + var start, end int64 + if query.Has("start") && query.Has("end") { + start, err = strconv.ParseInt(query.Get("start"), 10, 64) + if err != nil { + ctxLogger.Error("Failed to parse start as int", "error", err, "function", logEntrypoint()) + return err + } + + end, err = strconv.ParseInt(query.Get("end"), 10, 64) + if err != nil { + ctxLogger.Error("Failed to parse end as int", "error", err, "function", logEntrypoint()) + return err + } + } + + types, err := d.client.ProfileTypes(ctx, start, end) if err != nil { ctxLogger.Error("Received error from client", "error", err, "function", logEntrypoint()) return err @@ -199,7 +222,7 @@ func (d *PyroscopeDatasource) labelValues(ctx context.Context, req *backend.Call // contains Frames ([]*Frame). func (d *PyroscopeDatasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { ctxLogger := logger.FromContext(ctx) - ctxLogger.Debug("Processing queries", "queryLenght", len(req.Queries), "function", logEntrypoint()) + ctxLogger.Debug("Processing queries", "queryLength", len(req.Queries), "function", logEntrypoint()) // create response struct response := backend.NewQueryDataResponse() @@ -228,7 +251,11 @@ func (d *PyroscopeDatasource) CheckHealth(ctx context.Context, _ *backend.CheckH status := backend.HealthStatusOk message := "Data source is working" - if _, err := d.client.ProfileTypes(ctx); err != nil { + // Since this is a health check mechanism and we only care about whether the + // request succeeded or failed, we set the window to be small. + start := time.Now().Add(-5 * time.Minute).UnixMilli() + end := time.Now().UnixMilli() + if _, err := d.client.ProfileTypes(ctx, start, end); err != nil { status = backend.HealthStatusError message = err.Error() } diff --git a/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient.go b/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient.go index e642de237a6..ea0f1a14c69 100644 --- a/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient.go +++ b/pkg/tsdb/grafana-pyroscope-datasource/pyroscopeClient.go @@ -70,10 +70,13 @@ func NewPyroscopeClient(httpClient *http.Client, url string) *PyroscopeClient { } } -func (c *PyroscopeClient) ProfileTypes(ctx context.Context) ([]*ProfileType, error) { +func (c *PyroscopeClient) ProfileTypes(ctx context.Context, start int64, end int64) ([]*ProfileType, error) { ctx, span := tracing.DefaultTracer().Start(ctx, "datasource.pyroscope.ProfileTypes") defer span.End() - res, err := c.connectClient.ProfileTypes(ctx, connect.NewRequest(&querierv1.ProfileTypesRequest{})) + res, err := c.connectClient.ProfileTypes(ctx, connect.NewRequest(&querierv1.ProfileTypesRequest{ + Start: start, + End: end, + })) if err != nil { logger.Error("Received error from client", "error", err, "function", logEntrypoint()) span.RecordError(err) diff --git a/pkg/tsdb/grafana-pyroscope-datasource/query_test.go b/pkg/tsdb/grafana-pyroscope-datasource/query_test.go index 94d068b1e15..8926fd8d679 100644 --- a/pkg/tsdb/grafana-pyroscope-datasource/query_test.go +++ b/pkg/tsdb/grafana-pyroscope-datasource/query_test.go @@ -275,7 +275,7 @@ type FakeClient struct { Args []any } -func (f *FakeClient) ProfileTypes(ctx context.Context) ([]*ProfileType, error) { +func (f *FakeClient) ProfileTypes(ctx context.Context, start int64, end int64) ([]*ProfileType, error) { return []*ProfileType{ { ID: "type:1", diff --git a/public/app/core/components/TraceToProfiles/TraceToProfilesSettings.tsx b/public/app/core/components/TraceToProfiles/TraceToProfilesSettings.tsx index 41d5c7d7d57..0dae1382481 100644 --- a/public/app/core/components/TraceToProfiles/TraceToProfilesSettings.tsx +++ b/public/app/core/components/TraceToProfiles/TraceToProfilesSettings.tsx @@ -55,7 +55,7 @@ export function TraceToProfilesSettings({ options, onOptionsChange }: Props) { supportedDataSourceTypes.includes(dataSource.type) && dataSource.uid === options.jsonData.tracesToProfiles?.datasourceUid ) { - dataSource.getProfileTypes().then((profileTypes) => { + dataSource.getAllProfileTypes().then((profileTypes) => { setProfileTypes(profileTypes); }); } else { diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/ProfileTypesCascader.tsx b/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/ProfileTypesCascader.tsx index f04bb77b7de..fd21bb71585 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/ProfileTypesCascader.tsx +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/ProfileTypesCascader.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useMemo, useState } from 'react'; +import { TimeRange } from '@grafana/data'; import { Cascader, CascaderOption } from '@grafana/ui'; import { PyroscopeDataSource } from '../datasource'; @@ -70,16 +71,22 @@ function useCascaderOptions(profileTypes?: ProfileTypeMessage[]): CascaderOption * This is exported and not used directly in the ProfileTypesCascader component because in some case we need to know * the profileTypes before rendering the cascader. * @param datasource + * @param range Time range for the profile types query. */ -export function useProfileTypes(datasource: PyroscopeDataSource) { +export function useProfileTypes(datasource: PyroscopeDataSource, range?: TimeRange) { const [profileTypes, setProfileTypes] = useState(); + const impreciseRange = { + to: Math.ceil((range?.to.valueOf() || 0) / 60000) * 60000, + from: Math.floor((range?.from.valueOf() || 0) / 60000) * 60000, + }; + useEffect(() => { (async () => { - const profileTypes = await datasource.getProfileTypes(); + const profileTypes = await datasource.getProfileTypes(impreciseRange.from.valueOf(), impreciseRange.to.valueOf()); setProfileTypes(profileTypes); })(); - }, [datasource]); + }, [datasource, impreciseRange.from, impreciseRange.to]); return profileTypes; } diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryEditor.tsx b/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryEditor.tsx index 96d0de4dead..f1d04e8a4ba 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryEditor.tsx +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/QueryEditor/QueryEditor.tsx @@ -27,7 +27,7 @@ export function QueryEditor(props: Props) { onRunQuery(); } - const profileTypes = useProfileTypes(datasource); + const profileTypes = useProfileTypes(datasource, range); const { labels, getLabelValues, onLabelSelectorChange } = useLabels(range, datasource, query, onChange); useNormalizeQuery(query, profileTypes, onChange, app); diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/VariableQueryEditor.tsx b/public/app/plugins/datasource/grafana-pyroscope-datasource/VariableQueryEditor.tsx index 2e9860169c0..866fa34105a 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/VariableQueryEditor.tsx +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/VariableQueryEditor.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; -import { QueryEditorProps, SelectableValue } from '@grafana/data'; +import { QueryEditorProps, SelectableValue, TimeRange } from '@grafana/data'; import { InlineField, InlineFieldRow, LoadingPlaceholder, Select } from '@grafana/ui'; import { ProfileTypesCascader, useProfileTypes } from './QueryEditor/ProfileTypesCascader'; @@ -65,6 +65,7 @@ export function VariableQueryEditor(props: QueryEditorProps )} @@ -131,8 +132,9 @@ function ProfileTypeRow(props: { datasource: PyroscopeDataSource; onChange: (val: string) => void; initialValue?: string; + range?: TimeRange; }) { - const profileTypes = useProfileTypes(props.datasource); + const profileTypes = useProfileTypes(props.datasource, props.range); return ( ; + getProfileTypes(start: number, end: number): Promise; getLabelNames(query: string, start: number, end: number): Promise; getLabelValues(query: string, label: string, start: number, end: number): Promise; } @@ -26,7 +26,9 @@ export class VariableSupport extends CustomVariableSupport query(request: DataQueryRequest): Observable { if (request.targets[0].type === 'profileType') { - return from(this.dataAPI.getProfileTypes()).pipe( + return from( + this.dataAPI.getProfileTypes(this.timeSrv.timeRange().from.valueOf(), this.timeSrv.timeRange().to.valueOf()) + ).pipe( map((values) => { return { data: values.map((v) => ({ text: v.label, value: v.id })) }; }) diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/datasource.ts b/public/app/plugins/datasource/grafana-pyroscope-datasource/datasource.ts index 34c24e8d3a2..efcc5cb97cb 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/datasource.ts +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/datasource.ts @@ -48,7 +48,14 @@ export class PyroscopeDataSource extends DataSourceWithBackend { + async getProfileTypes(start: number, end: number): Promise { + return await this.getResource('profileTypes', { + start, + end, + }); + } + + async getAllProfileTypes(): Promise { return await this.getResource('profileTypes'); }