Skip to content

Commit

Permalink
[6.5] [ui/timeBuckets/calcAutoInterval] Refactor (#24669) (#24750)
Browse files Browse the repository at this point in the history
Backports the following commits to 6.5:
 - [ui/timeBuckets/calcAutoInterval] Refactor  (#24669)
  • Loading branch information
Spencer authored Oct 30, 2018
1 parent a14fb4a commit c21e368
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 91 deletions.
88 changes: 0 additions & 88 deletions src/ui/public/time_buckets/calc_auto_interval.js

This file was deleted.

136 changes: 136 additions & 0 deletions src/ui/public/time_buckets/calc_auto_interval.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import moment from 'moment';

import { calcAutoIntervalLessThan, calcAutoIntervalNear } from './calc_auto_interval';

describe('calcAutoIntervalNear', () => {
test('1h/0 buckets = 0ms buckets', () => {
const interval = calcAutoIntervalNear(0, Number(moment.duration(1, 'h')));
expect(interval.asMilliseconds()).toBe(0);
});

test('undefined/100 buckets = 0ms buckets', () => {
const interval = calcAutoIntervalNear(0, undefined as any);
expect(interval.asMilliseconds()).toBe(0);
});

test('1ms/100 buckets = 1ms buckets', () => {
const interval = calcAutoIntervalNear(100, Number(moment.duration(1, 'ms')));
expect(interval.asMilliseconds()).toBe(1);
});

test('200ms/100 buckets = 2ms buckets', () => {
const interval = calcAutoIntervalNear(100, Number(moment.duration(200, 'ms')));
expect(interval.asMilliseconds()).toBe(2);
});

test('1s/1000 buckets = 1ms buckets', () => {
const interval = calcAutoIntervalNear(1000, Number(moment.duration(1, 's')));
expect(interval.asMilliseconds()).toBe(1);
});

test('1000h/1000 buckets = 1h buckets', () => {
const interval = calcAutoIntervalNear(1000, Number(moment.duration(1000, 'hours')));
expect(interval.asHours()).toBe(1);
});

test('1h/100 buckets = 30s buckets', () => {
const interval = calcAutoIntervalNear(100, Number(moment.duration(1, 'hours')));
expect(interval.asSeconds()).toBe(30);
});

test('1d/25 buckets = 1h buckets', () => {
const interval = calcAutoIntervalNear(25, Number(moment.duration(1, 'day')));
expect(interval.asHours()).toBe(1);
});

test('1y/1000 buckets = 12h buckets', () => {
const interval = calcAutoIntervalNear(1000, Number(moment.duration(1, 'year')));
expect(interval.asHours()).toBe(12);
});

test('1y/10000 buckets = 1h buckets', () => {
const interval = calcAutoIntervalNear(10000, Number(moment.duration(1, 'year')));
expect(interval.asHours()).toBe(1);
});

test('1y/100000 buckets = 5m buckets', () => {
const interval = calcAutoIntervalNear(100000, Number(moment.duration(1, 'year')));
expect(interval.asMinutes()).toBe(5);
});
});

describe('calcAutoIntervalLessThan', () => {
test('1h/0 buckets = 0ms buckets', () => {
const interval = calcAutoIntervalLessThan(0, Number(moment.duration(1, 'h')));
expect(interval.asMilliseconds()).toBe(0);
});

test('undefined/100 buckets = 0ms buckets', () => {
const interval = calcAutoIntervalLessThan(0, undefined as any);
expect(interval.asMilliseconds()).toBe(0);
});

test('1ms/100 buckets = 1ms buckets', () => {
const interval = calcAutoIntervalLessThan(100, Number(moment.duration(1, 'ms')));
expect(interval.asMilliseconds()).toBe(1);
});

test('200ms/100 buckets = 2ms buckets', () => {
const interval = calcAutoIntervalLessThan(100, Number(moment.duration(200, 'ms')));
expect(interval.asMilliseconds()).toBe(2);
});

test('1s/1000 buckets = 1ms buckets', () => {
const interval = calcAutoIntervalLessThan(1000, Number(moment.duration(1, 's')));
expect(interval.asMilliseconds()).toBe(1);
});

test('1000h/1000 buckets = 1h buckets', () => {
const interval = calcAutoIntervalLessThan(1000, Number(moment.duration(1000, 'hours')));
expect(interval.asHours()).toBe(1);
});

test('1h/100 buckets = 30s buckets', () => {
const interval = calcAutoIntervalLessThan(100, Number(moment.duration(1, 'hours')));
expect(interval.asSeconds()).toBe(30);
});

test('1d/25 buckets = 30m buckets', () => {
const interval = calcAutoIntervalLessThan(25, Number(moment.duration(1, 'day')));
expect(interval.asMinutes()).toBe(30);
});

test('1y/1000 buckets = 3h buckets', () => {
const interval = calcAutoIntervalLessThan(1000, Number(moment.duration(1, 'year')));
expect(interval.asHours()).toBe(3);
});

test('1y/10000 buckets = 30m buckets', () => {
const interval = calcAutoIntervalLessThan(10000, Number(moment.duration(1, 'year')));
expect(interval.asMinutes()).toBe(30);
});

test('1y/100000 buckets = 5m buckets', () => {
const interval = calcAutoIntervalLessThan(100000, Number(moment.duration(1, 'year')));
expect(interval.asMinutes()).toBe(5);
});
});
145 changes: 145 additions & 0 deletions src/ui/public/time_buckets/calc_auto_interval.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import moment from 'moment';

const boundsDescending = [
{
bound: Infinity,
interval: Number(moment.duration(1, 'year')),
},
{
bound: Number(moment.duration(1, 'year')),
interval: Number(moment.duration(1, 'month')),
},
{
bound: Number(moment.duration(3, 'week')),
interval: Number(moment.duration(1, 'week')),
},
{
bound: Number(moment.duration(1, 'week')),
interval: Number(moment.duration(1, 'd')),
},
{
bound: Number(moment.duration(24, 'hour')),
interval: Number(moment.duration(12, 'hour')),
},
{
bound: Number(moment.duration(6, 'hour')),
interval: Number(moment.duration(3, 'hour')),
},
{
bound: Number(moment.duration(2, 'hour')),
interval: Number(moment.duration(1, 'hour')),
},
{
bound: Number(moment.duration(45, 'minute')),
interval: Number(moment.duration(30, 'minute')),
},
{
bound: Number(moment.duration(20, 'minute')),
interval: Number(moment.duration(10, 'minute')),
},
{
bound: Number(moment.duration(9, 'minute')),
interval: Number(moment.duration(5, 'minute')),
},
{
bound: Number(moment.duration(3, 'minute')),
interval: Number(moment.duration(1, 'minute')),
},
{
bound: Number(moment.duration(45, 'second')),
interval: Number(moment.duration(30, 'second')),
},
{
bound: Number(moment.duration(15, 'second')),
interval: Number(moment.duration(10, 'second')),
},
{
bound: Number(moment.duration(7.5, 'second')),
interval: Number(moment.duration(5, 'second')),
},
{
bound: Number(moment.duration(5, 'second')),
interval: Number(moment.duration(1, 'second')),
},
{
bound: Number(moment.duration(500, 'ms')),
interval: Number(moment.duration(100, 'ms')),
},
];

function getPerBucketMs(count: number, duration: number) {
const ms = duration / count;
return isFinite(ms) ? ms : NaN;
}

function normalizeMinimumInterval(targetMs: number) {
const value = isNaN(targetMs) ? 0 : Math.max(Math.floor(targetMs), 1);
return moment.duration(value);
}

/**
* Using some simple rules we pick a "pretty" interval that will
* produce around the number of buckets desired given a time range.
*
* @param targetBucketCount desired number of buckets
* @param duration time range the agg covers
*/
export function calcAutoIntervalNear(targetBucketCount: number, duration: number) {
const targetPerBucketMs = getPerBucketMs(targetBucketCount, duration);

// Find the first bound which is smaller than our target.
const lowerBoundIndex = boundsDescending.findIndex(({ bound }) => {
const boundMs = Number(bound);
return boundMs <= targetPerBucketMs;
});

// The bound immediately preceeding that lower bound contains the
// interval most closely matching our target.
if (lowerBoundIndex !== -1) {
const nearestInterval = boundsDescending[lowerBoundIndex - 1].interval;
return moment.duration(nearestInterval);
}

// If the target is smaller than any of our bounds, then we'll use it for the interval as-is.
return normalizeMinimumInterval(targetPerBucketMs);
}

/**
* Pick a "pretty" interval that produces no more than the maxBucketCount
* for the given time range.
*
* @param maxBucketCount maximum number of buckets to create
* @param duration amount of time covered by the agg
*/
export function calcAutoIntervalLessThan(maxBucketCount: number, duration: number) {
const maxPerBucketMs = getPerBucketMs(maxBucketCount, duration);

for (const { interval } of boundsDescending) {
// Find the highest interval which meets our per bucket limitation.
if (interval <= maxPerBucketMs) {
return moment.duration(interval);
}
}

// If the max is smaller than any of our intervals, then we'll use it for the interval as-is.
return normalizeMinimumInterval(maxPerBucketMs);
}
6 changes: 3 additions & 3 deletions src/ui/public/time_buckets/time_buckets.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import _ from 'lodash';
import moment from 'moment';
import chrome from 'ui/chrome';
import { parseInterval } from '../utils/parse_interval';
import { calcAutoInterval } from './calc_auto_interval';
import { calcAutoIntervalLessThan, calcAutoIntervalNear } from './calc_auto_interval';
import {
convertDurationToNormalizedEsInterval,
convertIntervalToEsInterval,
Expand Down Expand Up @@ -231,7 +231,7 @@ TimeBuckets.prototype.getInterval = function (useNormalizedEsInterval = true) {
function readInterval() {
const interval = self._i;
if (moment.isDuration(interval)) return interval;
return calcAutoInterval.near(config.get('histogram:barTarget'), duration);
return calcAutoIntervalNear(config.get('histogram:barTarget'), Number(duration));
}

// check to see if the interval should be scaled, and scale it if so
Expand All @@ -243,7 +243,7 @@ TimeBuckets.prototype.getInterval = function (useNormalizedEsInterval = true) {
let scaled;

if (approxLen > maxLength) {
scaled = calcAutoInterval.lessThan(maxLength, duration);
scaled = calcAutoIntervalLessThan(maxLength, Number(duration));
} else {
return interval;
}
Expand Down

0 comments on commit c21e368

Please sign in to comment.