introduce resource clients
This commit is contained in:
@@ -8,7 +8,6 @@ import { PrometheusDatasource } from './datasource';
|
||||
import {
|
||||
exportToAbstractQuery,
|
||||
importFromAbstractQuery,
|
||||
processSeries,
|
||||
removeQuotesIfExist,
|
||||
PrometheusLanguageProviderInterface,
|
||||
PrometheusLanguageProvider,
|
||||
@@ -948,136 +947,3 @@ describe('removeQuotesIfExist', () => {
|
||||
expect(result).toBe('12345');
|
||||
});
|
||||
});
|
||||
|
||||
describe('processSeries', () => {
|
||||
it('should extract metrics and label keys from series data', () => {
|
||||
const result = processSeries([
|
||||
{
|
||||
__name__: 'alerts',
|
||||
alertname: 'AppCrash',
|
||||
alertstate: 'firing',
|
||||
instance: 'host.docker.internal:3000',
|
||||
job: 'grafana',
|
||||
severity: 'critical',
|
||||
},
|
||||
{
|
||||
__name__: 'alerts',
|
||||
alertname: 'AppCrash',
|
||||
alertstate: 'firing',
|
||||
instance: 'prometheus-utf8:9112',
|
||||
job: 'prometheus-utf8',
|
||||
severity: 'critical',
|
||||
},
|
||||
{
|
||||
__name__: 'counters_logins',
|
||||
app: 'backend',
|
||||
geohash: '9wvfgzurfzb',
|
||||
instance: 'fake-prometheus-data:9091',
|
||||
job: 'fake-data-gen',
|
||||
server: 'backend-01',
|
||||
},
|
||||
]);
|
||||
|
||||
// Check structure
|
||||
expect(result).toHaveProperty('metrics');
|
||||
expect(result).toHaveProperty('labelKeys');
|
||||
|
||||
// Verify metrics are extracted correctly
|
||||
expect(result.metrics).toEqual(['alerts', 'counters_logins']);
|
||||
|
||||
// Verify all metrics are unique
|
||||
expect(result.metrics.length).toBe(new Set(result.metrics).size);
|
||||
|
||||
// Verify label keys are extracted correctly and don't include __name__
|
||||
expect(result.labelKeys).toContain('instance');
|
||||
expect(result.labelKeys).toContain('job');
|
||||
expect(result.labelKeys).not.toContain('__name__');
|
||||
|
||||
// Verify all label keys are unique
|
||||
expect(result.labelKeys.length).toBe(new Set(result.labelKeys).size);
|
||||
});
|
||||
|
||||
it('should handle empty series data', () => {
|
||||
const result = processSeries([]);
|
||||
|
||||
expect(result.metrics).toEqual([]);
|
||||
expect(result.labelKeys).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle series without __name__ attribute', () => {
|
||||
const series = [
|
||||
{ instance: 'localhost:9090', job: 'prometheus' },
|
||||
{ instance: 'localhost:9100', job: 'node' },
|
||||
];
|
||||
|
||||
const result = processSeries(series);
|
||||
|
||||
expect(result.metrics).toEqual([]);
|
||||
expect(result.labelKeys).toEqual(['instance', 'job']);
|
||||
});
|
||||
|
||||
it('should extract label values for a specific key when findValuesForKey is provided', () => {
|
||||
const series = [
|
||||
{
|
||||
__name__: 'alerts',
|
||||
instance: 'host.docker.internal:3000',
|
||||
job: 'grafana',
|
||||
severity: 'critical',
|
||||
},
|
||||
{
|
||||
__name__: 'alerts',
|
||||
instance: 'prometheus-utf8:9112',
|
||||
job: 'prometheus-utf8',
|
||||
severity: 'critical',
|
||||
},
|
||||
{
|
||||
__name__: 'counters_logins',
|
||||
instance: 'fake-prometheus-data:9091',
|
||||
job: 'fake-data-gen',
|
||||
severity: 'warning',
|
||||
},
|
||||
];
|
||||
|
||||
// Test finding values for 'job' label
|
||||
const jobResult = processSeries(series, 'job');
|
||||
expect(jobResult.labelValues).toEqual(['fake-data-gen', 'grafana', 'prometheus-utf8']);
|
||||
|
||||
// Test finding values for 'severity' label
|
||||
const severityResult = processSeries(series, 'severity');
|
||||
expect(severityResult.labelValues).toEqual(['critical', 'warning']);
|
||||
|
||||
// Test finding values for 'instance' label
|
||||
const instanceResult = processSeries(series, 'instance');
|
||||
expect(instanceResult.labelValues).toEqual([
|
||||
'fake-prometheus-data:9091',
|
||||
'host.docker.internal:3000',
|
||||
'prometheus-utf8:9112',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return empty labelValues array when findValuesForKey is not provided', () => {
|
||||
const series = [
|
||||
{
|
||||
__name__: 'alerts',
|
||||
instance: 'host.docker.internal:3000',
|
||||
job: 'grafana',
|
||||
},
|
||||
];
|
||||
|
||||
const result = processSeries(series);
|
||||
expect(result.labelValues).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty labelValues array when findValuesForKey does not match any labels', () => {
|
||||
const series = [
|
||||
{
|
||||
__name__: 'alerts',
|
||||
instance: 'host.docker.internal:3000',
|
||||
job: 'grafana',
|
||||
},
|
||||
];
|
||||
|
||||
const result = processSeries(series, 'non_existent_label');
|
||||
expect(result.labelValues).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,13 +23,13 @@ import { PrometheusDatasource } from './datasource';
|
||||
import {
|
||||
extractLabelMatchers,
|
||||
fixSummariesMetadata,
|
||||
getRangeSnapInterval,
|
||||
processHistogramMetrics,
|
||||
processLabels,
|
||||
toPromLikeQuery,
|
||||
} from './language_utils';
|
||||
import PromqlSyntax from './promql';
|
||||
import { buildVisualQueryFromString } from './querybuilder/parsing';
|
||||
import { LabelsApiClient, ResourceApiClient, SeriesApiClient } from './resource_clients';
|
||||
import { PromMetricsMetadata, PromQuery } from './types';
|
||||
import { escapeForUtf8Support, isValidLegacyName } from './utf8_support';
|
||||
|
||||
@@ -48,8 +48,6 @@ const API_V1 = {
|
||||
LABELS_VALUES: (labelKey: string) => `/api/v1/label/${labelKey}/values`,
|
||||
};
|
||||
|
||||
const MATCH_ALL_LABELS = '{__name__!=""}';
|
||||
|
||||
export interface PrometheusBaseLanguageProvider {
|
||||
datasource: PrometheusDatasource;
|
||||
|
||||
@@ -526,9 +524,40 @@ export interface PrometheusLanguageProviderInterface
|
||||
|
||||
export class PrometheusLanguageProvider extends PromQlLanguageProvider implements PrometheusLanguageProviderInterface {
|
||||
private _metricsMetadata?: PromMetricsMetadata;
|
||||
private _histogramMetrics: string[] = [];
|
||||
private _metrics: string[] = [];
|
||||
private _labelKeys: string[] = [];
|
||||
private _resourceClient: ResourceApiClient;
|
||||
|
||||
constructor(datasource: PrometheusDatasource) {
|
||||
super(datasource);
|
||||
|
||||
this.datasource = datasource;
|
||||
|
||||
const { cacheLevel, getAdjustedInterval, getTimeRangeParams, interpolateString } = this.datasource;
|
||||
if (this.datasource.hasLabelsMatchAPISupport()) {
|
||||
this._resourceClient = new LabelsApiClient(
|
||||
this.request,
|
||||
cacheLevel,
|
||||
getAdjustedInterval,
|
||||
getTimeRangeParams,
|
||||
interpolateString
|
||||
);
|
||||
} else {
|
||||
this._resourceClient = new SeriesApiClient(
|
||||
this.request,
|
||||
cacheLevel,
|
||||
getAdjustedInterval,
|
||||
getTimeRangeParams,
|
||||
interpolateString
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
start = async (timeRange: TimeRange = getDefaultTimeRange()): Promise<any[]> => {
|
||||
if (this.datasource.lookupsDisabled) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Promise.all([this._resourceClient.start(timeRange), this._queryMetadata()]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches metadata for metrics from Prometheus.
|
||||
@@ -550,117 +579,20 @@ export class PrometheusLanguageProvider extends PromQlLanguageProvider implement
|
||||
return fixSummariesMetadata(metadata);
|
||||
};
|
||||
|
||||
// ===================================
|
||||
// Labels API
|
||||
// ===================================
|
||||
|
||||
/**
|
||||
* Fetches all available label keys from Prometheus using labels endpoint.
|
||||
* Uses the labels endpoint with optional match parameter for filtering.
|
||||
*
|
||||
* @param {TimeRange} timeRange - Time range to use for the query
|
||||
* @param {string} match - Optional label matcher to filter results
|
||||
* @param {string} limit - Maximum number of results to return
|
||||
* @returns {Promise<string[]>} Array of label keys sorted alphabetically
|
||||
*/
|
||||
private _withLabelsApiFetchLabelKeys = async (
|
||||
timeRange: TimeRange,
|
||||
match?: string,
|
||||
limit: string = DEFAULT_SERIES_LIMIT
|
||||
): Promise<string[]> => {
|
||||
let url = API_V1.LABELS;
|
||||
const timeParams = getRangeSnapInterval(this.datasource.cacheLevel, timeRange);
|
||||
const searchParams = { limit, ...timeParams, ...(match ? { 'match[]': match } : {}) };
|
||||
|
||||
const res = await this.request(url, searchParams, getDefaultCacheHeaders(this.datasource.cacheLevel));
|
||||
if (Array.isArray(res)) {
|
||||
this._labelKeys = res.slice().sort();
|
||||
return this._labelKeys.slice();
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches all values for a specific label key from Prometheus using labels values endpoint.
|
||||
*
|
||||
* @param {TimeRange} timeRange - Time range to use for the query
|
||||
* @param {string} labelKey - The label key to fetch values for
|
||||
* @param {string} match - Optional label matcher to filter results
|
||||
* @param {string} limit - Maximum number of results to return
|
||||
* @returns {Promise<string[]>} Array of label values
|
||||
*/
|
||||
private _withLabelsApiFetchLabelValues = async (
|
||||
timeRange: TimeRange,
|
||||
labelKey: string,
|
||||
match?: string,
|
||||
limit: string = DEFAULT_SERIES_LIMIT
|
||||
): Promise<string[]> => {
|
||||
const timeParams = this.datasource.getAdjustedInterval(timeRange);
|
||||
const searchParams = { limit, ...timeParams, ...(match ? { 'match[]': match } : {}) };
|
||||
const interpolatedName = this.datasource.interpolateString(labelKey);
|
||||
const interpolatedAndEscapedName = escapeForUtf8Support(removeQuotesIfExist(interpolatedName));
|
||||
const url = API_V1.LABELS_VALUES(interpolatedAndEscapedName);
|
||||
const value = await this.request(url, searchParams, getDefaultCacheHeaders(this.datasource.cacheLevel));
|
||||
return value ?? [];
|
||||
};
|
||||
|
||||
// ===================================
|
||||
// Series API
|
||||
// ===================================
|
||||
|
||||
/**
|
||||
* Fetches all time series that match a specific label matcher using series endpoint.
|
||||
*
|
||||
* @param {TimeRange} timeRange - Time range to use for the query
|
||||
* @param {string} match - Label matcher to filter time series
|
||||
* @param {string} limit - Maximum number of series to return
|
||||
*/
|
||||
private _withSeriesApiFetchAllSeries = async (
|
||||
timeRange: TimeRange,
|
||||
match: string,
|
||||
limit: string = DEFAULT_SERIES_LIMIT
|
||||
) => {
|
||||
const timeParams = this.datasource.getTimeRangeParams(timeRange);
|
||||
const searchParams = { ...timeParams, 'match[]': match, limit };
|
||||
return await this.request(API_V1.SERIES, searchParams, getDefaultCacheHeaders(this.datasource.cacheLevel));
|
||||
};
|
||||
|
||||
private _withSeriesApiFetchLabelKeys = async (
|
||||
timeRange: TimeRange,
|
||||
match: string,
|
||||
limit: string = DEFAULT_SERIES_LIMIT
|
||||
): Promise<string[]> => {
|
||||
const series = await this._withSeriesApiFetchAllSeries(timeRange, match, limit);
|
||||
const { labelKeys } = processSeries(series);
|
||||
return labelKeys;
|
||||
};
|
||||
|
||||
private _withSeriesApiFetchLabelValues = async (
|
||||
timeRange: TimeRange,
|
||||
labelKey: string,
|
||||
match: string,
|
||||
limit: string = DEFAULT_SERIES_LIMIT
|
||||
): Promise<string[]> => {
|
||||
const series = await this._withSeriesApiFetchAllSeries(timeRange, match, limit);
|
||||
const { labelValues } = processSeries(series, labelKey);
|
||||
return labelValues;
|
||||
};
|
||||
|
||||
public retrieveMetricsMetadata = (): PromMetricsMetadata | undefined => {
|
||||
return this._metricsMetadata;
|
||||
};
|
||||
|
||||
public retrieveHistogramMetrics = (): string[] => {
|
||||
return this._histogramMetrics;
|
||||
return this._resourceClient?.histogramMetrics;
|
||||
};
|
||||
|
||||
public retrieveMetrics = (): string[] => {
|
||||
return this._metrics;
|
||||
return this._resourceClient?.metrics;
|
||||
};
|
||||
|
||||
public retrieveLabelKeys = (): string[] => {
|
||||
return this._labelKeys;
|
||||
return this._resourceClient?.labelKeys;
|
||||
};
|
||||
|
||||
public queryMetricsMetadata = async (): Promise<PromMetricsMetadata> => {
|
||||
@@ -669,11 +601,7 @@ export class PrometheusLanguageProvider extends PromQlLanguageProvider implement
|
||||
};
|
||||
|
||||
public queryLabelKeys = async (timeRange: TimeRange, match?: string, limit?: string): Promise<string[]> => {
|
||||
if (this.datasource.hasLabelsMatchAPISupport()) {
|
||||
return await this._withLabelsApiFetchLabelKeys(timeRange, match, limit);
|
||||
}
|
||||
|
||||
return await this._withSeriesApiFetchLabelKeys(timeRange, match ?? MATCH_ALL_LABELS, limit);
|
||||
return await this._resourceClient.queryLabelKeys(timeRange, match, limit);
|
||||
};
|
||||
|
||||
public queryLabelValues = async (
|
||||
@@ -682,11 +610,7 @@ export class PrometheusLanguageProvider extends PromQlLanguageProvider implement
|
||||
match?: string,
|
||||
limit?: string
|
||||
): Promise<string[]> => {
|
||||
if (this.datasource.hasLabelsMatchAPISupport()) {
|
||||
return await this._withLabelsApiFetchLabelValues(timeRange, labelKey, match, limit);
|
||||
}
|
||||
|
||||
return await this._withSeriesApiFetchLabelValues(timeRange, labelKey, match ?? MATCH_ALL_LABELS, limit);
|
||||
return await this._resourceClient.queryLabelValues(timeRange, labelKey, match, limit);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -716,36 +640,6 @@ export const exportToAbstractQuery = (query: PromQuery): AbstractQuery => {
|
||||
};
|
||||
};
|
||||
|
||||
export function processSeries(series: Array<{ [key: string]: string }>, findValuesForKey?: string) {
|
||||
const metrics: Set<string> = new Set();
|
||||
const labelKeys: Set<string> = new Set();
|
||||
const labelValues: Set<string> = new Set();
|
||||
|
||||
// Extract metrics and label keys
|
||||
series.forEach((item) => {
|
||||
// Add the __name__ value to metrics
|
||||
if ('__name__' in item) {
|
||||
metrics.add(item.__name__);
|
||||
}
|
||||
|
||||
// Add all keys except __name__ to labelKeys
|
||||
Object.keys(item).forEach((key) => {
|
||||
if (key !== '__name__') {
|
||||
labelKeys.add(key);
|
||||
}
|
||||
if (findValuesForKey && key === findValuesForKey) {
|
||||
labelValues.add(item[key]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
metrics: Array.from(metrics).sort(),
|
||||
labelKeys: Array.from(labelKeys).sort(),
|
||||
labelValues: Array.from(labelValues).sort(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an error is a cancelled request error.
|
||||
* Used to avoid logging cancelled request errors.
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import { processSeries } from './resource_clients';
|
||||
|
||||
describe('processSeries', () => {
|
||||
it('should extract metrics and label keys from series data', () => {
|
||||
const result = processSeries([
|
||||
{
|
||||
__name__: 'alerts',
|
||||
alertname: 'AppCrash',
|
||||
alertstate: 'firing',
|
||||
instance: 'host.docker.internal:3000',
|
||||
job: 'grafana',
|
||||
severity: 'critical',
|
||||
},
|
||||
{
|
||||
__name__: 'alerts',
|
||||
alertname: 'AppCrash',
|
||||
alertstate: 'firing',
|
||||
instance: 'prometheus-utf8:9112',
|
||||
job: 'prometheus-utf8',
|
||||
severity: 'critical',
|
||||
},
|
||||
{
|
||||
__name__: 'counters_logins',
|
||||
app: 'backend',
|
||||
geohash: '9wvfgzurfzb',
|
||||
instance: 'fake-prometheus-data:9091',
|
||||
job: 'fake-data-gen',
|
||||
server: 'backend-01',
|
||||
},
|
||||
]);
|
||||
|
||||
// Check structure
|
||||
expect(result).toHaveProperty('metrics');
|
||||
expect(result).toHaveProperty('labelKeys');
|
||||
|
||||
// Verify metrics are extracted correctly
|
||||
expect(result.metrics).toEqual(['alerts', 'counters_logins']);
|
||||
|
||||
// Verify all metrics are unique
|
||||
expect(result.metrics.length).toBe(new Set(result.metrics).size);
|
||||
|
||||
// Verify label keys are extracted correctly and don't include __name__
|
||||
expect(result.labelKeys).toContain('instance');
|
||||
expect(result.labelKeys).toContain('job');
|
||||
expect(result.labelKeys).not.toContain('__name__');
|
||||
|
||||
// Verify all label keys are unique
|
||||
expect(result.labelKeys.length).toBe(new Set(result.labelKeys).size);
|
||||
});
|
||||
|
||||
it('should handle empty series data', () => {
|
||||
const result = processSeries([]);
|
||||
|
||||
expect(result.metrics).toEqual([]);
|
||||
expect(result.labelKeys).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle series without __name__ attribute', () => {
|
||||
const series = [
|
||||
{ instance: 'localhost:9090', job: 'prometheus' },
|
||||
{ instance: 'localhost:9100', job: 'node' },
|
||||
];
|
||||
|
||||
const result = processSeries(series);
|
||||
|
||||
expect(result.metrics).toEqual([]);
|
||||
expect(result.labelKeys).toEqual(['instance', 'job']);
|
||||
});
|
||||
|
||||
it('should extract label values for a specific key when findValuesForKey is provided', () => {
|
||||
const series = [
|
||||
{
|
||||
__name__: 'alerts',
|
||||
instance: 'host.docker.internal:3000',
|
||||
job: 'grafana',
|
||||
severity: 'critical',
|
||||
},
|
||||
{
|
||||
__name__: 'alerts',
|
||||
instance: 'prometheus-utf8:9112',
|
||||
job: 'prometheus-utf8',
|
||||
severity: 'critical',
|
||||
},
|
||||
{
|
||||
__name__: 'counters_logins',
|
||||
instance: 'fake-prometheus-data:9091',
|
||||
job: 'fake-data-gen',
|
||||
severity: 'warning',
|
||||
},
|
||||
];
|
||||
|
||||
// Test finding values for 'job' label
|
||||
const jobResult = processSeries(series, 'job');
|
||||
expect(jobResult.labelValues).toEqual(['fake-data-gen', 'grafana', 'prometheus-utf8']);
|
||||
|
||||
// Test finding values for 'severity' label
|
||||
const severityResult = processSeries(series, 'severity');
|
||||
expect(severityResult.labelValues).toEqual(['critical', 'warning']);
|
||||
|
||||
// Test finding values for 'instance' label
|
||||
const instanceResult = processSeries(series, 'instance');
|
||||
expect(instanceResult.labelValues).toEqual([
|
||||
'fake-prometheus-data:9091',
|
||||
'host.docker.internal:3000',
|
||||
'prometheus-utf8:9112',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return empty labelValues array when findValuesForKey is not provided', () => {
|
||||
const series = [
|
||||
{
|
||||
__name__: 'alerts',
|
||||
instance: 'host.docker.internal:3000',
|
||||
job: 'grafana',
|
||||
},
|
||||
];
|
||||
|
||||
const result = processSeries(series);
|
||||
expect(result.labelValues).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty labelValues array when findValuesForKey does not match any labels', () => {
|
||||
const series = [
|
||||
{
|
||||
__name__: 'alerts',
|
||||
instance: 'host.docker.internal:3000',
|
||||
job: 'grafana',
|
||||
},
|
||||
];
|
||||
|
||||
const result = processSeries(series, 'non_existent_label');
|
||||
expect(result.labelValues).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,193 @@
|
||||
import { ScopedVars, TimeRange } from '@grafana/data';
|
||||
import { BackendSrvRequest } from '@grafana/runtime';
|
||||
|
||||
import { getDefaultCacheHeaders } from './caching';
|
||||
import { DEFAULT_SERIES_LIMIT } from './components/metrics-browser/types';
|
||||
import { removeQuotesIfExist } from './language_provider';
|
||||
import { getRangeSnapInterval, processHistogramMetrics } from './language_utils';
|
||||
import { PrometheusCacheLevel } from './types';
|
||||
import { escapeForUtf8Support } from './utf8_support';
|
||||
|
||||
export interface ResourceApiClient {
|
||||
metrics: string[];
|
||||
histogramMetrics: string[];
|
||||
labelKeys: string[];
|
||||
cachedLabelValues: Record<string, string[]>;
|
||||
|
||||
start: (timeRange: TimeRange) => Promise<void>;
|
||||
|
||||
queryMetrics: (timeRange: TimeRange) => Promise<{ metrics: string[]; histogramMetrics: string[] }>;
|
||||
queryLabelKeys: (timeRange: TimeRange, match?: string, limit?: string) => Promise<string[]>;
|
||||
queryLabelValues: (timeRange: TimeRange, labelKey: string, match?: string, limit?: string) => Promise<string[]>;
|
||||
}
|
||||
|
||||
const MATCH_ALL_LABELS = '{__name__!=""}';
|
||||
const METRIC_LABEL = '__name__';
|
||||
|
||||
abstract class BaseResourceClient {
|
||||
constructor(
|
||||
protected readonly request: (url: string, params?: any, options?: Partial<BackendSrvRequest>) => Promise<any>,
|
||||
protected readonly cacheLevel: PrometheusCacheLevel,
|
||||
protected readonly getAdjustedInterval: (timeRange: TimeRange) => { start: string; end: string },
|
||||
protected readonly getTimeRangeParams: (timeRange: TimeRange) => { start: string; end: string },
|
||||
protected readonly interpolateString: (string: string, scopedVars?: ScopedVars) => string
|
||||
) {}
|
||||
}
|
||||
|
||||
export class LabelsApiClient extends BaseResourceClient implements ResourceApiClient {
|
||||
public histogramMetrics: string[] = [];
|
||||
public metrics: string[] = [];
|
||||
public labelKeys: string[] = [];
|
||||
public cachedLabelValues: Record<string, string[]> = {};
|
||||
|
||||
start = async (timeRange: TimeRange) => {
|
||||
await this.queryMetrics(timeRange);
|
||||
this.labelKeys = await this.queryLabelKeys(timeRange);
|
||||
};
|
||||
|
||||
public queryMetrics = async (timeRange: TimeRange): Promise<{ metrics: string[]; histogramMetrics: string[] }> => {
|
||||
this.metrics = await this.queryLabelKeys(timeRange, METRIC_LABEL);
|
||||
this.histogramMetrics = processHistogramMetrics(this.metrics);
|
||||
return { metrics: this.metrics, histogramMetrics: this.histogramMetrics };
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches all available label keys from Prometheus using labels endpoint.
|
||||
* Uses the labels endpoint with optional match parameter for filtering.
|
||||
*
|
||||
* @param {TimeRange} timeRange - Time range to use for the query
|
||||
* @param {string} match - Optional label matcher to filter results
|
||||
* @param {string} limit - Maximum number of results to return
|
||||
* @returns {Promise<string[]>} Array of label keys sorted alphabetically
|
||||
*/
|
||||
public queryLabelKeys = async (
|
||||
timeRange: TimeRange,
|
||||
match?: string,
|
||||
limit: string = DEFAULT_SERIES_LIMIT
|
||||
): Promise<string[]> => {
|
||||
let url = '/api/v1/labels';
|
||||
const timeParams = getRangeSnapInterval(this.cacheLevel, timeRange);
|
||||
const searchParams = { limit, ...timeParams, ...(match ? { 'match[]': match } : {}) };
|
||||
|
||||
const res = await this.request(url, searchParams, getDefaultCacheHeaders(this.cacheLevel));
|
||||
if (Array.isArray(res)) {
|
||||
this.labelKeys = res.slice().sort();
|
||||
return this.labelKeys.slice();
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches all values for a specific label key from Prometheus using labels values endpoint.
|
||||
*
|
||||
* @param {TimeRange} timeRange - Time range to use for the query
|
||||
* @param {string} labelKey - The label key to fetch values for
|
||||
* @param {string} match - Optional label matcher to filter results
|
||||
* @param {string} limit - Maximum number of results to return
|
||||
* @returns {Promise<string[]>} Array of label values
|
||||
*/
|
||||
public queryLabelValues = async (
|
||||
timeRange: TimeRange,
|
||||
labelKey: string,
|
||||
match?: string,
|
||||
limit: string = DEFAULT_SERIES_LIMIT
|
||||
): Promise<string[]> => {
|
||||
const timeParams = this.getAdjustedInterval(timeRange);
|
||||
const searchParams = { limit, ...timeParams, ...(match ? { 'match[]': match } : {}) };
|
||||
const interpolatedName = this.interpolateString(labelKey);
|
||||
const interpolatedAndEscapedName = escapeForUtf8Support(removeQuotesIfExist(interpolatedName));
|
||||
const url = `/api/v1/label/${interpolatedAndEscapedName}/values`;
|
||||
const value = await this.request(url, searchParams, getDefaultCacheHeaders(this.cacheLevel));
|
||||
return value ?? [];
|
||||
};
|
||||
}
|
||||
|
||||
export class SeriesApiClient extends BaseResourceClient implements ResourceApiClient {
|
||||
public histogramMetrics: string[] = [];
|
||||
public metrics: string[] = [];
|
||||
public labelKeys: string[] = [];
|
||||
public cachedLabelValues: Record<string, string[]> = {};
|
||||
|
||||
start = async (timeRange: TimeRange) => {
|
||||
await this.queryMetrics(timeRange);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches all time series that match a specific label matcher using series endpoint.
|
||||
*
|
||||
* @param {TimeRange} timeRange - Time range to use for the query
|
||||
* @param {string} match - Label matcher to filter time series
|
||||
* @param {string} limit - Maximum number of series to return
|
||||
*/
|
||||
private _fetchAllSeries = async (timeRange: TimeRange, match: string, limit: string = DEFAULT_SERIES_LIMIT) => {
|
||||
const timeParams = this.getTimeRangeParams(timeRange);
|
||||
const searchParams = { ...timeParams, 'match[]': match, limit };
|
||||
return await this.request('/api/v1/series', searchParams, getDefaultCacheHeaders(this.cacheLevel));
|
||||
};
|
||||
|
||||
public queryMetrics = async (timeRange: TimeRange): Promise<{ metrics: string[]; histogramMetrics: string[] }> => {
|
||||
const series = await this._fetchAllSeries(timeRange, MATCH_ALL_LABELS);
|
||||
const { metrics, labelKeys } = processSeries(series);
|
||||
this.metrics = metrics;
|
||||
this.histogramMetrics = processHistogramMetrics(this.metrics);
|
||||
this.labelKeys = labelKeys;
|
||||
return { metrics: this.metrics, histogramMetrics: this.histogramMetrics };
|
||||
};
|
||||
|
||||
public queryLabelKeys = async (
|
||||
timeRange: TimeRange,
|
||||
match?: string,
|
||||
limit: string = DEFAULT_SERIES_LIMIT
|
||||
): Promise<string[]> => {
|
||||
if (!match) {
|
||||
throw new Error('Series endpoint always expects at least one matcher');
|
||||
}
|
||||
const series = await this._fetchAllSeries(timeRange, match, limit);
|
||||
const { labelKeys } = processSeries(series);
|
||||
return labelKeys;
|
||||
};
|
||||
public queryLabelValues = async (
|
||||
timeRange: TimeRange,
|
||||
labelKey: string,
|
||||
match?: string,
|
||||
limit: string = DEFAULT_SERIES_LIMIT
|
||||
): Promise<string[]> => {
|
||||
if (!match) {
|
||||
throw new Error('Series endpoint always expects at least one matcher');
|
||||
}
|
||||
const series = await this._fetchAllSeries(timeRange, match, limit);
|
||||
const { labelValues } = processSeries(series, labelKey);
|
||||
return labelValues;
|
||||
};
|
||||
}
|
||||
|
||||
export function processSeries(series: Array<{ [key: string]: string }>, findValuesForKey?: string) {
|
||||
const metrics: Set<string> = new Set();
|
||||
const labelKeys: Set<string> = new Set();
|
||||
const labelValues: Set<string> = new Set();
|
||||
|
||||
// Extract metrics and label keys
|
||||
series.forEach((item) => {
|
||||
// Add the __name__ value to metrics
|
||||
if (METRIC_LABEL in item) {
|
||||
metrics.add(item.__name__);
|
||||
}
|
||||
|
||||
// Add all keys except __name__ to labelKeys
|
||||
Object.keys(item).forEach((key) => {
|
||||
if (key !== METRIC_LABEL) {
|
||||
labelKeys.add(key);
|
||||
}
|
||||
if (findValuesForKey && key === findValuesForKey) {
|
||||
labelValues.add(item[key]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
metrics: Array.from(metrics).sort(),
|
||||
labelKeys: Array.from(labelKeys).sort(),
|
||||
labelValues: Array.from(labelValues).sort(),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user