mirror of
https://github.com/grafana/grafana.git
synced 2025-02-20 11:48:34 -06:00
Loki: Cache extracted labels (#75842)
* add simple cache to extracted label values in completion provider
This commit is contained in:
parent
6cfe6d8688
commit
5b63cdb5b0
@ -18,7 +18,7 @@ import {
|
|||||||
extractUnwrapLabelKeysFromDataFrame,
|
extractUnwrapLabelKeysFromDataFrame,
|
||||||
} from './responseUtils';
|
} from './responseUtils';
|
||||||
import syntax, { FUNCTIONS, PIPE_PARSERS, PIPE_OPERATORS } from './syntax';
|
import syntax, { FUNCTIONS, PIPE_PARSERS, PIPE_OPERATORS } from './syntax';
|
||||||
import { LokiQuery, LokiQueryType } from './types';
|
import { ExtractedLabelKeys, LokiQuery, LokiQueryType } from './types';
|
||||||
|
|
||||||
const DEFAULT_KEYS = ['job', 'namespace'];
|
const DEFAULT_KEYS = ['job', 'namespace'];
|
||||||
const EMPTY_SELECTOR = '{}';
|
const EMPTY_SELECTOR = '{}';
|
||||||
@ -459,13 +459,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
|
|||||||
return labelValues ?? [];
|
return labelValues ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async getParserAndLabelKeys(selector: string): Promise<{
|
async getParserAndLabelKeys(selector: string): Promise<ExtractedLabelKeys> {
|
||||||
extractedLabelKeys: string[];
|
|
||||||
hasJSON: boolean;
|
|
||||||
hasLogfmt: boolean;
|
|
||||||
hasPack: boolean;
|
|
||||||
unwrapLabelKeys: string[];
|
|
||||||
}> {
|
|
||||||
const series = await this.datasource.getDataSamples({ expr: selector, refId: 'data-samples' });
|
const series = await this.datasource.getDataSamples({ expr: selector, refId: 'data-samples' });
|
||||||
|
|
||||||
if (!series.length) {
|
if (!series.length) {
|
||||||
|
@ -110,6 +110,57 @@ describe('CompletionDataProvider', () => {
|
|||||||
|
|
||||||
test('Returns the expected parser and label keys', async () => {
|
test('Returns the expected parser and label keys', async () => {
|
||||||
expect(await completionProvider.getParserAndLabelKeys('')).toEqual(parserAndLabelKeys);
|
expect(await completionProvider.getParserAndLabelKeys('')).toEqual(parserAndLabelKeys);
|
||||||
|
expect(languageProvider.getParserAndLabelKeys).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Returns the expected parser and label keys, cache duplicate query', async () => {
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('')).toEqual(parserAndLabelKeys);
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('')).toEqual(parserAndLabelKeys);
|
||||||
|
|
||||||
|
expect(languageProvider.getParserAndLabelKeys).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Returns the expected parser and label keys, unique query is not cached', async () => {
|
||||||
|
//1
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('')).toEqual(parserAndLabelKeys);
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('')).toEqual(parserAndLabelKeys);
|
||||||
|
|
||||||
|
//2
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('unique')).toEqual(parserAndLabelKeys);
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('unique')).toEqual(parserAndLabelKeys);
|
||||||
|
|
||||||
|
// 3
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('uffdah')).toEqual(parserAndLabelKeys);
|
||||||
|
|
||||||
|
// 4
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('')).toEqual(parserAndLabelKeys);
|
||||||
|
|
||||||
|
expect(languageProvider.getParserAndLabelKeys).toHaveBeenCalledTimes(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Returns the expected parser and label keys, cache size is 2', async () => {
|
||||||
|
//1
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('')).toEqual(parserAndLabelKeys);
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('')).toEqual(parserAndLabelKeys);
|
||||||
|
|
||||||
|
//2
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('unique')).toEqual(parserAndLabelKeys);
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('unique')).toEqual(parserAndLabelKeys);
|
||||||
|
|
||||||
|
// 2
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('')).toEqual(parserAndLabelKeys);
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('')).toEqual(parserAndLabelKeys);
|
||||||
|
expect(languageProvider.getParserAndLabelKeys).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
|
// 3
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('new')).toEqual(parserAndLabelKeys);
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('unique')).toEqual(parserAndLabelKeys);
|
||||||
|
expect(languageProvider.getParserAndLabelKeys).toHaveBeenCalledTimes(3);
|
||||||
|
|
||||||
|
// 4
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('')).toEqual(parserAndLabelKeys);
|
||||||
|
expect(await completionProvider.getParserAndLabelKeys('')).toEqual(parserAndLabelKeys);
|
||||||
|
expect(languageProvider.getParserAndLabelKeys).toHaveBeenCalledTimes(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Returns the expected series labels', async () => {
|
test('Returns the expected series labels', async () => {
|
||||||
|
@ -4,7 +4,7 @@ import { HistoryItem } from '@grafana/data';
|
|||||||
import { escapeLabelValueInExactSelector } from 'app/plugins/datasource/prometheus/language_utils';
|
import { escapeLabelValueInExactSelector } from 'app/plugins/datasource/prometheus/language_utils';
|
||||||
|
|
||||||
import LanguageProvider from '../../../LanguageProvider';
|
import LanguageProvider from '../../../LanguageProvider';
|
||||||
import { LokiQuery } from '../../../types';
|
import { ExtractedLabelKeys, LokiQuery } from '../../../types';
|
||||||
|
|
||||||
import { Label } from './situation';
|
import { Label } from './situation';
|
||||||
|
|
||||||
@ -16,7 +16,10 @@ export class CompletionDataProvider {
|
|||||||
constructor(
|
constructor(
|
||||||
private languageProvider: LanguageProvider,
|
private languageProvider: LanguageProvider,
|
||||||
private historyRef: HistoryRef = { current: [] }
|
private historyRef: HistoryRef = { current: [] }
|
||||||
) {}
|
) {
|
||||||
|
this.queryToLabelKeysCache = new Map();
|
||||||
|
}
|
||||||
|
private queryToLabelKeysCache: Map<string, ExtractedLabelKeys>;
|
||||||
|
|
||||||
private buildSelector(labels: Label[]): string {
|
private buildSelector(labels: Label[]): string {
|
||||||
const allLabelTexts = labels.map(
|
const allLabelTexts = labels.map(
|
||||||
@ -55,8 +58,35 @@ export class CompletionDataProvider {
|
|||||||
return data[labelName] ?? [];
|
return data[labelName] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async getParserAndLabelKeys(logQuery: string) {
|
/**
|
||||||
return await this.languageProvider.getParserAndLabelKeys(logQuery);
|
* Runs a Loki query to extract label keys from the result.
|
||||||
|
* The result is cached for the query string.
|
||||||
|
*
|
||||||
|
* Since various "situations" in the monaco code editor trigger this function, it is prone to being called multiple times for the same query
|
||||||
|
* Here is a lightweight and simple cache to avoid calling the backend multiple times for the same query.
|
||||||
|
*
|
||||||
|
* @param logQuery
|
||||||
|
*/
|
||||||
|
async getParserAndLabelKeys(logQuery: string): Promise<ExtractedLabelKeys> {
|
||||||
|
const EXTRACTED_LABEL_KEYS_MAX_CACHE_SIZE = 2;
|
||||||
|
const cachedLabelKeys = this.queryToLabelKeysCache.has(logQuery) ? this.queryToLabelKeysCache.get(logQuery) : null;
|
||||||
|
if (cachedLabelKeys) {
|
||||||
|
// cache hit! Serve stale result from cache
|
||||||
|
return cachedLabelKeys;
|
||||||
|
} else {
|
||||||
|
// If cache is larger than max size, delete the first (oldest) index
|
||||||
|
if (this.queryToLabelKeysCache.size >= EXTRACTED_LABEL_KEYS_MAX_CACHE_SIZE) {
|
||||||
|
// Make room in the cache for the fresh result by deleting the "first" index
|
||||||
|
const keys = this.queryToLabelKeysCache.keys();
|
||||||
|
const firstKey = keys.next().value;
|
||||||
|
this.queryToLabelKeysCache.delete(firstKey);
|
||||||
|
}
|
||||||
|
// Fetch a fresh result from the backend
|
||||||
|
const labelKeys = await this.languageProvider.getParserAndLabelKeys(logQuery);
|
||||||
|
// Add the result to the cache
|
||||||
|
this.queryToLabelKeysCache.set(logQuery, labelKeys);
|
||||||
|
return labelKeys;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSeriesLabels(labels: Label[]) {
|
async getSeriesLabels(labels: Label[]) {
|
||||||
|
@ -84,4 +84,12 @@ export interface ContextFilter {
|
|||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExtractedLabelKeys {
|
||||||
|
extractedLabelKeys: string[];
|
||||||
|
hasJSON: boolean;
|
||||||
|
hasLogfmt: boolean;
|
||||||
|
hasPack: boolean;
|
||||||
|
unwrapLabelKeys: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export type LokiGroupedRequest = { request: DataQueryRequest<LokiQuery>; partition: TimeRange[] };
|
export type LokiGroupedRequest = { request: DataQueryRequest<LokiQuery>; partition: TimeRange[] };
|
||||||
|
Loading…
Reference in New Issue
Block a user