The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/packages/grafana-ui/src/components/GraphNG/nullInsertThreshold.test.ts

216 lines
7.3 KiB

import { ArrayVector, FieldType, MutableDataFrame } from '@grafana/data';
import { applyNullInsertThreshold } from './nullInsertThreshold';
function randInt(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function genFrame() {
let fieldCount = 10;
let valueCount = 3000;
let step = 1000;
let skipProb = 0.5;
let skipSteps = [1, 5]; // min, max
let allValues = Array(fieldCount);
allValues[0] = Array(valueCount);
for (let i = 0, curStep = Date.now(); i < valueCount; i++) {
curStep = allValues[0][i] = curStep + step * (Math.random() < skipProb ? randInt(skipSteps[0], skipSteps[1]) : 1);
}
for (let fi = 1; fi < fieldCount; fi++) {
let values = Array(valueCount);
for (let i = 0; i < valueCount; i++) {
values[i] = Math.random() * 100;
}
allValues[fi] = values;
}
return {
length: valueCount,
fields: allValues.map((values, i) => {
return {
name: 'A-' + i,
type: i === 0 ? FieldType.time : FieldType.number,
config: {
interval: i === 0 ? step : null,
},
values: new ArrayVector(values),
};
}),
};
}
describe('nullInsertThreshold Transformer', () => {
test('should insert nulls at +threshold between adjacent > threshold: 1', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, values: [1, 3, 10] },
{ name: 'One', type: FieldType.number, config: { custom: { insertNulls: 1 } }, values: [4, 6, 8] },
{ name: 'Two', type: FieldType.string, config: { custom: { insertNulls: 1 } }, values: ['a', 'b', 'c'] },
],
});
const result = applyNullInsertThreshold(df);
expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3, 4, 10]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, null, 6, null, 8]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, 'c']);
});
test('should insert nulls at +threshold between adjacent > threshold: 2', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, values: [5, 7, 11] },
{ name: 'One', type: FieldType.number, config: { custom: { insertNulls: 2 } }, values: [4, 6, 8] },
{ name: 'Two', type: FieldType.string, config: { custom: { insertNulls: 2 } }, values: ['a', 'b', 'c'] },
],
});
const result = applyNullInsertThreshold(df);
expect(result.fields[0].values.toArray()).toStrictEqual([5, 7, 9, 11]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, 6, null, 8]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', 'b', null, 'c']);
});
test('should insert nulls at +interval between adjacent > interval: 1', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, config: { interval: 1 }, values: [1, 3, 10] },
{ name: 'One', type: FieldType.number, values: [4, 6, 8] },
{ name: 'Two', type: FieldType.string, values: ['a', 'b', 'c'] },
],
});
const result = applyNullInsertThreshold(df);
expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3, 4, 10]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, null, 6, null, 8]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, 'c']);
});
test('should insert trailing null at end +interval when timeRange.to.valueOf() exceeds threshold', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, config: { interval: 1 }, values: [1, 3, 10] },
{ name: 'One', type: FieldType.number, values: [4, 6, 8] },
{ name: 'Two', type: FieldType.string, values: ['a', 'b', 'c'] },
],
});
const result = applyNullInsertThreshold(df, null, 13);
expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3, 4, 10, 11]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, null, 6, null, 8, null]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, 'c', null]);
});
// TODO: make this work
test.skip('should insert nulls at +threshold (when defined) instead of +interval', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, config: { interval: 2 }, values: [5, 7, 11] },
{ name: 'One', type: FieldType.number, config: { custom: { insertNulls: 1 } }, values: [4, 6, 8] },
{ name: 'Two', type: FieldType.string, config: { custom: { insertNulls: 1 } }, values: ['a', 'b', 'c'] },
],
});
const result = applyNullInsertThreshold(df);
expect(result.fields[0].values.toArray()).toStrictEqual([5, 6, 7, 8, 11]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, null, 6, null, 8]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, 'c']);
});
test('should noop on fewer than two values', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, config: { interval: 1 }, values: [1] },
{ name: 'Value', type: FieldType.number, values: [1] },
],
});
const result = applyNullInsertThreshold(df);
expect(result).toBe(df);
});
test('should noop on invalid threshold', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, values: [1, 2, 4] },
{ name: 'Value', type: FieldType.number, config: { custom: { insertNulls: -1 } }, values: [1, 1, 1] },
],
});
const result = applyNullInsertThreshold(df);
expect(result).toBe(df);
});
test('should noop on invalid interval', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, config: { interval: -1 }, values: [1, 2, 4] },
{ name: 'Value', type: FieldType.number, values: [1, 1, 1] },
],
});
const result = applyNullInsertThreshold(df);
expect(result).toBe(df);
});
test('should noop when no missing steps', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, config: { interval: 1 }, values: [1, 2, 3] },
{ name: 'Value', type: FieldType.number, values: [1, 1, 1] },
],
});
const result = applyNullInsertThreshold(df);
expect(result).toBe(df);
});
test('should noop when refFieldName not found', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, config: { interval: 1 }, values: [1, 2, 5] },
{ name: 'Value', type: FieldType.number, values: [1, 1, 1] },
],
});
const result = applyNullInsertThreshold(df, 'Time2');
expect(result).toBe(df);
});
// Leave this test skipped - it should be run manually
test.skip('perf stress test should be <= 10ms', () => {
// 10 fields x 3,000 values with 50% skip (output = 10 fields x 6,000 values)
let bigFrameA = genFrame();
// eslint-disable-next-line no-console
console.time('insertValues-10x3k');
applyNullInsertThreshold(bigFrameA);
// eslint-disable-next-line no-console
console.timeEnd('insertValues-10x3k');
});
});