[Metrics UI] Round metric threshold time buckets to nearest unit (#71172)
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
3fc54e7c55
commit
66c531d903
|
@ -23,6 +23,7 @@ interface Aggregation {
|
|||
buckets: Array<{
|
||||
aggregatedValue: { value: number; values?: Array<{ key: number; value: number }> };
|
||||
doc_count: number;
|
||||
key_as_string: string;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
@ -57,17 +58,18 @@ export const evaluateAlert = (
|
|||
);
|
||||
const { threshold, comparator } = criterion;
|
||||
const comparisonFunction = comparatorMap[comparator];
|
||||
return mapValues(currentValues, (values: number | number[] | null) => {
|
||||
if (isTooManyBucketsPreviewException(values)) throw values;
|
||||
return mapValues(currentValues, (points: any[] | typeof NaN | null) => {
|
||||
if (isTooManyBucketsPreviewException(points)) throw points;
|
||||
return {
|
||||
...criterion,
|
||||
metric: criterion.metric ?? DOCUMENT_COUNT_I18N,
|
||||
currentValue: Array.isArray(values) ? last(values) : NaN,
|
||||
shouldFire: Array.isArray(values)
|
||||
? values.map((value) => comparisonFunction(value, threshold))
|
||||
currentValue: Array.isArray(points) ? last(points)?.value : NaN,
|
||||
timestamp: Array.isArray(points) ? last(points)?.key : NaN,
|
||||
shouldFire: Array.isArray(points)
|
||||
? points.map((point) => comparisonFunction(point.value, threshold))
|
||||
: [false],
|
||||
isNoData: values === null,
|
||||
isError: isNaN(values),
|
||||
isNoData: points === null,
|
||||
isError: isNaN(points),
|
||||
};
|
||||
});
|
||||
})
|
||||
|
@ -157,17 +159,20 @@ const getValuesFromAggregations = (
|
|||
const { buckets } = aggregations.aggregatedIntervals;
|
||||
if (!buckets.length) return null; // No Data state
|
||||
if (aggType === Aggregators.COUNT) {
|
||||
return buckets.map((bucket) => bucket.doc_count);
|
||||
return buckets.map((bucket) => ({ key: bucket.key_as_string, value: bucket.doc_count }));
|
||||
}
|
||||
if (aggType === Aggregators.P95 || aggType === Aggregators.P99) {
|
||||
return buckets.map((bucket) => {
|
||||
const values = bucket.aggregatedValue?.values || [];
|
||||
const firstValue = first(values);
|
||||
if (!firstValue) return null;
|
||||
return firstValue.value;
|
||||
return { key: bucket.key_as_string, value: firstValue.value };
|
||||
});
|
||||
}
|
||||
return buckets.map((bucket) => bucket.aggregatedValue.value);
|
||||
return buckets.map((bucket) => ({
|
||||
key: bucket.key_as_string,
|
||||
value: bucket.aggregatedValue.value,
|
||||
}));
|
||||
} catch (e) {
|
||||
return NaN; // Error state
|
||||
}
|
||||
|
|
|
@ -56,4 +56,26 @@ describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handles time', () => {
|
||||
const end = new Date('2020-07-08T22:07:27.235Z').valueOf();
|
||||
const timerange = {
|
||||
end,
|
||||
start: end - 5 * 60 * 1000,
|
||||
};
|
||||
const searchBody = getElasticsearchMetricQuery(
|
||||
expressionParams,
|
||||
timefield,
|
||||
undefined,
|
||||
undefined,
|
||||
timerange
|
||||
);
|
||||
test('by rounding timestamps to the nearest timeUnit', () => {
|
||||
const rangeFilter = searchBody.query.bool.filter.find((filter) =>
|
||||
filter.hasOwnProperty('range')
|
||||
)?.range[timefield];
|
||||
expect(rangeFilter?.lte).toBe(1594246020000);
|
||||
expect(rangeFilter?.gte).toBe(1594245720000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { networkTraffic } from '../../../../../common/inventory_models/shared/metrics/snapshot/network_traffic';
|
||||
import { MetricExpressionParams, Aggregators } from '../types';
|
||||
import { getIntervalInSeconds } from '../../../../utils/get_interval_in_seconds';
|
||||
import { roundTimestamp } from '../../../../utils/round_timestamp';
|
||||
import { getDateHistogramOffset } from '../../../snapshot/query_helpers';
|
||||
import { createPercentileAggregation } from './create_percentile_aggregation';
|
||||
|
||||
|
@ -34,12 +36,15 @@ export const getElasticsearchMetricQuery = (
|
|||
const interval = `${timeSize}${timeUnit}`;
|
||||
const intervalAsSeconds = getIntervalInSeconds(interval);
|
||||
|
||||
const to = timeframe ? timeframe.end : Date.now();
|
||||
const to = roundTimestamp(timeframe ? timeframe.end : Date.now(), timeUnit);
|
||||
// We need enough data for 5 buckets worth of data. We also need
|
||||
// to convert the intervalAsSeconds to milliseconds.
|
||||
const minimumFrom = to - intervalAsSeconds * 1000 * MINIMUM_BUCKETS;
|
||||
|
||||
const from = timeframe && timeframe.start <= minimumFrom ? timeframe.start : minimumFrom;
|
||||
const from = roundTimestamp(
|
||||
timeframe && timeframe.start <= minimumFrom ? timeframe.start : minimumFrom,
|
||||
timeUnit
|
||||
);
|
||||
|
||||
const offset = getDateHistogramOffset(from, interval);
|
||||
|
||||
|
|
|
@ -94,12 +94,14 @@ describe('The metric threshold alert type', () => {
|
|||
expect(getState(instanceID).alertState).toBe(AlertStates.OK);
|
||||
});
|
||||
test('reports expected values to the action context', async () => {
|
||||
const now = 1577858400000;
|
||||
await execute(Comparator.GT, [0.75]);
|
||||
const { action } = mostRecentAction(instanceID);
|
||||
expect(action.group).toBe('*');
|
||||
expect(action.reason).toContain('current value is 1');
|
||||
expect(action.reason).toContain('threshold of 0.75');
|
||||
expect(action.reason).toContain('test.metric.1');
|
||||
expect(action.timestamp).toBe(new Date(now).toISOString());
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -76,11 +76,13 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs, alertId: s
|
|||
}
|
||||
}
|
||||
if (reason) {
|
||||
const firstResult = first(alertResults);
|
||||
const timestamp = (firstResult && firstResult[group].timestamp) ?? moment().toISOString();
|
||||
alertInstance.scheduleActions(FIRED_ACTIONS.id, {
|
||||
group,
|
||||
alertState: stateToAlertMessage[nextState],
|
||||
reason,
|
||||
timestamp: moment().toISOString(),
|
||||
timestamp,
|
||||
value: mapToConditionsLookup(alertResults, (result) => result[group].currentValue),
|
||||
threshold: mapToConditionsLookup(criteria, (c) => c.threshold),
|
||||
metric: mapToConditionsLookup(criteria, (c) => c.metric),
|
||||
|
|
|
@ -12,6 +12,7 @@ const bucketsA = [
|
|||
{
|
||||
doc_count: 3,
|
||||
aggregatedValue: { value: 1.0, values: [{ key: 95.0, value: 1.0 }] },
|
||||
key_as_string: new Date(1577858400000).toISOString(),
|
||||
},
|
||||
];
|
||||
|
||||
|
|
15
x-pack/plugins/infra/server/utils/round_timestamp.ts
Normal file
15
x-pack/plugins/infra/server/utils/round_timestamp.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Unit } from '@elastic/datemath';
|
||||
import moment from 'moment';
|
||||
|
||||
export const roundTimestamp = (timestamp: number, unit: Unit) => {
|
||||
const floor = moment(timestamp).startOf(unit).valueOf();
|
||||
const ceil = moment(timestamp).add(1, unit).startOf(unit).valueOf();
|
||||
if (Math.abs(timestamp - floor) <= Math.abs(timestamp - ceil)) return floor;
|
||||
return ceil;
|
||||
};
|
Loading…
Reference in a new issue