2025-04-01 10:38:02 +09:00

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';
}