2023-01-03 10:25:16 +00:00
|
|
|
import Prism, { Grammar } from 'prismjs';
|
2022-10-28 13:33:37 +02:00
|
|
|
import { Observable, of } from 'rxjs';
|
|
|
|
|
|
2023-03-08 13:29:28 +00:00
|
|
|
import {
|
|
|
|
|
AbstractQuery,
|
|
|
|
|
DataQueryRequest,
|
|
|
|
|
DataQueryResponse,
|
|
|
|
|
DataSourceInstanceSettings,
|
|
|
|
|
ScopedVars,
|
|
|
|
|
} from '@grafana/data';
|
|
|
|
|
import { DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/runtime';
|
2022-10-28 13:33:37 +02:00
|
|
|
|
2023-01-03 10:25:16 +00:00
|
|
|
import { extractLabelMatchers, toPromLikeExpr } from '../prometheus/language_utils';
|
|
|
|
|
|
2022-10-28 13:33:37 +02:00
|
|
|
import { normalizeQuery } from './QueryEditor/QueryEditor';
|
2023-04-25 16:08:18 +02:00
|
|
|
import { PhlareDataSourceOptions, Query, ProfileTypeMessage, BackendType } from './types';
|
2022-10-28 13:33:37 +02:00
|
|
|
|
2022-11-30 17:22:47 +00:00
|
|
|
export class PhlareDataSource extends DataSourceWithBackend<Query, PhlareDataSourceOptions> {
|
2023-04-25 16:08:18 +02:00
|
|
|
backendType: BackendType;
|
|
|
|
|
|
2023-03-08 13:29:28 +00:00
|
|
|
constructor(
|
|
|
|
|
instanceSettings: DataSourceInstanceSettings<PhlareDataSourceOptions>,
|
|
|
|
|
private readonly templateSrv: TemplateSrv = getTemplateSrv()
|
|
|
|
|
) {
|
2022-10-28 13:33:37 +02:00
|
|
|
super(instanceSettings);
|
2023-04-25 16:08:18 +02:00
|
|
|
this.backendType = instanceSettings.jsonData.backendType ?? 'phlare';
|
2022-10-28 13:33:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
query(request: DataQueryRequest<Query>): Observable<DataQueryResponse> {
|
|
|
|
|
const validTargets = request.targets
|
|
|
|
|
.filter((t) => t.profileTypeId)
|
|
|
|
|
.map((t) => {
|
|
|
|
|
// Empty string errors out but honestly seems like we can just normalize it this way
|
|
|
|
|
if (t.labelSelector === '') {
|
|
|
|
|
return {
|
|
|
|
|
...t,
|
|
|
|
|
labelSelector: '{}',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return normalizeQuery(t, request.app);
|
|
|
|
|
});
|
|
|
|
|
if (!validTargets.length) {
|
|
|
|
|
return of({ data: [] });
|
|
|
|
|
}
|
|
|
|
|
return super.query({
|
|
|
|
|
...request,
|
|
|
|
|
targets: validTargets,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async getProfileTypes(): Promise<ProfileTypeMessage[]> {
|
|
|
|
|
return await super.getResource('profileTypes');
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-25 16:08:18 +02:00
|
|
|
async getLabelNames(query: string, start: number, end: number): Promise<string[]> {
|
|
|
|
|
return await super.getResource('labelNames', { query, start, end });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async getLabelValues(query: string, label: string, start: number, end: number): Promise<string[]> {
|
|
|
|
|
return await super.getResource('labelValues', { label, query, start, end });
|
2022-10-28 13:33:37 +02:00
|
|
|
}
|
|
|
|
|
|
2023-04-25 16:08:18 +02:00
|
|
|
// We need the URL here because it may not be saved on the backend yet when used from config page.
|
|
|
|
|
async getBackendType(url: string): Promise<{ backendType: BackendType | 'unknown' }> {
|
|
|
|
|
return await super.getResource('backendType', { url });
|
2022-10-28 13:33:37 +02:00
|
|
|
}
|
2023-01-03 10:25:16 +00:00
|
|
|
|
2023-03-08 13:29:28 +00:00
|
|
|
applyTemplateVariables(query: Query, scopedVars: ScopedVars): Query {
|
|
|
|
|
return {
|
|
|
|
|
...query,
|
|
|
|
|
labelSelector: this.templateSrv.replace(query.labelSelector ?? '', scopedVars),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-03 10:25:16 +00:00
|
|
|
async importFromAbstractQueries(abstractQueries: AbstractQuery[]): Promise<Query[]> {
|
|
|
|
|
return abstractQueries.map((abstractQuery) => this.importFromAbstractQuery(abstractQuery));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
importFromAbstractQuery(labelBasedQuery: AbstractQuery): Query {
|
|
|
|
|
return {
|
|
|
|
|
refId: labelBasedQuery.refId,
|
|
|
|
|
labelSelector: toPromLikeExpr(labelBasedQuery),
|
|
|
|
|
queryType: 'both',
|
|
|
|
|
profileTypeId: '',
|
2023-04-27 01:50:11 -07:00
|
|
|
maxNodes: 16,
|
2023-01-03 10:25:16 +00:00
|
|
|
groupBy: [''],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async exportToAbstractQueries(queries: Query[]): Promise<AbstractQuery[]> {
|
|
|
|
|
return queries.map((query) => this.exportToAbstractQuery(query));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
exportToAbstractQuery(query: Query): AbstractQuery {
|
|
|
|
|
const phlareQuery = query.labelSelector;
|
|
|
|
|
if (!phlareQuery || phlareQuery.length === 0) {
|
|
|
|
|
return { refId: query.refId, labelMatchers: [] };
|
|
|
|
|
}
|
|
|
|
|
const tokens = Prism.tokenize(phlareQuery, grammar);
|
|
|
|
|
return {
|
|
|
|
|
refId: query.refId,
|
|
|
|
|
labelMatchers: extractLabelMatchers(tokens),
|
|
|
|
|
};
|
|
|
|
|
}
|
2022-10-28 13:33:37 +02:00
|
|
|
}
|
2023-01-03 10:25:16 +00:00
|
|
|
|
|
|
|
|
const grammar: Grammar = {
|
|
|
|
|
'context-labels': {
|
|
|
|
|
pattern: /\{[^}]*(?=}?)/,
|
|
|
|
|
greedy: true,
|
|
|
|
|
inside: {
|
|
|
|
|
comment: {
|
|
|
|
|
pattern: /#.*/,
|
|
|
|
|
},
|
|
|
|
|
'label-key': {
|
|
|
|
|
pattern: /[a-zA-Z_]\w*(?=\s*(=|!=|=~|!~))/,
|
|
|
|
|
alias: 'attr-name',
|
|
|
|
|
greedy: true,
|
|
|
|
|
},
|
|
|
|
|
'label-value': {
|
|
|
|
|
pattern: /"(?:\\.|[^\\"])*"/,
|
|
|
|
|
greedy: true,
|
|
|
|
|
alias: 'attr-value',
|
|
|
|
|
},
|
|
|
|
|
punctuation: /[{]/,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
punctuation: /[{}(),.]/,
|
|
|
|
|
};
|