mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 07:32:13 +08:00
Pyroscope: Send start/end with profile types query (#77523)
This commit is contained in:
@ -29,7 +29,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ProfilingClient interface {
|
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)
|
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)
|
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)
|
||||||
@ -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 {
|
func (d *PyroscopeDatasource) profileTypes(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||||
ctxLogger := logger.FromContext(ctx)
|
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 {
|
if err != nil {
|
||||||
ctxLogger.Error("Received error from client", "error", err, "function", logEntrypoint())
|
ctxLogger.Error("Received error from client", "error", err, "function", logEntrypoint())
|
||||||
return err
|
return err
|
||||||
@ -199,7 +222,7 @@ func (d *PyroscopeDatasource) labelValues(ctx context.Context, req *backend.Call
|
|||||||
// contains Frames ([]*Frame).
|
// contains Frames ([]*Frame).
|
||||||
func (d *PyroscopeDatasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
func (d *PyroscopeDatasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||||
ctxLogger := logger.FromContext(ctx)
|
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
|
// create response struct
|
||||||
response := backend.NewQueryDataResponse()
|
response := backend.NewQueryDataResponse()
|
||||||
@ -228,7 +251,11 @@ func (d *PyroscopeDatasource) CheckHealth(ctx context.Context, _ *backend.CheckH
|
|||||||
status := backend.HealthStatusOk
|
status := backend.HealthStatusOk
|
||||||
message := "Data source is working"
|
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
|
status = backend.HealthStatusError
|
||||||
message = err.Error()
|
message = err.Error()
|
||||||
}
|
}
|
||||||
|
@ -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")
|
ctx, span := tracing.DefaultTracer().Start(ctx, "datasource.pyroscope.ProfileTypes")
|
||||||
defer span.End()
|
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 {
|
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)
|
||||||
|
@ -275,7 +275,7 @@ type FakeClient struct {
|
|||||||
Args []any
|
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{
|
return []*ProfileType{
|
||||||
{
|
{
|
||||||
ID: "type:1",
|
ID: "type:1",
|
||||||
|
@ -55,7 +55,7 @@ export function TraceToProfilesSettings({ options, onOptionsChange }: Props) {
|
|||||||
supportedDataSourceTypes.includes(dataSource.type) &&
|
supportedDataSourceTypes.includes(dataSource.type) &&
|
||||||
dataSource.uid === options.jsonData.tracesToProfiles?.datasourceUid
|
dataSource.uid === options.jsonData.tracesToProfiles?.datasourceUid
|
||||||
) {
|
) {
|
||||||
dataSource.getProfileTypes().then((profileTypes) => {
|
dataSource.getAllProfileTypes().then((profileTypes) => {
|
||||||
setProfileTypes(profileTypes);
|
setProfileTypes(profileTypes);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { TimeRange } from '@grafana/data';
|
||||||
import { Cascader, CascaderOption } from '@grafana/ui';
|
import { Cascader, CascaderOption } from '@grafana/ui';
|
||||||
|
|
||||||
import { PyroscopeDataSource } from '../datasource';
|
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
|
* 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.
|
* the profileTypes before rendering the cascader.
|
||||||
* @param datasource
|
* @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<ProfileTypeMessage[]>();
|
const [profileTypes, setProfileTypes] = useState<ProfileTypeMessage[]>();
|
||||||
|
|
||||||
|
const impreciseRange = {
|
||||||
|
to: Math.ceil((range?.to.valueOf() || 0) / 60000) * 60000,
|
||||||
|
from: Math.floor((range?.from.valueOf() || 0) / 60000) * 60000,
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const profileTypes = await datasource.getProfileTypes();
|
const profileTypes = await datasource.getProfileTypes(impreciseRange.from.valueOf(), impreciseRange.to.valueOf());
|
||||||
setProfileTypes(profileTypes);
|
setProfileTypes(profileTypes);
|
||||||
})();
|
})();
|
||||||
}, [datasource]);
|
}, [datasource, impreciseRange.from, impreciseRange.to]);
|
||||||
|
|
||||||
return profileTypes;
|
return profileTypes;
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ export function QueryEditor(props: Props) {
|
|||||||
onRunQuery();
|
onRunQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
const profileTypes = useProfileTypes(datasource);
|
const profileTypes = useProfileTypes(datasource, range);
|
||||||
const { labels, getLabelValues, onLabelSelectorChange } = useLabels(range, datasource, query, onChange);
|
const { labels, getLabelValues, onLabelSelectorChange } = useLabels(range, datasource, query, onChange);
|
||||||
useNormalizeQuery(query, profileTypes, onChange, app);
|
useNormalizeQuery(query, profileTypes, onChange, app);
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
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 { InlineField, InlineFieldRow, LoadingPlaceholder, Select } from '@grafana/ui';
|
||||||
|
|
||||||
import { ProfileTypesCascader, useProfileTypes } from './QueryEditor/ProfileTypesCascader';
|
import { ProfileTypesCascader, useProfileTypes } from './QueryEditor/ProfileTypesCascader';
|
||||||
@ -65,6 +65,7 @@ export function VariableQueryEditor(props: QueryEditorProps<PyroscopeDataSource,
|
|||||||
props.onChange({ ...props.query, profileTypeId: val });
|
props.onChange({ ...props.query, profileTypeId: val });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
range={props.range}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -131,8 +132,9 @@ function ProfileTypeRow(props: {
|
|||||||
datasource: PyroscopeDataSource;
|
datasource: PyroscopeDataSource;
|
||||||
onChange: (val: string) => void;
|
onChange: (val: string) => void;
|
||||||
initialValue?: string;
|
initialValue?: string;
|
||||||
|
range?: TimeRange;
|
||||||
}) {
|
}) {
|
||||||
const profileTypes = useProfileTypes(props.datasource);
|
const profileTypes = useProfileTypes(props.datasource, props.range);
|
||||||
return (
|
return (
|
||||||
<InlineFieldRow>
|
<InlineFieldRow>
|
||||||
<InlineField
|
<InlineField
|
||||||
|
@ -9,7 +9,7 @@ import { PyroscopeDataSource } from './datasource';
|
|||||||
import { ProfileTypeMessage, VariableQuery } from './types';
|
import { ProfileTypeMessage, VariableQuery } from './types';
|
||||||
|
|
||||||
export interface DataAPI {
|
export interface DataAPI {
|
||||||
getProfileTypes(): Promise<ProfileTypeMessage[]>;
|
getProfileTypes(start: number, end: number): Promise<ProfileTypeMessage[]>;
|
||||||
getLabelNames(query: string, start: number, end: number): Promise<string[]>;
|
getLabelNames(query: string, start: number, end: number): Promise<string[]>;
|
||||||
getLabelValues(query: string, label: string, start: number, end: number): Promise<string[]>;
|
getLabelValues(query: string, label: string, start: number, end: number): Promise<string[]>;
|
||||||
}
|
}
|
||||||
@ -26,7 +26,9 @@ export class VariableSupport extends CustomVariableSupport<PyroscopeDataSource>
|
|||||||
|
|
||||||
query(request: DataQueryRequest<VariableQuery>): Observable<DataQueryResponse> {
|
query(request: DataQueryRequest<VariableQuery>): Observable<DataQueryResponse> {
|
||||||
if (request.targets[0].type === 'profileType') {
|
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) => {
|
map((values) => {
|
||||||
return { data: values.map<MetricFindValue>((v) => ({ text: v.label, value: v.id })) };
|
return { data: values.map<MetricFindValue>((v) => ({ text: v.label, value: v.id })) };
|
||||||
})
|
})
|
||||||
|
@ -48,7 +48,14 @@ export class PyroscopeDataSource extends DataSourceWithBackend<Query, PyroscopeD
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProfileTypes(): Promise<ProfileTypeMessage[]> {
|
async getProfileTypes(start: number, end: number): Promise<ProfileTypeMessage[]> {
|
||||||
|
return await this.getResource('profileTypes', {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllProfileTypes(): Promise<ProfileTypeMessage[]> {
|
||||||
return await this.getResource('profileTypes');
|
return await this.getResource('profileTypes');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user