Loki: Add timeRange to labels requests in LogContext to reduce loading times (#79478)

Loki: Add timeRange to labels requests
This commit is contained in:
Sven Grossmann 2023-12-14 10:14:02 +01:00 committed by GitHub
parent 53863c52ca
commit a1ec5be730
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 74 additions and 10 deletions

View File

@ -1,6 +1,13 @@
import { of } from 'rxjs'; import { of } from 'rxjs';
import { DataQueryResponse, FieldType, LogRowContextQueryDirection, LogRowModel, createDataFrame } from '@grafana/data'; import {
DataQueryResponse,
FieldType,
LogRowContextQueryDirection,
LogRowModel,
createDataFrame,
dateTime,
} from '@grafana/data';
import LokiLanguageProvider from './LanguageProvider'; import LokiLanguageProvider from './LanguageProvider';
import { import {
@ -50,6 +57,7 @@ const defaultLogRow = {
}), }),
labels: { bar: 'baz', foo: 'uniqueParsedLabel', xyz: 'abc' }, labels: { bar: 'baz', foo: 'uniqueParsedLabel', xyz: 'abc' },
uid: '1', uid: '1',
timeEpochMs: new Date().getTime(),
} as unknown as LogRowModel; } as unknown as LogRowModel;
describe('LogContextProvider', () => { describe('LogContextProvider', () => {
@ -83,7 +91,12 @@ describe('LogContextProvider', () => {
expect(logContextProvider.getInitContextFilters).toBeCalled(); expect(logContextProvider.getInitContextFilters).toBeCalled();
expect(logContextProvider.getInitContextFilters).toHaveBeenCalledWith( expect(logContextProvider.getInitContextFilters).toHaveBeenCalledWith(
{ bar: 'baz', foo: 'uniqueParsedLabel', xyz: 'abc' }, { bar: 'baz', foo: 'uniqueParsedLabel', xyz: 'abc' },
{ expr: '{bar="baz"}', refId: 'A' } { expr: '{bar="baz"}', refId: 'A' },
{
from: dateTime(defaultLogRow.timeEpochMs),
to: dateTime(defaultLogRow.timeEpochMs),
raw: { from: dateTime(defaultLogRow.timeEpochMs), to: dateTime(defaultLogRow.timeEpochMs) },
}
); );
expect(logContextProvider.appliedContextFilters).toHaveLength(1); expect(logContextProvider.appliedContextFilters).toHaveLength(1);
}); });
@ -371,13 +384,24 @@ describe('LogContextProvider', () => {
}); });
}); });
describe('getInitContextFiltersFromLabels', () => { describe('getInitContextFilters', () => {
describe('query with no parser', () => { describe('query with no parser', () => {
const queryWithoutParser: LokiQuery = { const queryWithoutParser: LokiQuery = {
expr: '{bar="baz"}', expr: '{bar="baz"}',
refId: 'A', refId: 'A',
}; };
const queryWithParser: LokiQuery = {
expr: '{bar="baz"} | logfmt',
refId: 'A',
};
const timeRange = {
from: dateTime(defaultLogRow.timeEpochMs),
to: dateTime(defaultLogRow.timeEpochMs),
raw: { from: dateTime(defaultLogRow.timeEpochMs), to: dateTime(defaultLogRow.timeEpochMs) },
};
it('should correctly create contextFilters', async () => { it('should correctly create contextFilters', async () => {
const filters = await logContextProvider.getInitContextFilters(defaultLogRow.labels, queryWithoutParser); const filters = await logContextProvider.getInitContextFilters(defaultLogRow.labels, queryWithoutParser);
expect(filters).toEqual([ expect(filters).toEqual([
@ -396,6 +420,21 @@ describe('LogContextProvider', () => {
const filters = await logContextProvider.getInitContextFilters({}, queryWithoutParser); const filters = await logContextProvider.getInitContextFilters({}, queryWithoutParser);
expect(filters).toEqual([]); expect(filters).toEqual([]);
}); });
it('should call fetchSeriesLabels if parser', async () => {
await logContextProvider.getInitContextFilters(defaultLogRow.labels, queryWithParser);
expect(defaultLanguageProviderMock.fetchSeriesLabels).toBeCalled();
});
it('should call fetchSeriesLabels with given timerange', async () => {
await logContextProvider.getInitContextFilters(defaultLogRow.labels, queryWithParser, timeRange);
expect(defaultLanguageProviderMock.fetchSeriesLabels).toBeCalledWith(`{bar="baz"}`, { timeRange });
});
it('should call `languageProvider.start` if no parser with given timerange', async () => {
await logContextProvider.getInitContextFilters(defaultLogRow.labels, queryWithoutParser, timeRange);
expect(defaultLanguageProviderMock.start).toBeCalledWith(timeRange);
});
}); });
describe('query with parser', () => { describe('query with parser', () => {

View File

@ -13,6 +13,7 @@ import {
toUtc, toUtc,
LogRowContextQueryDirection, LogRowContextQueryDirection,
LogRowContextOptions, LogRowContextOptions,
dateTime,
} from '@grafana/data'; } from '@grafana/data';
import { LabelParser, LabelFilter, LineFilters, PipelineStage, Logfmt, Json } from '@grafana/lezer-logql'; import { LabelParser, LabelFilter, LineFilters, PipelineStage, Logfmt, Json } from '@grafana/lezer-logql';
import { Labels } from '@grafana/schema'; import { Labels } from '@grafana/schema';
@ -58,7 +59,13 @@ export class LogContextProvider {
// This happens only on initial load, when user haven't applied any filters yet // This happens only on initial load, when user haven't applied any filters yet
// We need to get the initial filters from the row labels // We need to get the initial filters from the row labels
if (this.appliedContextFilters.length === 0) { if (this.appliedContextFilters.length === 0) {
const filters = (await this.getInitContextFilters(row.labels, origQuery)).filter((filter) => filter.enabled); const filters = (
await this.getInitContextFilters(row.labels, origQuery, {
from: dateTime(row.timeEpochMs),
to: dateTime(row.timeEpochMs),
raw: { from: dateTime(row.timeEpochMs), to: dateTime(row.timeEpochMs) },
})
).filter((filter) => filter.enabled);
this.appliedContextFilters = filters; this.appliedContextFilters = filters;
} }
@ -285,7 +292,7 @@ export class LogContextProvider {
); );
}; };
getInitContextFilters = async (labels: Labels, query?: LokiQuery) => { getInitContextFilters = async (labels: Labels, query?: LokiQuery, timeRange?: TimeRange) => {
if (!query || isEmpty(labels)) { if (!query || isEmpty(labels)) {
return []; return [];
} }
@ -296,13 +303,13 @@ export class LogContextProvider {
if (!isQueryWithParser(query.expr).queryWithParser) { if (!isQueryWithParser(query.expr).queryWithParser) {
// If there is no parser, we use getLabelKeys because it has better caching // If there is no parser, we use getLabelKeys because it has better caching
// and all labels should already be fetched // and all labels should already be fetched
await this.datasource.languageProvider.start(); await this.datasource.languageProvider.start(timeRange);
allLabels = this.datasource.languageProvider.getLabelKeys(); allLabels = this.datasource.languageProvider.getLabelKeys();
} else { } else {
// If we have parser, we use fetchSeriesLabels to fetch actual labels for selected stream // If we have parser, we use fetchSeriesLabels to fetch actual labels for selected stream
const stream = getStreamSelectorsFromQuery(query.expr); const stream = getStreamSelectorsFromQuery(query.expr);
// We are using stream[0] as log query can always have just 1 stream selector // We are using stream[0] as log query can always have just 1 stream selector
const series = await this.datasource.languageProvider.fetchSeriesLabels(stream[0]); const series = await this.datasource.languageProvider.fetchSeriesLabels(stream[0], { timeRange });
allLabels = Object.keys(series); allLabels = Object.keys(series);
} }

View File

@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event';
import React from 'react'; import React from 'react';
import { selectOptionInTest } from 'test/helpers/selectOptionInTest'; import { selectOptionInTest } from 'test/helpers/selectOptionInTest';
import { LogRowModel } from '@grafana/data'; import { LogRowModel, dateTime } from '@grafana/data';
import { LogContextProvider, SHOULD_INCLUDE_PIPELINE_OPERATIONS } from '../LogContextProvider'; import { LogContextProvider, SHOULD_INCLUDE_PIPELINE_OPERATIONS } from '../LogContextProvider';
import { ContextFilter, LokiQuery } from '../types'; import { ContextFilter, LokiQuery } from '../types';
@ -41,6 +41,7 @@ const setupProps = (): LokiContextUiProps => {
label1: 'value1', label1: 'value1',
label3: 'value3', label3: 'value3',
}, },
timeEpochMs: new Date().getTime(),
} as unknown as LogRowModel, } as unknown as LogRowModel,
onClose: jest.fn(), onClose: jest.fn(),
origQuery: { origQuery: {
@ -128,6 +129,19 @@ describe('LokiContextUi', () => {
}); });
}); });
it('calls `getInitContextFilters` with the right set of parameters', async () => {
const props = setupProps();
render(<LokiContextUi {...props} />);
await waitFor(() => {
expect(props.logContextProvider.getInitContextFilters).toHaveBeenCalledWith(props.row.labels, props.origQuery, {
from: dateTime(props.row.timeEpochMs),
to: dateTime(props.row.timeEpochMs),
raw: { from: dateTime(props.row.timeEpochMs), to: dateTime(props.row.timeEpochMs) },
});
});
});
it('finds label1 as a real label', async () => { it('finds label1 as a real label', async () => {
const props = setupProps(); const props = setupProps();
render(<LokiContextUi {...props} />); render(<LokiContextUi {...props} />);

View File

@ -2,7 +2,7 @@ import { css } from '@emotion/css';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useAsync } from 'react-use'; import { useAsync } from 'react-use';
import { GrafanaTheme2, LogRowModel, renderMarkdown, SelectableValue } from '@grafana/data'; import { dateTime, GrafanaTheme2, LogRowModel, renderMarkdown, SelectableValue } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime'; import { reportInteraction } from '@grafana/runtime';
import { import {
Button, Button,
@ -199,7 +199,11 @@ export function LokiContextUi(props: LokiContextUiProps) {
useAsync(async () => { useAsync(async () => {
setLoading(true); setLoading(true);
const initContextFilters = await logContextProvider.getInitContextFilters(row.labels, origQuery); const initContextFilters = await logContextProvider.getInitContextFilters(row.labels, origQuery, {
from: dateTime(row.timeEpochMs),
to: dateTime(row.timeEpochMs),
raw: { from: dateTime(row.timeEpochMs), to: dateTime(row.timeEpochMs) },
});
setContextFilters(initContextFilters); setContextFilters(initContextFilters);
setInitialized(true); setInitialized(true);