162 lines
4.3 KiB
TypeScript
162 lines
4.3 KiB
TypeScript
import { describeInterval } from '@grafana/data/src/datetime/rangeutil';
|
|
|
|
import { TimeOptions } from '../types/time';
|
|
|
|
/**
|
|
* ⚠️
|
|
* Some of these functions might be confusing, but there is a significant difference between "Golang duration",
|
|
* supported by the time.ParseDuration() function and "prometheus duration" which is similar but does not support anything
|
|
* smaller than seconds and adds the following supported units: "d, w, y"
|
|
*/
|
|
|
|
export function parseInterval(value: string): [number, string] {
|
|
const match = value.match(/(\d+)(\w+)/);
|
|
if (match) {
|
|
return [Number(match[1]), match[2]];
|
|
}
|
|
throw new Error(`Invalid interval description: ${value}`);
|
|
}
|
|
|
|
export function intervalToSeconds(interval: string): number {
|
|
const { sec, count } = describeInterval(interval);
|
|
return sec * count;
|
|
}
|
|
|
|
export const timeOptions = Object.entries(TimeOptions).map(([key, value]) => ({
|
|
label: key[0].toUpperCase() + key.slice(1),
|
|
value: value,
|
|
}));
|
|
|
|
export function isValidPrometheusDuration(duration: string): boolean {
|
|
try {
|
|
parsePrometheusDuration(duration);
|
|
return true;
|
|
} catch (err) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const PROMETHEUS_SUFFIX_MULTIPLIER: Record<string, number> = {
|
|
ms: 1,
|
|
s: 1000,
|
|
m: 60 * 1000,
|
|
h: 60 * 60 * 1000,
|
|
d: 24 * 60 * 60 * 1000,
|
|
w: 7 * 24 * 60 * 60 * 1000,
|
|
y: 365 * 24 * 60 * 60 * 1000,
|
|
};
|
|
|
|
const DURATION_REGEXP = new RegExp(/^(?:(?<value>\d+)(?<type>ms|s|m|h|d|w|y))|0$/);
|
|
const INVALID_FORMAT = new Error(
|
|
`Must be of format "(number)(unit)", for example "1m", or just "0". Available units: ${Object.values(
|
|
TimeOptions
|
|
).join(', ')}`
|
|
);
|
|
|
|
/**
|
|
* According to https://prometheus.io/docs/alerting/latest/configuration/#configuration-file
|
|
* see <duration>
|
|
*
|
|
* @returns Duration in milliseconds
|
|
*/
|
|
export function parsePrometheusDuration(duration: string): number {
|
|
let input = duration;
|
|
const parts: Array<[number, string]> = [];
|
|
|
|
function matchDuration(part: string) {
|
|
const match = DURATION_REGEXP.exec(part);
|
|
const hasValueAndType = match?.groups?.value && match?.groups?.type;
|
|
|
|
if (!match || !hasValueAndType) {
|
|
throw INVALID_FORMAT;
|
|
}
|
|
|
|
if (match && match.groups?.value && match.groups?.type) {
|
|
input = input.replace(match[0], '');
|
|
parts.push([Number(match.groups.value), match.groups.type]);
|
|
}
|
|
|
|
if (input) {
|
|
matchDuration(input);
|
|
}
|
|
}
|
|
|
|
matchDuration(duration);
|
|
|
|
if (!parts.length) {
|
|
throw INVALID_FORMAT;
|
|
}
|
|
|
|
const totalDuration = parts.reduce((acc, [value, type]) => {
|
|
const duration = value * PROMETHEUS_SUFFIX_MULTIPLIER[type];
|
|
return acc + duration;
|
|
}, 0);
|
|
|
|
return totalDuration;
|
|
}
|
|
|
|
/**
|
|
* Formats the given duration in milliseconds into a human-readable string representation.
|
|
*
|
|
* @param milliseconds - The duration in milliseconds.
|
|
* @returns The formatted duration string.
|
|
*/
|
|
export function formatPrometheusDuration(milliseconds: number): string {
|
|
const seconds = Math.floor(milliseconds / 1000);
|
|
const minutes = Math.floor(seconds / 60);
|
|
const hours = Math.floor(minutes / 60);
|
|
const days = Math.floor(hours / 24);
|
|
const weeks = Math.floor(days / 7);
|
|
const years = Math.floor(days / 365);
|
|
|
|
// we'll make an exception here for 0, 0ms seems a bit weird
|
|
if (milliseconds === 0) {
|
|
return '0s';
|
|
}
|
|
|
|
const timeUnits: Array<[number, string]> = [
|
|
[years, 'y'],
|
|
[weeks % 52, 'w'],
|
|
[(days % 365) - 7 * (weeks % 52), 'd'],
|
|
[hours % 24, 'h'],
|
|
[minutes % 60, 'm'],
|
|
[seconds % 60, 's'],
|
|
[milliseconds % 1000, 'ms'],
|
|
];
|
|
|
|
return (
|
|
timeUnits
|
|
// remove all 0 values
|
|
.filter(([time]) => time > 0)
|
|
// join time and unit
|
|
.map(([time, unit]) => time + unit)
|
|
.join('')
|
|
);
|
|
}
|
|
|
|
export const safeParsePrometheusDuration = (duration: string): number => {
|
|
try {
|
|
return parsePrometheusDuration(duration);
|
|
} catch (e) {
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
export const isNullDate = (date: string) => {
|
|
return date.includes('0001-01-01T00');
|
|
};
|
|
|
|
// Format given time span in MS to the largest single unit duration string up to hours.
|
|
export function msToSingleUnitDuration(rangeMs: number): string {
|
|
if (rangeMs % (1000 * 60 * 60) === 0) {
|
|
return rangeMs / (1000 * 60 * 60) + 'h';
|
|
}
|
|
if (rangeMs % (1000 * 60) === 0) {
|
|
return rangeMs / (1000 * 60) + 'm';
|
|
}
|
|
if (rangeMs % 1000 === 0) {
|
|
return rangeMs / 1000 + 's';
|
|
}
|
|
return rangeMs.toFixed() + 'ms';
|
|
}
|