Pyroscope: Preselect default profile type or app in the query editor dropdown (#70624)

This commit is contained in:
Andrej Ocenas 2023-06-28 12:26:09 +02:00 committed by GitHub
parent a2dad8a636
commit 0259ae2630
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 73 additions and 49 deletions

View File

@ -1,12 +1,11 @@
import { defaults } from 'lodash';
import deepEqual from 'fast-deep-equal';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useAsync } from 'react-use';
import { CoreApp, QueryEditorProps, TimeRange } from '@grafana/data';
import { ButtonCascader, CascaderOption } from '@grafana/ui';
import { defaultGrafanaPyroscope, defaultPhlareQueryType, GrafanaPyroscope } from '../dataquery.gen';
import { PhlareDataSource } from '../datasource';
import { normalizeQuery, PhlareDataSource } from '../datasource';
import { BackendType, PhlareDataSourceOptions, ProfileTypeMessage, Query } from '../types';
import { EditorRow } from './EditorRow';
@ -16,31 +15,18 @@ import { QueryOptions } from './QueryOptions';
export type Props = QueryEditorProps<PhlareDataSource, Query, PhlareDataSourceOptions>;
export const defaultQuery: Partial<GrafanaPyroscope> = {
...defaultGrafanaPyroscope,
queryType: defaultPhlareQueryType,
};
export function QueryEditor(props: Props) {
let query = normalizeQuery(props.query, props.app);
const { onChange, onRunQuery, datasource, query, range, app } = props;
function handleRunQuery(value: string) {
props.onChange({ ...props.query, labelSelector: value });
props.onRunQuery();
onChange({ ...query, labelSelector: value });
onRunQuery();
}
const { profileTypes, onProfileTypeChange, selectedProfileName } = useProfileTypes(
props.datasource,
props.query,
props.onChange,
props.datasource.backendType
);
const { labels, getLabelValues, onLabelSelectorChange } = useLabels(
props.range,
props.datasource,
props.query,
props.onChange
);
const { profileTypes, onProfileTypeChange, selectedProfileName } = useProfileTypes(datasource, query, onChange);
const { labels, getLabelValues, onLabelSelectorChange } = useLabels(range, datasource, query, onChange);
useNormalizeQuery(query, profileTypes, onChange, app);
const cascaderOptions = useCascaderOptions(profileTypes);
return (
@ -64,6 +50,42 @@ export function QueryEditor(props: Props) {
);
}
function useNormalizeQuery(
query: Query,
profileTypes: ProfileTypeMessage[],
onChange: (value: Query) => void,
app?: CoreApp
) {
useEffect(() => {
const normalizedQuery = normalizeQuery(query, app);
// Query can be stored with some old type, or we can have query from different pyro datasource
const selectedProfile = query.profileTypeId && profileTypes.find((p) => p.id === query.profileTypeId);
if (profileTypes.length && !selectedProfile) {
normalizedQuery.profileTypeId = defaultProfileType(profileTypes);
}
// Makes sure we don't have an infinite loop updates because the normalization creates a new object
if (!deepEqual(query, normalizedQuery)) {
onChange(normalizedQuery);
}
}, [app, query, profileTypes, onChange]);
}
function defaultProfileType(profileTypes: ProfileTypeMessage[]): string {
const cpuProfiles = profileTypes.filter((p) => p.id.indexOf('cpu') >= 0);
if (cpuProfiles.length) {
// Prefer cpu time profile if available instead of samples
const cpuTimeProfile = cpuProfiles.find((p) => p.id.indexOf('samples') === -1);
if (cpuTimeProfile) {
return cpuTimeProfile.id;
}
// Fallback to first cpu profile type
return cpuProfiles[0].id;
}
// Fallback to first profile type from response data
return profileTypes[0].id;
}
function useLabels(
range: TimeRange | undefined,
datasource: PhlareDataSource,
@ -138,12 +160,7 @@ function useCascaderOptions(profileTypes: ProfileTypeMessage[]) {
}, [profileTypes]);
}
function useProfileTypes(
datasource: PhlareDataSource,
query: Query,
onChange: (value: Query) => void,
backendType: BackendType = 'phlare'
) {
function useProfileTypes(datasource: PhlareDataSource, query: Query, onChange: (value: Query) => void) {
const [profileTypes, setProfileTypes] = useState<ProfileTypeMessage[]>([]);
useEffect(() => {
@ -160,23 +177,20 @@ function useProfileTypes(
}
const id = selectedOptions[selectedOptions.length - 1].value;
// Probably cannot happen but makes TS happy
if (typeof id !== 'string') {
throw new Error('id is not string');
}
onChange({ ...query, profileTypeId: id });
},
[onChange, query]
);
const selectedProfileName = useProfileName(profileTypes, query.profileTypeId, backendType);
const selectedProfileName = useProfileName(profileTypes, query.profileTypeId, datasource.backendType);
return { profileTypes, onProfileTypeChange, selectedProfileName };
}
function useProfileName(profileTypes: ProfileTypeMessage[], profileTypeId: string, backendType: BackendType) {
function useProfileName(
profileTypes: ProfileTypeMessage[],
profileTypeId: string,
backendType: BackendType = 'phlare'
) {
return useMemo(() => {
if (!profileTypes) {
return 'Loading';
@ -192,13 +206,3 @@ function useProfileName(profileTypes: ProfileTypeMessage[], profileTypeId: strin
return profile.label;
}, [profileTypeId, profileTypes, backendType]);
}
export function normalizeQuery(query: Query, app?: CoreApp | string) {
let normalized = defaults(query, defaultQuery);
if (app !== CoreApp.Explore && normalized.queryType === 'both') {
// In dashboards and other places, we can't show both types of graphs at the same time.
// This will also be a default when having 'both' query and adding it from explore to dashboard
normalized.queryType = 'profile';
}
return normalized;
}

View File

@ -3,6 +3,7 @@ import { Observable, of } from 'rxjs';
import {
AbstractQuery,
CoreApp,
DataQueryRequest,
DataQueryResponse,
DataSourceInstanceSettings,
@ -12,7 +13,7 @@ import { DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/run
import { extractLabelMatchers, toPromLikeExpr } from '../prometheus/language_utils';
import { normalizeQuery } from './QueryEditor/QueryEditor';
import { defaultGrafanaPyroscope, defaultPhlareQueryType } from './dataquery.gen';
import { PhlareDataSourceOptions, Query, ProfileTypeMessage, BackendType } from './types';
export class PhlareDataSource extends DataSourceWithBackend<Query, PhlareDataSourceOptions> {
@ -101,6 +102,25 @@ export class PhlareDataSource extends DataSourceWithBackend<Query, PhlareDataSou
labelMatchers: extractLabelMatchers(tokens),
};
}
getDefaultQuery(app: CoreApp): Partial<Query> {
return defaultQuery;
}
}
export const defaultQuery: Partial<Query> = {
...defaultGrafanaPyroscope,
queryType: defaultPhlareQueryType,
};
export function normalizeQuery(query: Query, app?: CoreApp | string) {
let normalized = { ...query, ...defaultQuery };
if (app !== CoreApp.Explore && normalized.queryType === 'both') {
// In dashboards and other places, we can't show both types of graphs at the same time.
// This will also be a default when having 'both' query and adding it from explore to dashboard
normalized.queryType = 'profile';
}
return normalized;
}
const grammar: Grammar = {