mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Prometheus: Support 1ms resolution intervals (#44707)
* Prometheus: Support 1ms resolution in time ranges * UI: Support 1ms resolution in time ranges
This commit is contained in:
parent
d32ec75661
commit
9595fd6b66
@ -1,4 +1,4 @@
|
|||||||
import { localTimeFormat } from './formats';
|
import { localTimeFormat, systemDateFormats } from './formats';
|
||||||
|
|
||||||
describe('Date Formats', () => {
|
describe('Date Formats', () => {
|
||||||
it('localTimeFormat', () => {
|
it('localTimeFormat', () => {
|
||||||
@ -36,3 +36,30 @@ describe('Date Formats without hour12', () => {
|
|||||||
expect(format).toBe('MM/DD/YYYY, HH:mm:ss');
|
expect(format).toBe('MM/DD/YYYY, HH:mm:ss');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('systemDateFormats', () => {
|
||||||
|
it('contains correct date formats', () => {
|
||||||
|
expect(systemDateFormats.fullDate).toBe('YYYY-MM-DD HH:mm:ss');
|
||||||
|
expect(systemDateFormats.fullDateMS).toBe('YYYY-MM-DD HH:mm:ss.SSS');
|
||||||
|
expect(systemDateFormats.interval.millisecond).toBe('HH:mm:ss.SSS');
|
||||||
|
expect(systemDateFormats.interval.second).toBe('HH:mm:ss');
|
||||||
|
expect(systemDateFormats.interval.minute).toBe('HH:mm');
|
||||||
|
expect(systemDateFormats.interval.hour).toBe('MM/DD HH:mm');
|
||||||
|
expect(systemDateFormats.interval.day).toBe('MM/DD');
|
||||||
|
expect(systemDateFormats.interval.month).toBe('YYYY-MM');
|
||||||
|
expect(systemDateFormats.interval.year).toBe('YYYY');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('contains correct browser-localized date formats', () => {
|
||||||
|
systemDateFormats.useBrowserLocale();
|
||||||
|
expect(systemDateFormats.fullDate).toBe('MM/DD/YYYY, hh:mm:ss A');
|
||||||
|
expect(systemDateFormats.fullDateMS).toBe('MM/DD/YYYY, hh:mm:ss.SSS A');
|
||||||
|
expect(systemDateFormats.interval.millisecond).toBe('HH:mm:ss.SSS');
|
||||||
|
expect(systemDateFormats.interval.second).toBe('HH:mm:ss');
|
||||||
|
expect(systemDateFormats.interval.minute).toBe('HH:mm');
|
||||||
|
expect(systemDateFormats.interval.hour).toBe('MM/DD, HH:mm');
|
||||||
|
expect(systemDateFormats.interval.day).toBe('MM/DD');
|
||||||
|
expect(systemDateFormats.interval.month).toBe('MM/YYYY');
|
||||||
|
expect(systemDateFormats.interval.year).toBe('YYYY');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
export interface SystemDateFormatSettings {
|
export interface SystemDateFormatSettings {
|
||||||
fullDate: string;
|
fullDate: string;
|
||||||
interval: {
|
interval: {
|
||||||
|
millisecond: string;
|
||||||
second: string;
|
second: string;
|
||||||
minute: string;
|
minute: string;
|
||||||
hour: string;
|
hour: string;
|
||||||
@ -12,10 +13,13 @@ export interface SystemDateFormatSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_SYSTEM_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
const DEFAULT_SYSTEM_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
||||||
|
const DEFAULT_SYSTEM_DATE_MS_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSS';
|
||||||
|
|
||||||
export class SystemDateFormatsState {
|
export class SystemDateFormatsState {
|
||||||
fullDate = DEFAULT_SYSTEM_DATE_FORMAT;
|
fullDate = DEFAULT_SYSTEM_DATE_FORMAT;
|
||||||
|
fullDateMS = DEFAULT_SYSTEM_DATE_MS_FORMAT;
|
||||||
interval = {
|
interval = {
|
||||||
|
millisecond: 'HH:mm:ss.SSS',
|
||||||
second: 'HH:mm:ss',
|
second: 'HH:mm:ss',
|
||||||
minute: 'HH:mm',
|
minute: 'HH:mm',
|
||||||
hour: 'MM/DD HH:mm',
|
hour: 'MM/DD HH:mm',
|
||||||
@ -33,11 +37,6 @@ export class SystemDateFormatsState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get fullDateMS() {
|
|
||||||
// Add millisecond to seconds part
|
|
||||||
return this.fullDate.replace('ss', 'ss.SSS');
|
|
||||||
}
|
|
||||||
|
|
||||||
useBrowserLocale() {
|
useBrowserLocale() {
|
||||||
this.fullDate = localTimeFormat({
|
this.fullDate = localTimeFormat({
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
@ -48,6 +47,15 @@ export class SystemDateFormatsState {
|
|||||||
second: '2-digit',
|
second: '2-digit',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ES5 doesn't support `DateTimeFormatOptions.fractionalSecondDigits` so we have to use
|
||||||
|
// a hack with string replacement.
|
||||||
|
this.fullDateMS = this.fullDate.replace('ss', 'ss.SSS');
|
||||||
|
|
||||||
|
this.interval.millisecond = localTimeFormat(
|
||||||
|
{ hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false },
|
||||||
|
null,
|
||||||
|
this.interval.second
|
||||||
|
).replace('ss', 'ss.SSS');
|
||||||
this.interval.second = localTimeFormat(
|
this.interval.second = localTimeFormat(
|
||||||
{ hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false },
|
{ hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false },
|
||||||
null,
|
null,
|
||||||
|
@ -53,6 +53,128 @@ describe('Range Utils', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('roundInterval', () => {
|
||||||
|
it('rounds 9ms to 1ms', () => {
|
||||||
|
expect(rangeUtil.roundInterval(9)).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 14ms to 10ms', () => {
|
||||||
|
expect(rangeUtil.roundInterval(9)).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 34ms to 20ms', () => {
|
||||||
|
expect(rangeUtil.roundInterval(34)).toEqual(20);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 74ms to 50ms', () => {
|
||||||
|
expect(rangeUtil.roundInterval(74)).toEqual(50);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 149ms to 100ms', () => {
|
||||||
|
expect(rangeUtil.roundInterval(149)).toEqual(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 349ms to 200ms', () => {
|
||||||
|
expect(rangeUtil.roundInterval(349)).toEqual(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 749ms to 500ms', () => {
|
||||||
|
expect(rangeUtil.roundInterval(749)).toEqual(500);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 1.5s to 1s', () => {
|
||||||
|
expect(rangeUtil.roundInterval(1499)).toEqual(1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 3.5s to 2s', () => {
|
||||||
|
expect(rangeUtil.roundInterval(3499)).toEqual(2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 7.5s to 5s', () => {
|
||||||
|
expect(rangeUtil.roundInterval(7499)).toEqual(5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 12.5s to 10s', () => {
|
||||||
|
expect(rangeUtil.roundInterval(12499)).toEqual(10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 17.5s to 15s', () => {
|
||||||
|
expect(rangeUtil.roundInterval(17499)).toEqual(15000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 25s to 20s', () => {
|
||||||
|
expect(rangeUtil.roundInterval(24999)).toEqual(20000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 45s to 30s', () => {
|
||||||
|
expect(rangeUtil.roundInterval(44999)).toEqual(30000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 1m30s to 1m', () => {
|
||||||
|
expect(rangeUtil.roundInterval(89999)).toEqual(60000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 3m30s to 2m', () => {
|
||||||
|
expect(rangeUtil.roundInterval(209999)).toEqual(120000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 7m30s to 5m', () => {
|
||||||
|
expect(rangeUtil.roundInterval(449999)).toEqual(300000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 12m30s to 10m', () => {
|
||||||
|
expect(rangeUtil.roundInterval(749999)).toEqual(600000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 17m30s to 15m', () => {
|
||||||
|
expect(rangeUtil.roundInterval(1049999)).toEqual(900000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 25m to 20m', () => {
|
||||||
|
expect(rangeUtil.roundInterval(1499999)).toEqual(1200000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 45m to 30m', () => {
|
||||||
|
expect(rangeUtil.roundInterval(2699999)).toEqual(1800000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 1h30m to 1h', () => {
|
||||||
|
expect(rangeUtil.roundInterval(5399999)).toEqual(3600000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 2h30m to 2h', () => {
|
||||||
|
expect(rangeUtil.roundInterval(8999999)).toEqual(7200000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 4h30m to 3h', () => {
|
||||||
|
expect(rangeUtil.roundInterval(16199999)).toEqual(10800000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 9h to 6h', () => {
|
||||||
|
expect(rangeUtil.roundInterval(32399999)).toEqual(21600000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 1d to 12h', () => {
|
||||||
|
expect(rangeUtil.roundInterval(86399999)).toEqual(43200000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 1w to 1d', () => {
|
||||||
|
expect(rangeUtil.roundInterval(604799999)).toEqual(86400000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 3w to 1w', () => {
|
||||||
|
expect(rangeUtil.roundInterval(1814399999)).toEqual(604800000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds 6w to 30d', () => {
|
||||||
|
expect(rangeUtil.roundInterval(3628799999)).toEqual(2592000000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rounds >6w to 1y', () => {
|
||||||
|
expect(rangeUtil.roundInterval(3628800000)).toEqual(31536000000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('relativeToTimeRange', () => {
|
describe('relativeToTimeRange', () => {
|
||||||
it('should convert seconds to timeRange', () => {
|
it('should convert seconds to timeRange', () => {
|
||||||
const relativeTimeRange = { from: 600, to: 300 };
|
const relativeTimeRange = { from: 600, to: 300 };
|
||||||
|
@ -341,6 +341,9 @@ export function intervalToMs(str: string): number {
|
|||||||
|
|
||||||
export function roundInterval(interval: number) {
|
export function roundInterval(interval: number) {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
|
// 0.01s
|
||||||
|
case interval < 10:
|
||||||
|
return 1; // 0.001s
|
||||||
// 0.015s
|
// 0.015s
|
||||||
case interval < 15:
|
case interval < 15:
|
||||||
return 10; // 0.01s
|
return 10; // 0.01s
|
||||||
@ -392,7 +395,7 @@ export function roundInterval(interval: number) {
|
|||||||
// 12.5m
|
// 12.5m
|
||||||
case interval < 750000:
|
case interval < 750000:
|
||||||
return 600000; // 10m
|
return 600000; // 10m
|
||||||
// 12.5m
|
// 17.5m
|
||||||
case interval < 1050000:
|
case interval < 1050000:
|
||||||
return 900000; // 15m
|
return 900000; // 15m
|
||||||
// 25m
|
// 25m
|
||||||
|
@ -118,6 +118,9 @@ export const graphTimeFormat = (ticks: number | null, min: number | null, max: n
|
|||||||
const oneDay = 86400010;
|
const oneDay = 86400010;
|
||||||
const oneYear = 31536000000;
|
const oneYear = 31536000000;
|
||||||
|
|
||||||
|
if (secPerTick <= 10) {
|
||||||
|
return systemDateFormats.interval.millisecond;
|
||||||
|
}
|
||||||
if (secPerTick <= 45) {
|
if (secPerTick <= 45) {
|
||||||
return systemDateFormats.interval.second;
|
return systemDateFormats.interval.second;
|
||||||
}
|
}
|
||||||
|
@ -204,7 +204,7 @@ export function formatTime(
|
|||||||
let format = systemDateFormats.interval.year;
|
let format = systemDateFormats.interval.year;
|
||||||
|
|
||||||
if (foundIncr < timeUnitSize.second) {
|
if (foundIncr < timeUnitSize.second) {
|
||||||
format = systemDateFormats.interval.second.replace('ss', 'ss.SS');
|
format = systemDateFormats.interval.millisecond;
|
||||||
} else if (foundIncr <= timeUnitSize.minute) {
|
} else if (foundIncr <= timeUnitSize.minute) {
|
||||||
format = systemDateFormats.interval.second;
|
format = systemDateFormats.interval.second;
|
||||||
} else if (range <= timeUnitSize.day) {
|
} else if (range <= timeUnitSize.day) {
|
||||||
|
@ -15,12 +15,13 @@ type DateFormats struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DateFormatIntervals struct {
|
type DateFormatIntervals struct {
|
||||||
Second string `json:"second"`
|
Millisecond string `json:"millisecond"`
|
||||||
Minute string `json:"minute"`
|
Second string `json:"second"`
|
||||||
Hour string `json:"hour"`
|
Minute string `json:"minute"`
|
||||||
Day string `json:"day"`
|
Hour string `json:"hour"`
|
||||||
Month string `json:"month"`
|
Day string `json:"day"`
|
||||||
Year string `json:"year"`
|
Month string `json:"month"`
|
||||||
|
Year string `json:"year"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const localBrowser = "browser"
|
const localBrowser = "browser"
|
||||||
@ -42,6 +43,7 @@ func valueAsTimezone(section *ini.Section, keyName string) (string, error) {
|
|||||||
func (cfg *Cfg) readDateFormats() {
|
func (cfg *Cfg) readDateFormats() {
|
||||||
dateFormats := cfg.Raw.Section("date_formats")
|
dateFormats := cfg.Raw.Section("date_formats")
|
||||||
cfg.DateFormats.FullDate = valueAsString(dateFormats, "full_date", "YYYY-MM-DD HH:mm:ss")
|
cfg.DateFormats.FullDate = valueAsString(dateFormats, "full_date", "YYYY-MM-DD HH:mm:ss")
|
||||||
|
cfg.DateFormats.Interval.Millisecond = valueAsString(dateFormats, "interval_millisecond", "HH:mm:ss.SSS")
|
||||||
cfg.DateFormats.Interval.Second = valueAsString(dateFormats, "interval_second", "HH:mm:ss")
|
cfg.DateFormats.Interval.Second = valueAsString(dateFormats, "interval_second", "HH:mm:ss")
|
||||||
cfg.DateFormats.Interval.Minute = valueAsString(dateFormats, "interval_minute", "HH:mm")
|
cfg.DateFormats.Interval.Minute = valueAsString(dateFormats, "interval_minute", "HH:mm")
|
||||||
cfg.DateFormats.Interval.Hour = valueAsString(dateFormats, "interval_hour", "MM-DD HH:mm")
|
cfg.DateFormats.Interval.Hour = valueAsString(dateFormats, "interval_hour", "MM-DD HH:mm")
|
||||||
|
@ -104,7 +104,7 @@ func TestTimeSeriesQuery(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("appends graph_period to the query", func(t *testing.T) {
|
t.Run("appends graph_period to the query", func(t *testing.T) {
|
||||||
query := &cloudMonitoringTimeSeriesQuery{}
|
query := &cloudMonitoringTimeSeriesQuery{}
|
||||||
assert.Equal(t, query.appendGraphPeriod(&backend.QueryDataRequest{Queries: []backend.DataQuery{{}}}), " | graph_period 10ms")
|
assert.Equal(t, query.appendGraphPeriod(&backend.QueryDataRequest{Queries: []backend.DataQuery{{}}}), " | graph_period 1ms")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("skips graph_period if disabled", func(t *testing.T) {
|
t.Run("skips graph_period if disabled", func(t *testing.T) {
|
||||||
|
@ -159,6 +159,9 @@ func FormatDuration(inter time.Duration) string {
|
|||||||
//nolint: gocyclo
|
//nolint: gocyclo
|
||||||
func roundInterval(interval time.Duration) time.Duration {
|
func roundInterval(interval time.Duration) time.Duration {
|
||||||
switch {
|
switch {
|
||||||
|
// 0.01s
|
||||||
|
case interval <= 10*time.Millisecond:
|
||||||
|
return time.Millisecond * 1 // 0.001s
|
||||||
// 0.015s
|
// 0.015s
|
||||||
case interval <= 15*time.Millisecond:
|
case interval <= 15*time.Millisecond:
|
||||||
return time.Millisecond * 10 // 0.01s
|
return time.Millisecond * 10 // 0.01s
|
||||||
|
@ -70,6 +70,8 @@ func TestRoundInterval(t *testing.T) {
|
|||||||
interval time.Duration
|
interval time.Duration
|
||||||
expected time.Duration
|
expected time.Duration
|
||||||
}{
|
}{
|
||||||
|
{"10ms", time.Millisecond * 10, time.Millisecond * 1},
|
||||||
|
{"15ms", time.Millisecond * 15, time.Millisecond * 10},
|
||||||
{"30ms", time.Millisecond * 30, time.Millisecond * 20},
|
{"30ms", time.Millisecond * 30, time.Millisecond * 20},
|
||||||
{"45ms", time.Millisecond * 45, time.Millisecond * 50},
|
{"45ms", time.Millisecond * 45, time.Millisecond * 50},
|
||||||
}
|
}
|
||||||
|
@ -630,7 +630,9 @@ func newDataFrame(name string, typ string, fields ...*data.Field) *data.Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func alignTimeRange(t time.Time, step time.Duration, offset int64) time.Time {
|
func alignTimeRange(t time.Time, step time.Duration, offset int64) time.Time {
|
||||||
return time.Unix(int64(math.Floor((float64(t.Unix()+offset)/step.Seconds()))*step.Seconds()-float64(offset)), 0)
|
offsetNano := float64(offset * 1e9)
|
||||||
|
stepNano := float64(step.Nanoseconds())
|
||||||
|
return time.Unix(0, int64(math.Floor((float64(t.UnixNano())+offsetNano)/stepNano)*stepNano-offsetNano))
|
||||||
}
|
}
|
||||||
|
|
||||||
func isVariableInterval(interval string) bool {
|
func isVariableInterval(interval string) bool {
|
||||||
|
Loading…
Reference in New Issue
Block a user