mirror of
https://github.com/prometheus/prometheus.git
synced 2025-12-04 00:51:02 +01:00
Codemirror - TS Prometheus Client: performance improvement when getting label name and values (#17194)
* Codemirror - TS Prometheus Client: don't use lookback interval if not set Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * TS Prometheus Client: remove usage of series api when getting labels Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * mock api /api/v1/labels Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * mock more endpoints and fix tests Signed-off-by: Augustin Husson <husson.augustin@gmail.com> --------- Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
This commit is contained in:
parent
1070a034e5
commit
03d0c18c79
@ -36,6 +36,7 @@ export interface PrometheusClient {
|
|||||||
// Note that the returned list can be a superset of those suggestions for the prefix (i.e., including ones without the
|
// Note that the returned list can be a superset of those suggestions for the prefix (i.e., including ones without the
|
||||||
// prefix), as codemirror will filter these out when displaying suggestions to the user.
|
// prefix), as codemirror will filter these out when displaying suggestions to the user.
|
||||||
metricNames(prefix?: string): Promise<string[]>;
|
metricNames(prefix?: string): Promise<string[]>;
|
||||||
|
|
||||||
// flags returns flag values that prometheus was configured with.
|
// flags returns flag values that prometheus was configured with.
|
||||||
flags(): Promise<Record<string, string>>;
|
flags(): Promise<Record<string, string>>;
|
||||||
}
|
}
|
||||||
@ -77,7 +78,7 @@ const serviceUnavailable = 503;
|
|||||||
|
|
||||||
// HTTPPrometheusClient is the HTTP client that should be used to get some information from the different endpoint provided by prometheus.
|
// HTTPPrometheusClient is the HTTP client that should be used to get some information from the different endpoint provided by prometheus.
|
||||||
export class HTTPPrometheusClient implements PrometheusClient {
|
export class HTTPPrometheusClient implements PrometheusClient {
|
||||||
private readonly lookbackInterval = 60 * 60 * 1000 * 12; //12 hours
|
private readonly lookbackInterval: undefined | number; //12 hours
|
||||||
private readonly url: string;
|
private readonly url: string;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
private readonly errorHandler?: (error: any) => void;
|
private readonly errorHandler?: (error: any) => void;
|
||||||
@ -91,9 +92,7 @@ export class HTTPPrometheusClient implements PrometheusClient {
|
|||||||
constructor(config: PrometheusConfig) {
|
constructor(config: PrometheusConfig) {
|
||||||
this.url = config.url ? config.url : '';
|
this.url = config.url ? config.url : '';
|
||||||
this.errorHandler = config.httpErrorHandler;
|
this.errorHandler = config.httpErrorHandler;
|
||||||
if (config.lookbackInterval) {
|
|
||||||
this.lookbackInterval = config.lookbackInterval;
|
this.lookbackInterval = config.lookbackInterval;
|
||||||
}
|
|
||||||
if (config.fetchFn) {
|
if (config.fetchFn) {
|
||||||
this.fetchFn = config.fetchFn;
|
this.fetchFn = config.fetchFn;
|
||||||
}
|
}
|
||||||
@ -109,16 +108,17 @@ export class HTTPPrometheusClient implements PrometheusClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
labelNames(metricName?: string): Promise<string[]> {
|
labelNames(metricName?: string): Promise<string[]> {
|
||||||
|
const params: URLSearchParams = new URLSearchParams();
|
||||||
|
if (this.lookbackInterval) {
|
||||||
const end = new Date();
|
const end = new Date();
|
||||||
const start = new Date(end.getTime() - this.lookbackInterval);
|
const start = new Date(end.getTime() - this.lookbackInterval);
|
||||||
if (metricName === undefined || metricName === '') {
|
params.set('start', start.toISOString());
|
||||||
const request = this.buildRequest(
|
params.set('end', end.toISOString());
|
||||||
this.labelsEndpoint(),
|
}
|
||||||
new URLSearchParams({
|
if (metricName && metricName.length > 0) {
|
||||||
start: start.toISOString(),
|
params.set('match[]', labelMatchersToString(metricName));
|
||||||
end: end.toISOString(),
|
}
|
||||||
})
|
const request = this.buildRequest(this.labelsEndpoint(), params);
|
||||||
);
|
|
||||||
// See https://prometheus.io/docs/prometheus/latest/querying/api/#getting-label-names
|
// See https://prometheus.io/docs/prometheus/latest/querying/api/#getting-label-names
|
||||||
return this.fetchAPI<string[]>(request.uri, {
|
return this.fetchAPI<string[]>(request.uri, {
|
||||||
method: this.httpMethod,
|
method: this.httpMethod,
|
||||||
@ -131,31 +131,21 @@ export class HTTPPrometheusClient implements PrometheusClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.series(metricName).then((series) => {
|
|
||||||
const labelNames = new Set<string>();
|
|
||||||
for (const labelSet of series) {
|
|
||||||
for (const [key] of Object.entries(labelSet)) {
|
|
||||||
if (key === '__name__') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
labelNames.add(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Array.from(labelNames);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// labelValues return a list of the value associated to the given labelName.
|
// labelValues return a list of the value associated to the given labelName.
|
||||||
// In case a metric is provided, then the list of values is then associated to the couple <MetricName, LabelName>
|
// In case a metric is provided, then the list of values is then associated to the couple <MetricName, LabelName>
|
||||||
labelValues(labelName: string, metricName?: string, matchers?: Matcher[]): Promise<string[]> {
|
labelValues(labelName: string, metricName?: string, matchers?: Matcher[]): Promise<string[]> {
|
||||||
|
const params: URLSearchParams = new URLSearchParams();
|
||||||
|
if (this.lookbackInterval) {
|
||||||
const end = new Date();
|
const end = new Date();
|
||||||
const start = new Date(end.getTime() - this.lookbackInterval);
|
const start = new Date(end.getTime() - this.lookbackInterval);
|
||||||
|
params.set('start', start.toISOString());
|
||||||
|
params.set('end', end.toISOString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metricName && metricName.length > 0) {
|
||||||
|
params.set('match[]', labelMatchersToString(metricName, matchers, labelName));
|
||||||
|
}
|
||||||
|
|
||||||
if (!metricName || metricName.length === 0) {
|
|
||||||
const params: URLSearchParams = new URLSearchParams({
|
|
||||||
start: start.toISOString(),
|
|
||||||
end: end.toISOString(),
|
|
||||||
});
|
|
||||||
// See https://prometheus.io/docs/prometheus/latest/querying/api/#querying-label-values
|
// See https://prometheus.io/docs/prometheus/latest/querying/api/#querying-label-values
|
||||||
return this.fetchAPI<string[]>(`${this.labelValuesEndpoint().replace(/:name/gi, labelName)}?${params}`).catch((error) => {
|
return this.fetchAPI<string[]>(`${this.labelValuesEndpoint().replace(/:name/gi, labelName)}?${params}`).catch((error) => {
|
||||||
if (this.errorHandler) {
|
if (this.errorHandler) {
|
||||||
@ -165,22 +155,6 @@ export class HTTPPrometheusClient implements PrometheusClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.series(metricName, matchers, labelName).then((series) => {
|
|
||||||
const labelValues = new Set<string>();
|
|
||||||
for (const labelSet of series) {
|
|
||||||
for (const [key, value] of Object.entries(labelSet)) {
|
|
||||||
if (key === '__name__') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (key === labelName) {
|
|
||||||
labelValues.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Array.from(labelValues);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
metricMetadata(): Promise<Record<string, MetricMetadata[]>> {
|
metricMetadata(): Promise<Record<string, MetricMetadata[]>> {
|
||||||
return this.fetchAPI<Record<string, MetricMetadata[]>>(this.metricMetadataEndpoint()).catch((error) => {
|
return this.fetchAPI<Record<string, MetricMetadata[]>>(this.metricMetadataEndpoint()).catch((error) => {
|
||||||
if (this.errorHandler) {
|
if (this.errorHandler) {
|
||||||
@ -191,16 +165,15 @@ export class HTTPPrometheusClient implements PrometheusClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
series(metricName: string, matchers?: Matcher[], labelName?: string): Promise<Map<string, string>[]> {
|
series(metricName: string, matchers?: Matcher[], labelName?: string): Promise<Map<string, string>[]> {
|
||||||
|
const params: URLSearchParams = new URLSearchParams();
|
||||||
|
if (this.lookbackInterval) {
|
||||||
const end = new Date();
|
const end = new Date();
|
||||||
const start = new Date(end.getTime() - this.lookbackInterval);
|
const start = new Date(end.getTime() - this.lookbackInterval);
|
||||||
const request = this.buildRequest(
|
params.set('start', start.toISOString());
|
||||||
this.seriesEndpoint(),
|
params.set('end', end.toISOString());
|
||||||
new URLSearchParams({
|
}
|
||||||
start: start.toISOString(),
|
params.set('match[]', labelMatchersToString(metricName, matchers, labelName));
|
||||||
end: end.toISOString(),
|
const request = this.buildRequest(this.seriesEndpoint(), params);
|
||||||
'match[]': labelMatchersToString(metricName, matchers, labelName),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
// See https://prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers
|
// See https://prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers
|
||||||
return this.fetchAPI<Map<string, string>[]>(request.uri, {
|
return this.fetchAPI<Map<string, string>[]>(request.uri, {
|
||||||
method: this.httpMethod,
|
method: this.httpMethod,
|
||||||
@ -306,13 +279,18 @@ class Cache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setAssociations(metricName: string, series: Map<string, string>[]): void {
|
getAssociations(metricName: string): Map<string, Set<string>> {
|
||||||
series.forEach((labelSet: Map<string, string>) => {
|
|
||||||
let currentAssociation = this.completeAssociation.get(metricName);
|
let currentAssociation = this.completeAssociation.get(metricName);
|
||||||
if (!currentAssociation) {
|
if (!currentAssociation) {
|
||||||
currentAssociation = new Map<string, Set<string>>();
|
currentAssociation = new Map<string, Set<string>>();
|
||||||
this.completeAssociation.set(metricName, currentAssociation);
|
this.completeAssociation.set(metricName, currentAssociation);
|
||||||
}
|
}
|
||||||
|
return currentAssociation;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAssociations(metricName: string, series: Map<string, string>[]): void {
|
||||||
|
series.forEach((labelSet: Map<string, string>) => {
|
||||||
|
const currentAssociation = this.getAssociations(metricName);
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(labelSet)) {
|
for (const [key, value] of Object.entries(labelSet)) {
|
||||||
if (key === '__name__') {
|
if (key === '__name__') {
|
||||||
@ -328,6 +306,30 @@ class Cache {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLabelValuesAssociation(metricName: string, labelName: string, labelValues: string[]): void {
|
||||||
|
const currentAssociation = this.getAssociations(metricName);
|
||||||
|
const set = currentAssociation.get(labelName);
|
||||||
|
if (set === undefined) {
|
||||||
|
currentAssociation.set(labelName, new Set<string>(labelValues));
|
||||||
|
} else {
|
||||||
|
labelValues.forEach((value) => {
|
||||||
|
set.add(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setLabelNamesAssociation(metricName: string, labelNames: string[]): void {
|
||||||
|
const currentAssociation = this.getAssociations(metricName);
|
||||||
|
labelNames.forEach((labelName) => {
|
||||||
|
if (labelName === '__name__') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!currentAssociation.has(labelName)) {
|
||||||
|
currentAssociation.set(labelName, new Set<string>());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setFlags(flags: Record<string, string>): void {
|
setFlags(flags: Record<string, string>): void {
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
}
|
}
|
||||||
@ -344,7 +346,10 @@ class Cache {
|
|||||||
return this.metricMetadata;
|
return this.metricMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLabelNames(labelNames: string[]): void {
|
setLabelNames(labelNames: string[], metricName?: string): void {
|
||||||
|
if (metricName && metricName.length > 0) {
|
||||||
|
this.setLabelNamesAssociation(metricName, labelNames);
|
||||||
|
}
|
||||||
this.labelNames = labelNames;
|
this.labelNames = labelNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,7 +361,10 @@ class Cache {
|
|||||||
return labelSet ? Array.from(labelSet.keys()) : [];
|
return labelSet ? Array.from(labelSet.keys()) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
setLabelValues(labelName: string, labelValues: string[]): void {
|
setLabelValues(labelName: string, labelValues: string[], metricName?: string): void {
|
||||||
|
if (metricName && metricName.length > 0) {
|
||||||
|
this.setLabelValuesAssociation(metricName, labelName, labelValues);
|
||||||
|
}
|
||||||
this.labelValues.set(labelName, labelValues);
|
this.labelValues.set(labelName, labelValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,14 +397,8 @@ export class CachedPrometheusClient implements PrometheusClient {
|
|||||||
if (cachedLabel && cachedLabel.length > 0) {
|
if (cachedLabel && cachedLabel.length > 0) {
|
||||||
return Promise.resolve(cachedLabel);
|
return Promise.resolve(cachedLabel);
|
||||||
}
|
}
|
||||||
|
return this.client.labelNames(metricName).then((labelNames) => {
|
||||||
if (metricName === undefined || metricName === '') {
|
this.cache.setLabelNames(labelNames, metricName);
|
||||||
return this.client.labelNames().then((labelNames) => {
|
|
||||||
this.cache.setLabelNames(labelNames);
|
|
||||||
return labelNames;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this.series(metricName).then(() => {
|
|
||||||
return this.cache.getLabelNames(metricName);
|
return this.cache.getLabelNames(metricName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -406,19 +408,12 @@ export class CachedPrometheusClient implements PrometheusClient {
|
|||||||
if (cachedLabel && cachedLabel.length > 0) {
|
if (cachedLabel && cachedLabel.length > 0) {
|
||||||
return Promise.resolve(cachedLabel);
|
return Promise.resolve(cachedLabel);
|
||||||
}
|
}
|
||||||
|
return this.client.labelValues(labelName, metricName).then((labelValues) => {
|
||||||
if (metricName === undefined || metricName === '') {
|
this.cache.setLabelValues(labelName, labelValues, metricName);
|
||||||
return this.client.labelValues(labelName).then((labelValues) => {
|
|
||||||
this.cache.setLabelValues(labelName, labelValues);
|
|
||||||
return labelValues;
|
return labelValues;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.series(metricName).then(() => {
|
|
||||||
return this.cache.getLabelValues(labelName, metricName);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
metricMetadata(): Promise<Record<string, MetricMetadata[]>> {
|
metricMetadata(): Promise<Record<string, MetricMetadata[]>> {
|
||||||
const cachedMetadata = this.cache.getMetricMetadata();
|
const cachedMetadata = this.cache.getMetricMetadata();
|
||||||
if (cachedMetadata && Object.keys(cachedMetadata).length > 0) {
|
if (cachedMetadata && Object.keys(cachedMetadata).length > 0) {
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"data": [
|
||||||
|
"demo"
|
||||||
|
]
|
||||||
|
}
|
||||||
10
web/ui/module/codemirror-promql/src/test/labels.json
Normal file
10
web/ui/module/codemirror-promql/src/test/labels.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"data": [
|
||||||
|
"__name__",
|
||||||
|
"env",
|
||||||
|
"instance",
|
||||||
|
"job",
|
||||||
|
"state"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -34,8 +34,14 @@ export function mockPrometheusServer(): void {
|
|||||||
.get('/api/v1/label/__name__/values')
|
.get('/api/v1/label/__name__/values')
|
||||||
.query(true)
|
.query(true)
|
||||||
.replyWithFile(200, __dirname + '/metric_name.json')
|
.replyWithFile(200, __dirname + '/metric_name.json')
|
||||||
|
.get('/api/v1/label/env/values')
|
||||||
|
.query(true)
|
||||||
|
.replyWithFile(200, __dirname + '/label_env_values.json')
|
||||||
.get('/api/v1/metadata')
|
.get('/api/v1/metadata')
|
||||||
.replyWithFile(200, __dirname + '/metadata.json')
|
.replyWithFile(200, __dirname + '/metadata.json')
|
||||||
|
.post('/api/v1/labels')
|
||||||
|
.query(true)
|
||||||
|
.replyWithFile(200, __dirname + '/labels.json')
|
||||||
.get('/api/v1/series')
|
.get('/api/v1/series')
|
||||||
.query(true)
|
.query(true)
|
||||||
.replyWithFile(200, __dirname + '/alertmanager_alerts_series.json')
|
.replyWithFile(200, __dirname + '/alertmanager_alerts_series.json')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user