mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
* Added exemplar support Signed-off-by: Levi Harrison <git@leviharrison.dev> * Modified tests Signed-off-by: Levi Harrison <git@leviharrison.dev> * Fix eslint suggestions Signed-off-by: Levi Harrison <git@leviharrison.dev> * Address review comments Signed-off-by: Levi Harrison <git@leviharrison.dev> * Fixed undefined data property error Signed-off-by: Levi Harrison <git@leviharrison.dev> * Added series label section to tooltip Signed-off-by: Levi Harrison <git@leviharrison.dev> * Fixed spacing Signed-off-by: GitHub <noreply@github.com> Signed-off-by: Levi Harrison <git@leviharrison.dev> * Fixed tests Signed-off-by: Levi Harrison <git@leviharrison.dev> * Added exemplar info Signed-off-by: Levi Harrison <git@leviharrison.dev> * Changed exemplar symbol Signed-off-by: Levi Harrison <git@leviharrison.dev> Co-authored-by: Julius Volz <julius.volz@gmail.com> Signed-off-by: Levi Harrison <git@leviharrison.dev> * Hide selected exemplar info when 'Show Exemplars' is unchecked Signed-off-by: Levi Harrison <git@leviharrison.dev> * Include series labels in exemplar info Signed-off-by: Levi Harrison <git@leviharrison.dev> * De-densify exemplars Signed-off-by: Levi Harrison <git@leviharrison.dev> * Moved showExemplars to per-panel control Signed-off-by: Levi Harrison <git@leviharrison.dev> * Eslint fixes Signed-off-by: Levi Harrison <git@leviharrison.dev> * Address review comments Signed-off-by: Levi Harrison <git@leviharrison.dev> * Fixed tests Signed-off-by: Levi Harrison <git@leviharrison.dev> * Fix state bug Signed-off-by: Levi Harrison <git@leviharrison.dev> * Removed unused object Signed-off-by: Levi Harrison <git@leviharrison.dev> * Fix eslint Signed-off-by: Levi Harrison <git@leviharrison.dev> * Encoded 'show_exemplars' in url Signed-off-by: Levi Harrison <git@leviharrison.dev> Co-authored-by: Julius Volz <julius.volz@gmail.com>
336 lines
11 KiB
TypeScript
336 lines
11 KiB
TypeScript
import moment from 'moment';
|
|
|
|
import {
|
|
escapeHTML,
|
|
metricToSeriesName,
|
|
formatTime,
|
|
parseTime,
|
|
formatDuration,
|
|
parseDuration,
|
|
humanizeDuration,
|
|
formatRelative,
|
|
now,
|
|
toQueryString,
|
|
encodePanelOptionsToQueryString,
|
|
parseOption,
|
|
decodePanelOptionsFromQueryString,
|
|
parsePrometheusFloat,
|
|
} from '.';
|
|
import { PanelType } from '../pages/graph/Panel';
|
|
|
|
describe('Utils', () => {
|
|
describe('escapeHTML', (): void => {
|
|
it('escapes html sequences', () => {
|
|
expect(escapeHTML(`<strong>'example'&"another/example"</strong>`)).toEqual(
|
|
'<strong>'example'&"another/example"</strong>'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('metricToSeriesName', () => {
|
|
it('returns "{}" if labels is empty', () => {
|
|
const labels = {};
|
|
expect(metricToSeriesName(labels)).toEqual('{}');
|
|
});
|
|
it('returns "metric_name{}" if labels only contains __name__', () => {
|
|
const labels = { __name__: 'metric_name' };
|
|
expect(metricToSeriesName(labels)).toEqual('metric_name{}');
|
|
});
|
|
it('returns "{label1=value_1, ..., labeln=value_n} if there are many labels and no name', () => {
|
|
const labels = { label1: 'value_1', label2: 'value_2', label3: 'value_3' };
|
|
expect(metricToSeriesName(labels)).toEqual('{label1="value_1", label2="value_2", label3="value_3"}');
|
|
});
|
|
it('returns "metric_name{label1=value_1, ... ,labeln=value_n}" if there are many labels and a name', () => {
|
|
const labels = {
|
|
__name__: 'metric_name',
|
|
label1: 'value_1',
|
|
label2: 'value_2',
|
|
label3: 'value_3',
|
|
};
|
|
expect(metricToSeriesName(labels)).toEqual('metric_name{label1="value_1", label2="value_2", label3="value_3"}');
|
|
});
|
|
});
|
|
|
|
describe('Time format', () => {
|
|
describe('formatTime', () => {
|
|
it('returns a time string representing the time in seconds', () => {
|
|
expect(formatTime(1572049380000)).toEqual('2019-10-26 00:23:00');
|
|
expect(formatTime(0)).toEqual('1970-01-01 00:00:00');
|
|
});
|
|
});
|
|
|
|
describe('parseTime', () => {
|
|
it('returns a time string representing the time in seconds', () => {
|
|
expect(parseTime('2019-10-26 00:23')).toEqual(1572049380000);
|
|
expect(parseTime('1970-01-01 00:00')).toEqual(0);
|
|
expect(parseTime('0001-01-01T00:00:00Z')).toEqual(-62135596800000);
|
|
});
|
|
});
|
|
|
|
describe('parseDuration and formatDuration', () => {
|
|
describe('should parse and format durations correctly', () => {
|
|
const tests: { input: string; output: number; expectedString?: string }[] = [
|
|
{
|
|
input: '0',
|
|
output: 0,
|
|
expectedString: '0s',
|
|
},
|
|
{
|
|
input: '0w',
|
|
output: 0,
|
|
expectedString: '0s',
|
|
},
|
|
{
|
|
input: '0s',
|
|
output: 0,
|
|
},
|
|
{
|
|
input: '324ms',
|
|
output: 324,
|
|
},
|
|
{
|
|
input: '3s',
|
|
output: 3 * 1000,
|
|
},
|
|
{
|
|
input: '5m',
|
|
output: 5 * 60 * 1000,
|
|
},
|
|
{
|
|
input: '1h',
|
|
output: 60 * 60 * 1000,
|
|
},
|
|
{
|
|
input: '4d',
|
|
output: 4 * 24 * 60 * 60 * 1000,
|
|
},
|
|
{
|
|
input: '4d1h',
|
|
output: 4 * 24 * 60 * 60 * 1000 + 1 * 60 * 60 * 1000,
|
|
},
|
|
{
|
|
input: '14d',
|
|
output: 14 * 24 * 60 * 60 * 1000,
|
|
expectedString: '2w',
|
|
},
|
|
{
|
|
input: '3w',
|
|
output: 3 * 7 * 24 * 60 * 60 * 1000,
|
|
},
|
|
{
|
|
input: '3w2d1h',
|
|
output: 3 * 7 * 24 * 60 * 60 * 1000 + 2 * 24 * 60 * 60 * 1000 + 60 * 60 * 1000,
|
|
expectedString: '23d1h',
|
|
},
|
|
{
|
|
input: '1y2w3d4h5m6s7ms',
|
|
output:
|
|
1 * 365 * 24 * 60 * 60 * 1000 +
|
|
2 * 7 * 24 * 60 * 60 * 1000 +
|
|
3 * 24 * 60 * 60 * 1000 +
|
|
4 * 60 * 60 * 1000 +
|
|
5 * 60 * 1000 +
|
|
6 * 1000 +
|
|
7,
|
|
expectedString: '382d4h5m6s7ms',
|
|
},
|
|
];
|
|
|
|
tests.forEach(t => {
|
|
it(t.input, () => {
|
|
const d = parseDuration(t.input);
|
|
expect(d).toEqual(t.output);
|
|
expect(formatDuration(d!)).toEqual(t.expectedString || t.input);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('should fail to parse invalid durations', () => {
|
|
const tests = ['1', '1y1m1d', '-1w', '1.5d', 'd', ''];
|
|
|
|
tests.forEach(t => {
|
|
it(t, () => {
|
|
expect(parseDuration(t)).toBe(null);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('humanizeDuration', () => {
|
|
it('humanizes zero', () => {
|
|
expect(humanizeDuration(0)).toEqual('0s');
|
|
});
|
|
it('humanizes milliseconds', () => {
|
|
expect(humanizeDuration(1.234567)).toEqual('1.235ms');
|
|
expect(humanizeDuration(12.34567)).toEqual('12.346ms');
|
|
expect(humanizeDuration(123.45678)).toEqual('123.457ms');
|
|
expect(humanizeDuration(123)).toEqual('123.000ms');
|
|
});
|
|
it('humanizes seconds', () => {
|
|
expect(humanizeDuration(12340)).toEqual('12.340s');
|
|
});
|
|
it('humanizes minutes', () => {
|
|
expect(humanizeDuration(1234567)).toEqual('20m 34s');
|
|
});
|
|
|
|
it('humanizes hours', () => {
|
|
expect(humanizeDuration(12345678)).toEqual('3h 25m 45s');
|
|
});
|
|
|
|
it('humanizes days', () => {
|
|
expect(humanizeDuration(123456789)).toEqual('1d 10h 17m 36s');
|
|
expect(humanizeDuration(123456789000)).toEqual('1428d 21h 33m 9s');
|
|
});
|
|
it('takes sign into account', () => {
|
|
expect(humanizeDuration(-123456789000)).toEqual('-1428d 21h 33m 9s');
|
|
});
|
|
});
|
|
|
|
describe('formatRelative', () => {
|
|
it('renders never for pre-beginning-of-time strings', () => {
|
|
expect(formatRelative('0001-01-01T00:00:00Z', now())).toEqual('Never');
|
|
});
|
|
it('renders a humanized duration for sane durations', () => {
|
|
expect(formatRelative('2019-11-04T09:15:29.578701-07:00', parseTime('2019-11-04T09:15:35.8701-07:00'))).toEqual(
|
|
'6.292s ago'
|
|
);
|
|
expect(formatRelative('2019-11-04T09:15:35.8701-07:00', parseTime('2019-11-04T09:15:29.578701-07:00'))).toEqual(
|
|
'-6.292s ago'
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('URL Params', () => {
|
|
const panels: any = [
|
|
{
|
|
key: '0',
|
|
options: {
|
|
endTime: 1572046620000,
|
|
expr: 'rate(node_cpu_seconds_total{mode="system"}[1m])',
|
|
range: 60 * 60 * 1000,
|
|
resolution: null,
|
|
stacked: false,
|
|
type: PanelType.Graph,
|
|
},
|
|
},
|
|
{
|
|
key: '1',
|
|
options: {
|
|
endTime: null,
|
|
expr: 'node_filesystem_avail_bytes',
|
|
range: 60 * 60 * 1000,
|
|
resolution: null,
|
|
stacked: false,
|
|
type: PanelType.Table,
|
|
},
|
|
},
|
|
];
|
|
const query =
|
|
'?g0.expr=rate(node_cpu_seconds_total%7Bmode%3D%22system%22%7D%5B1m%5D)&g0.tab=0&g0.stacked=0&g0.show_exemplars=0&g0.range_input=1h&g0.end_input=2019-10-25%2023%3A37%3A00&g0.moment_input=2019-10-25%2023%3A37%3A00&g1.expr=node_filesystem_avail_bytes&g1.tab=1&g1.stacked=0&g1.show_exemplars=0&g1.range_input=1h';
|
|
|
|
describe('decodePanelOptionsFromQueryString', () => {
|
|
it('returns [] when query is empty', () => {
|
|
expect(decodePanelOptionsFromQueryString('')).toEqual([]);
|
|
});
|
|
it('returns and array of parsed params when query string is non-empty', () => {
|
|
expect(decodePanelOptionsFromQueryString(query)).toMatchObject(panels);
|
|
});
|
|
});
|
|
|
|
describe('parseOption', () => {
|
|
it('should return empty object for invalid param', () => {
|
|
expect(parseOption('invalid_prop=foo')).toEqual({});
|
|
});
|
|
it('should parse expr param', () => {
|
|
expect(parseOption('expr=foo')).toEqual({ expr: 'foo' });
|
|
});
|
|
it('should parse stacked', () => {
|
|
expect(parseOption('stacked=1')).toEqual({ stacked: true });
|
|
});
|
|
it('should parse end_input', () => {
|
|
expect(parseOption('end_input=2019-10-25%2023%3A37')).toEqual({ endTime: moment.utc('2019-10-25 23:37').valueOf() });
|
|
});
|
|
it('should parse moment_input', () => {
|
|
expect(parseOption('moment_input=2019-10-25%2023%3A37')).toEqual({
|
|
endTime: moment.utc('2019-10-25 23:37').valueOf(),
|
|
});
|
|
});
|
|
|
|
describe('step_input', () => {
|
|
it('should return step_input parsed if > 0', () => {
|
|
expect(parseOption('step_input=2')).toEqual({ resolution: 2 });
|
|
});
|
|
it('should return empty object if step is equal 0', () => {
|
|
expect(parseOption('step_input=0')).toEqual({});
|
|
});
|
|
});
|
|
|
|
describe('range_input', () => {
|
|
it('should return range parsed if its not null', () => {
|
|
expect(parseOption('range_input=2h')).toEqual({ range: 2 * 60 * 60 * 1000 });
|
|
});
|
|
it('should return empty object for invalid value', () => {
|
|
expect(parseOption('range_input=h')).toEqual({});
|
|
});
|
|
});
|
|
|
|
describe('Parse type param', () => {
|
|
it('should return panel type "graph" if tab=0', () => {
|
|
expect(parseOption('tab=0')).toEqual({ type: PanelType.Graph });
|
|
});
|
|
it('should return panel type "table" if tab=1', () => {
|
|
expect(parseOption('tab=1')).toEqual({ type: PanelType.Table });
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('toQueryString', () => {
|
|
it('should generate query string from panel options', () => {
|
|
expect(
|
|
toQueryString({
|
|
id: 'asdf',
|
|
key: '0',
|
|
options: {
|
|
expr: 'foo',
|
|
type: PanelType.Graph,
|
|
stacked: true,
|
|
showExemplars: true,
|
|
range: 0,
|
|
endTime: null,
|
|
resolution: 1,
|
|
},
|
|
})
|
|
).toEqual('g0.expr=foo&g0.tab=0&g0.stacked=1&g0.show_exemplars=1&g0.range_input=0s&g0.step_input=1');
|
|
});
|
|
});
|
|
|
|
describe('encodePanelOptionsToQueryString', () => {
|
|
it('returns ? when panels is empty', () => {
|
|
expect(encodePanelOptionsToQueryString([])).toEqual('?');
|
|
});
|
|
it('returns an encoded query string otherwise', () => {
|
|
expect(encodePanelOptionsToQueryString(panels)).toEqual(query);
|
|
});
|
|
});
|
|
|
|
describe('parsePrometheusFloat', () => {
|
|
it('returns Inf when param is Inf', () => {
|
|
expect(parsePrometheusFloat('Inf')).toEqual('Inf');
|
|
});
|
|
it('returns +Inf when param is +Inf', () => {
|
|
expect(parsePrometheusFloat('+Inf')).toEqual('+Inf');
|
|
});
|
|
it('returns -Inf when param is -Inf', () => {
|
|
expect(parsePrometheusFloat('-Inf')).toEqual('-Inf');
|
|
});
|
|
it('returns 17 when param is 1.7e+01', () => {
|
|
expect(parsePrometheusFloat('1.7e+01')).toEqual(17);
|
|
});
|
|
it('returns -17 when param is -1.7e+01', () => {
|
|
expect(parsePrometheusFloat('-1.7e+01')).toEqual(-17);
|
|
});
|
|
});
|
|
});
|
|
});
|