mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
loki: logs-context: handle sorting for dataplane frames (#72286)
loki: context: better dataframe sorting algo
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { DataFrame, FieldType } from '@grafana/data';
|
||||
import { DataFrame, DataFrameType, FieldType } from '@grafana/data';
|
||||
|
||||
import { sortDataFrameByTime, SortDirection } from './sortDataFrame';
|
||||
|
||||
const inputFrame: DataFrame = {
|
||||
const classicFrame: DataFrame = {
|
||||
refId: 'A',
|
||||
fields: [
|
||||
{
|
||||
@@ -28,27 +28,126 @@ const inputFrame: DataFrame = {
|
||||
length: 5,
|
||||
};
|
||||
|
||||
describe('loki sortDataFrame', () => {
|
||||
const dataPlaneFrame1: DataFrame = {
|
||||
refId: 'A',
|
||||
meta: {
|
||||
type: DataFrameType.LogLines,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
type: FieldType.time,
|
||||
config: {},
|
||||
values: [1005, 1001, 1004, 1002, 1003],
|
||||
},
|
||||
{
|
||||
name: 'body',
|
||||
type: FieldType.string,
|
||||
config: {},
|
||||
values: ['line5', 'line1', 'line4', 'line2', 'line3'],
|
||||
},
|
||||
],
|
||||
length: 5,
|
||||
};
|
||||
|
||||
const dataPlaneFrame2: DataFrame = {
|
||||
refId: 'A',
|
||||
meta: {
|
||||
type: DataFrameType.LogLines,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
type: FieldType.time,
|
||||
config: {},
|
||||
values: [1000, 1000, 1000, 1000, 1000],
|
||||
nanos: [5, 1, 4, 2, 3],
|
||||
},
|
||||
{
|
||||
name: 'body',
|
||||
type: FieldType.string,
|
||||
config: {},
|
||||
values: ['line5', 'line1', 'line4', 'line2', 'line3'],
|
||||
},
|
||||
],
|
||||
length: 5,
|
||||
};
|
||||
|
||||
describe('loki sortDataFrame classic', () => {
|
||||
it('sorts a dataframe ascending', () => {
|
||||
const sortedFrame = sortDataFrameByTime(inputFrame, SortDirection.Ascending);
|
||||
const sortedFrame = sortDataFrameByTime(classicFrame, SortDirection.Ascending);
|
||||
expect(sortedFrame.length).toBe(5);
|
||||
const timeValues = sortedFrame.fields[0].values;
|
||||
const timeNanos = sortedFrame.fields[0].nanos;
|
||||
const lineValues = sortedFrame.fields[1].values;
|
||||
const tsNsValues = sortedFrame.fields[2].values;
|
||||
|
||||
expect(timeValues).toEqual([1001, 1002, 1003, 1003, 1005]);
|
||||
expect(timeNanos).toEqual([0, 0, 0, 5, 0]);
|
||||
expect(lineValues).toEqual(['line1', 'line2', 'line3', 'line4', 'line5']);
|
||||
expect(tsNsValues).toEqual([`1001000000`, `1002000000`, `1003000000`, `1003000005`, `1005000000`]);
|
||||
});
|
||||
it('sorts a dataframe descending', () => {
|
||||
const sortedFrame = sortDataFrameByTime(inputFrame, SortDirection.Descending);
|
||||
const sortedFrame = sortDataFrameByTime(classicFrame, SortDirection.Descending);
|
||||
expect(sortedFrame.length).toBe(5);
|
||||
const timeValues = sortedFrame.fields[0].values;
|
||||
const timeNanos = sortedFrame.fields[0].nanos;
|
||||
const lineValues = sortedFrame.fields[1].values;
|
||||
const tsNsValues = sortedFrame.fields[2].values;
|
||||
|
||||
expect(timeValues).toEqual([1005, 1003, 1003, 1002, 1001]);
|
||||
expect(timeNanos).toEqual([0, 5, 0, 0, 0]);
|
||||
expect(lineValues).toEqual(['line5', 'line4', 'line3', 'line2', 'line1']);
|
||||
expect(tsNsValues).toEqual([`1005000000`, `1003000005`, `1003000000`, `1002000000`, `1001000000`]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loki sortDataFrame dataplane without timefield-nanos', () => {
|
||||
it('sorts a dataframe ascending', () => {
|
||||
const sortedFrame = sortDataFrameByTime(dataPlaneFrame1, SortDirection.Ascending);
|
||||
expect(sortedFrame.length).toBe(5);
|
||||
const timeValues = sortedFrame.fields[0].values;
|
||||
const timeNanos = sortedFrame.fields[0].nanos;
|
||||
const lineValues = sortedFrame.fields[1].values;
|
||||
|
||||
expect(timeValues).toEqual([1001, 1002, 1003, 1004, 1005]);
|
||||
expect(timeNanos).toBe(undefined);
|
||||
expect(lineValues).toEqual(['line1', 'line2', 'line3', 'line4', 'line5']);
|
||||
});
|
||||
it('sorts a dataframe descending', () => {
|
||||
const sortedFrame = sortDataFrameByTime(dataPlaneFrame1, SortDirection.Descending);
|
||||
expect(sortedFrame.length).toBe(5);
|
||||
const timeValues = sortedFrame.fields[0].values;
|
||||
const lineValues = sortedFrame.fields[1].values;
|
||||
const timeNanos = sortedFrame.fields[0].nanos;
|
||||
|
||||
expect(timeValues).toEqual([1005, 1004, 1003, 1002, 1001]);
|
||||
expect(timeNanos).toBe(undefined);
|
||||
expect(lineValues).toEqual(['line5', 'line4', 'line3', 'line2', 'line1']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loki sortDataFrame dataplane with timefield-nanos', () => {
|
||||
it('sorts a dataframe ascending', () => {
|
||||
const sortedFrame = sortDataFrameByTime(dataPlaneFrame2, SortDirection.Ascending);
|
||||
expect(sortedFrame.length).toBe(5);
|
||||
const timeValues = sortedFrame.fields[0].values;
|
||||
const timeNanos = sortedFrame.fields[0].nanos;
|
||||
const lineValues = sortedFrame.fields[1].values;
|
||||
|
||||
expect(timeValues).toEqual([1000, 1000, 1000, 1000, 1000]);
|
||||
expect(timeNanos).toEqual([1, 2, 3, 4, 5]);
|
||||
expect(lineValues).toEqual(['line1', 'line2', 'line3', 'line4', 'line5']);
|
||||
});
|
||||
it('sorts a dataframe descending', () => {
|
||||
const sortedFrame = sortDataFrameByTime(dataPlaneFrame2, SortDirection.Descending);
|
||||
expect(sortedFrame.length).toBe(5);
|
||||
const timeValues = sortedFrame.fields[0].values;
|
||||
const timeNanos = sortedFrame.fields[0].nanos;
|
||||
const lineValues = sortedFrame.fields[1].values;
|
||||
|
||||
expect(timeValues).toEqual([1000, 1000, 1000, 1000, 1000]);
|
||||
expect(timeNanos).toEqual([5, 4, 3, 2, 1]);
|
||||
expect(lineValues).toEqual(['line5', 'line4', 'line3', 'line2', 'line1']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DataFrame, Field, SortedVector } from '@grafana/data';
|
||||
import { DataFrame, Field, FieldType, SortedVector } from '@grafana/data';
|
||||
|
||||
export enum SortDirection {
|
||||
Ascending,
|
||||
@@ -15,8 +15,9 @@ export enum SortDirection {
|
||||
// - the third row will become the first
|
||||
// - the first row will become the second
|
||||
// - the second row will become the third
|
||||
function makeIndex(field: Field<string>, dir: SortDirection): number[] {
|
||||
const fieldValues: string[] = field.values;
|
||||
function makeIndex(field: Field<number>, dir: SortDirection): number[] {
|
||||
const fieldValues: number[] = field.values;
|
||||
const { nanos } = field;
|
||||
|
||||
// we first build an array which is [0,1,2,3....]
|
||||
const index = Array(fieldValues.length);
|
||||
@@ -39,33 +40,53 @@ function makeIndex(field: Field<string>, dir: SortDirection): number[] {
|
||||
return isAsc ? 1 : -1;
|
||||
}
|
||||
|
||||
// the millisecond timestamps are equal,
|
||||
// compare the nanosecond part, if available
|
||||
|
||||
if (nanos === undefined) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const nanoA = nanos[a];
|
||||
const nanoB = nanos[b];
|
||||
|
||||
if (nanoA < nanoB) {
|
||||
return isAsc ? -1 : 1;
|
||||
}
|
||||
|
||||
if (nanoA > nanoB) {
|
||||
return isAsc ? 1 : -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
// sort a dataframe that is in the Loki format ascending or descending,
|
||||
// based on the nanosecond-timestamp
|
||||
// sort a dataframe that is in the Loki dataframe format ascending or desceding based on time,
|
||||
// with nanosecond precision.
|
||||
export function sortDataFrameByTime(frame: DataFrame, dir: SortDirection): DataFrame {
|
||||
const { fields, ...rest } = frame;
|
||||
|
||||
// we use the approach used in @grafana/data/sortDataframe.
|
||||
// we cannot use it directly, because our tsNs field has a type=time,
|
||||
// so we have to build the `index` manually.
|
||||
// we cannot use it directly, because it does not take `.nanos` into account
|
||||
// (see https://github.com/grafana/grafana/issues/72351).
|
||||
// we can switch to to @grafana/data/sortDataframe when the issue is fixed.
|
||||
|
||||
const tsNsField = fields.find((field) => field.name === 'tsNs');
|
||||
if (tsNsField === undefined) {
|
||||
throw new Error('missing nanosecond-timestamp field. should never happen');
|
||||
const timeField = fields.find((field) => field.type === FieldType.time);
|
||||
if (timeField === undefined) {
|
||||
throw new Error('missing timestamp field. should never happen');
|
||||
}
|
||||
|
||||
const index = makeIndex(tsNsField, dir);
|
||||
const index = makeIndex(timeField, dir);
|
||||
|
||||
return {
|
||||
...rest,
|
||||
fields: fields.map((field) => ({
|
||||
...field,
|
||||
values: new SortedVector(field.values, index).toArray(),
|
||||
nanos: field.nanos === undefined ? undefined : new SortedVector(field.nanos, index).toArray(),
|
||||
})),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user