Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 91 additions & 31 deletions pyroscope/src/model/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,51 +12,111 @@
// limitations under the License.

/**
* Request parameters of Pyroscope HTTP API endpoint GET /pyroscope/render
* https://grafana.com/docs/pyroscope/latest/reference-server-api/#querying-profile-data
* Output format for SelectMergeStacktraces, serialized as the protobuf enum
* name. When omitted, the server defaults to the flame graph format.
*/
export interface SearchProfilesParameters {
query: string;
/** the start time can be an absolute value (number) or a relative value (string). Ex of relative value : now-15m*/
from: string | number;
/** end of the search window, default : now */
until?: string | number;
/** format of the returned profiling data, default: json */
format?: 'json' | 'dot';
/** maximum number of nodes the resulting flame graph will contain, default: 50 */
export type ProfileFormat =
| 'PROFILE_FORMAT_UNSPECIFIED'
| 'PROFILE_FORMAT_FLAMEGRAPH'
| 'PROFILE_FORMAT_TREE'
| 'PROFILE_FORMAT_DOT'
| 'PROFILE_FORMAT_PPROF';

/**
* Aggregation function applied by SelectSeries when down-sampling to the step
* resolution. Defaults to SUM when omitted.
*/
export type TimeSeriesAggregationType = 'TIME_SERIES_AGGREGATION_TYPE_SUM' | 'TIME_SERIES_AGGREGATION_TYPE_AVERAGE';

/**
* Request body of POST /querier.v1.QuerierService/SelectMergeStacktraces.
* Returns matching profiles aggregated into a Flamegraph.
* https://grafana.com/docs/pyroscope/latest/reference-server-api/#querierv1querierserviceselectmergestacktraces
*/
export interface SelectMergeStacktracesRequest {
/** Profile type ID: <name>:<type>:<unit>:<period_type>:<period_unit> */
profileTypeID: string;
/** Label selector string, e.g. `{service_name="my_service"}` */
labelSelector: string;
/** Start of the query window, milliseconds since epoch */
start: number;
/** End of the query window, milliseconds since epoch */
end: number;
/** Caps the number of nodes in the returned flame graph */
maxNodes?: number;
groupeBy?: string;
/** Output format; defaults to the flame graph format when omitted */
format?: ProfileFormat;
}

/**
* Response of Pyroscope HTTP API endpoint GET /pyroscope/render
* https://grafana.com/docs/pyroscope/latest/reference-server-api/#query-output
* Response of POST /querier.v1.QuerierService/SelectMergeStacktraces.
*/
export interface SearchProfilesResponse {
flamebearer: Flamebearer;
metadata: Metadata;
timeline: Timeline;
export interface SelectMergeStacktracesResponse {
flamegraph: FlameGraph;
}

export interface Flamebearer {
/**
* FlameGraph in the packed level representation. Each level's `values` array
* is a flat list of 4-tuples (offset, total, self, nameIndex)
* https://github.com/grafana/pyroscope/blob/788a581f23db4af50c2a5ebc81da2d5959ace8f6/api/querier/v1/querier.proto#L157
*/
export interface FlameGraph {
names: string[];
levels: number[][];
numTicks: number;
maxSelf: number;
levels: Level[];
// int64 fields are serialized as JSON strings by the Connect/protobuf encoding, so `total`,
// `maxSelf`, and every `Level.values` entry must be coerced to a number before use.
total: number | string;
maxSelf: number | string;
}

export interface Level {
values: Array<number | string>;
}

/**
* Request body of POST /querier.v1.QuerierService/SelectSeries.
* Returns the time series for the total of the matching profiles.
* https://grafana.com/docs/pyroscope/latest/reference-server-api/#querierv1querierserviceselectseries
*/
export interface SelectSeriesRequest {
/** Profile type ID: <name>:<type>:<unit>:<period_type>:<period_unit> */
profileTypeID: string;
/** Label selector string, e.g. `{service_name="my_service"}` */
labelSelector: string;
/** Start of the query window, milliseconds since epoch */
start: number;
/** End of the query window, milliseconds since epoch */
end: number;
/** Query resolution step width, in seconds */
step: number;
/** Aggregation function; defaults to SUM when omitted */
aggregation?: TimeSeriesAggregationType;
/** Labels to group the series by */
groupBy?: string[];
}

/**
* Response of POST /querier.v1.QuerierService/SelectSeries.
*/
export interface SelectSeriesResponse {
series: Series[];
}

export interface Series {
labels: LabelPair[];
points: Point[];
}

export interface Metadata {
format: 'single' | 'double';
spyName: string;
sampleRate: number;
units: string;
export interface LabelPair {
name: string;
value: string;
}

export interface Timeline {
startTime: number;
samples: number[];
durationDelta: number;
export interface Point {
/** Sample value (protobuf double, encoded as a JSON number). */
value: number;
/** Milliseconds unix timestamp (protobuf int64, serialized as a JSON string). */
timestamp: number | string;
}

/**
Expand Down
58 changes: 33 additions & 25 deletions pyroscope/src/model/pyroscope-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@
import { DatasourceClient } from '@perses-dev/plugin-system';
import { RequestHeaders } from '@perses-dev/client';
import {
SearchProfilesParameters,
SearchProfilesResponse,
SearchProfileTypesParameters,
SearchProfileTypesResponse,
SearchLabelNamesParameters,
SearchLabelNamesResponse,
SearchLabelValuesParameters,
SearchLabelValuesResponse,
SelectMergeStacktracesRequest,
SelectMergeStacktracesResponse,
SelectSeriesRequest,
SelectSeriesResponse,
} from './api-types';

interface PyroscopeClientOptions {
Expand All @@ -31,7 +33,11 @@ interface PyroscopeClientOptions {

export interface PyroscopeClient extends DatasourceClient {
options: PyroscopeClientOptions;
searchProfiles(params: SearchProfilesParameters, headers?: RequestHeaders): Promise<SearchProfilesResponse>;
selectMergeStacktraces(
body: SelectMergeStacktracesRequest,
headers?: RequestHeaders
): Promise<SelectMergeStacktracesResponse>;
selectSeries(body: SelectSeriesRequest, headers?: RequestHeaders): Promise<SelectSeriesResponse>;
searchProfileTypes(
params: SearchProfileTypesParameters,
headers: RequestHeaders,
Expand Down Expand Up @@ -69,26 +75,11 @@ export const executeRequest = async <T>(...args: Parameters<typeof global.fetch>
}
};

function fetchWithGet<T, TResponse>(apiURI: string, params: T | null, queryOptions: QueryOptions): Promise<TResponse> {
const { datasourceUrl, headers = {} } = queryOptions;

let url = `${datasourceUrl}${apiURI}`;
if (params) {
url += '?' + new URLSearchParams(params);
}
const init = {
method: 'GET',
headers,
};

return executeRequest<TResponse>(url, init);
}

function fetchWithPost<T, TResponse>(
apiURI: string,
params: T | null,
queryOptions: QueryOptions,
body: Record<string, string | number>
body: object
): Promise<TResponse> {
const { datasourceUrl, headers = {} } = queryOptions;

Expand All @@ -98,21 +89,38 @@ function fetchWithPost<T, TResponse>(
}
const init = {
method: 'POST',
headers,
headers: { 'content-type': 'application/json', ...headers },
body: JSON.stringify(body),
};

return executeRequest<TResponse>(url, init);
}

/**
* Returns profiling data.
* Returns the flame graph for the matching profiles.
*/
export function searchProfiles(
params: SearchProfilesParameters,
export function selectMergeStacktraces(
body: SelectMergeStacktracesRequest,
queryOptions: QueryOptions
): Promise<SearchProfilesResponse> {
return fetchWithGet<SearchProfilesParameters, SearchProfilesResponse>('/pyroscope/render', params, queryOptions);
): Promise<SelectMergeStacktracesResponse> {
return fetchWithPost<Record<string, never>, SelectMergeStacktracesResponse>(
'/querier.v1.QuerierService/SelectMergeStacktraces',
null,
queryOptions,
body
);
}

/**
* Returns the time series (timeline) for the matching profiles.
*/
export function selectSeries(body: SelectSeriesRequest, queryOptions: QueryOptions): Promise<SelectSeriesResponse> {
return fetchWithPost<Record<string, never>, SelectSeriesResponse>(
'/querier.v1.QuerierService/SelectSeries',
null,
queryOptions,
body
);
}

/**
Expand Down
7 changes: 5 additions & 2 deletions pyroscope/src/plugins/pyroscope-datasource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
import { DatasourcePlugin } from '@perses-dev/plugin-system';
import {
PyroscopeClient,
searchProfiles,
selectMergeStacktraces,
selectSeries,
searchProfileTypes,
searchLabelNames,
searchLabelValues,
Expand Down Expand Up @@ -42,7 +43,9 @@ const createClient: DatasourcePlugin<PyroscopeDatasourceSpec, PyroscopeClient>['
options: {
datasourceUrl,
},
searchProfiles: (params, headers) => searchProfiles(params, { datasourceUrl, headers: headers ?? specHeaders }),
selectMergeStacktraces: (body, headers) =>
selectMergeStacktraces(body, { datasourceUrl, headers: headers ?? specHeaders }),
selectSeries: (body, headers) => selectSeries(body, { datasourceUrl, headers: headers ?? specHeaders }),
searchProfileTypes: (params, headers, body) =>
searchProfileTypes(params, { datasourceUrl, headers: headers ?? specHeaders }, body),
searchLabelNames: (params, headers, body) =>
Expand Down
Loading
Loading