Skip to content

Commit

Permalink
[Monitoring] Ensure all charts use the configured timezone (elastic#4…
Browse files Browse the repository at this point in the history
…5949)

* Consistently apply dateFormat:tz to all monitoring time-series data

* Ensure browser timezone works properly

* Fix tests

* Fix other test

* Simplfy timezone fetching

* Fix tests
  • Loading branch information
chrisronline committed Oct 2, 2019
1 parent 7cb0ece commit 908ec75
Show file tree
Hide file tree
Showing 12 changed files with 83 additions and 30 deletions.
6 changes: 4 additions & 2 deletions x-pack/legacy/plugins/monitoring/common/formatting.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ export const LARGE_ABBREVIATED = '0,0.[0]a';
* @param date Either a numeric Unix timestamp or a {@code Date} object
* @returns The date formatted using 'LL LTS'
*/
export function formatDateTimeLocal(date) {
return moment.tz(date, moment.tz.guess()).format('LL LTS');
export function formatDateTimeLocal(date, useUTC = false) {
return useUTC
? moment.utc(date).format('LL LTS')
: moment.tz(date, moment.tz.guess()).format('LL LTS');
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ export class ChartTarget extends React.Component {
.value();
}

getOptions() {
const opts = getChartOptions({
async getOptions() {
const opts = await getChartOptions({
yaxis: { tickFormatter: this.props.tickFormatter },
xaxis: this.props.timeRange
});
Expand All @@ -88,12 +88,12 @@ export class ChartTarget extends React.Component {
};
}

renderChart() {
async renderChart() {
const { target } = this.refs;
const { series } = this.props;
const data = this.filterData(series, this.props.seriesToShow);

this.plot = $.plot(target, data, this.getOptions());
this.plot = $.plot(target, data, await this.getOptions());

this._handleResize = () => {
if (!this.plot) { return; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@
* you may not use this file except in compliance with the Elastic License.
*/

import chrome from 'ui/chrome';
import { merge } from 'lodash';
import { CHART_LINE_COLOR, CHART_TEXT_COLOR } from '../../../common/constants';

export function getChartOptions(axisOptions) {
export async function getChartOptions(axisOptions) {
const $injector = await chrome.dangerouslyGetActiveInjector();
const timezone = $injector.get('config').get('dateFormat:tz');
const opts = {
legend: {
show: false
},
xaxis: {
color: CHART_LINE_COLOR,
timezone: 'browser',
timezone: timezone === 'Browser' ? 'browser' : 'utc',
mode: 'time', // requires `time` flot plugin
font: {
color: CHART_TEXT_COLOR
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const columns = [
field: 'timestamp',
name: columnTimestampTitle,
width: '12%',
render: timestamp => formatDateTimeLocal(timestamp),
render: timestamp => formatDateTimeLocal(timestamp, true),
},
{
field: 'level',
Expand Down Expand Up @@ -80,7 +80,7 @@ const clusterColumns = [
field: 'timestamp',
name: columnTimestampTitle,
width: '12%',
render: timestamp => formatDateTimeLocal(timestamp),
render: timestamp => formatDateTimeLocal(timestamp, true),
},
{
field: 'level',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ function getMockReq(metricsBuckets = []) {
},
params: {
clusterUuid: '1234xyz'
}
},
getUiSettingsService: () => ({
get: () => 'Browser'
})
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import Promise from 'bluebird';
import { checkParam } from '../error_missing_required';
import { getSeries } from './get_series';
import { calculateTimeseriesInterval } from '../calculate_timeseries_interval';
import { getTimezone } from '../get_timezone';

export function getMetrics(req, indexPattern, metricSet = [], filters = []) {
export async function getMetrics(req, indexPattern, metricSet = [], filters = []) {
checkParam(indexPattern, 'indexPattern in details/getMetrics');
checkParam(metricSet, 'metricSet in details/getMetrics');

Expand All @@ -21,6 +22,7 @@ export function getMetrics(req, indexPattern, metricSet = [], filters = []) {
const max = moment.utc(req.payload.timeRange.max).valueOf();
const minIntervalSeconds = config.get('xpack.monitoring.min_interval_seconds');
const bucketSize = calculateTimeseriesInterval(min, max, minIntervalSeconds);
const timezone = await getTimezone(req);

return Promise.map(metricSet, metric => {
// metric names match the literal metric name, but they can be supplied in groups or individually
Expand All @@ -33,7 +35,7 @@ export function getMetrics(req, indexPattern, metricSet = [], filters = []) {
}

return Promise.map(metricNames, metricName => {
return getSeries(req, indexPattern, metricName, filters, { min, max, bucketSize });
return getSeries(req, indexPattern, metricName, filters, { min, max, bucketSize, timezone });
});
})
.then(rows => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
NORMALIZED_DERIVATIVE_UNIT,
CALCULATE_DURATION_UNTIL
} from '../../../common/constants';
import { formatUTCTimestampForTimezone } from '../format_timezone';

/**
* Derivative metrics for the first two agg buckets are unusable. For the first bucket, there
Expand Down Expand Up @@ -177,7 +178,7 @@ const formatBucketSize = bucketSizeInSeconds => {
return formatTimestampToDuration(timestamp, CALCULATE_DURATION_UNTIL, now);
};

function handleSeries(metric, min, max, bucketSizeInSeconds, response) {
function handleSeries(metric, min, max, bucketSizeInSeconds, timezone, response) {
const { derivative, calculation: customCalculation } = metric;
const buckets = get(response, 'aggregations.check.buckets', []);
const firstUsableBucketIndex = findFirstUsableBucketIndex(buckets, min);
Expand All @@ -193,14 +194,17 @@ function handleSeries(metric, min, max, bucketSizeInSeconds, response) {
data = buckets
.slice(firstUsableBucketIndex, lastUsableBucketIndex + 1) // take only the buckets we know are usable
.map(bucket => ([
bucket.key,
formatUTCTimestampForTimezone(bucket.key, timezone),
calculation(bucket, key, metric, bucketSizeInSeconds)
])); // map buckets to X/Y coords for Flot charting
}

return {
bucket_size: formatBucketSize(bucketSizeInSeconds),
timeRange: { min, max },
timeRange: {
min: formatUTCTimestampForTimezone(min, timezone),
max: formatUTCTimestampForTimezone(max, timezone),
},
metric: metric.serialize(),
data
};
Expand All @@ -217,7 +221,7 @@ function handleSeries(metric, min, max, bucketSizeInSeconds, response) {
* @param {Array} filters Any filters that should be applied to the query.
* @return {Promise} The object response containing the {@code timeRange}, {@code metric}, and {@code data}.
*/
export async function getSeries(req, indexPattern, metricName, filters, { min, max, bucketSize }) {
export async function getSeries(req, indexPattern, metricName, filters, { min, max, bucketSize, timezone }) {
checkParam(indexPattern, 'indexPattern in details/getSeries');

const metric = metrics[metricName];
Expand All @@ -226,5 +230,5 @@ export async function getSeries(req, indexPattern, metricName, filters, { min, m
}
const response = await fetchSeries(req, indexPattern, metric, min, max, bucketSize, filters);

return handleSeries(metric, min, max, bucketSize, response);
return handleSeries(metric, min, max, bucketSize, timezone, response);
}
25 changes: 25 additions & 0 deletions x-pack/legacy/plugins/monitoring/server/lib/format_timezone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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 moment from 'moment';


/**
* This function is designed to offset a UTC timestamp based on the provided timezone
* For example, EST is UTC-4h so this function will subtract (4 * 60 * 60 * 1000)ms
* from the UTC timestamp. This allows us to allow users to view monitoring data
* in various timezones without needing to not store UTC dates.
*
* @param {*} utcTimestamp UTC timestamp
* @param {*} timezone The timezone to convert into
*/
export const formatUTCTimestampForTimezone = (utcTimestamp, timezone) => {
if (timezone === 'Browser') {
return utcTimestamp;
}
const offsetInMinutes = moment.tz(timezone).utcOffset();
const offsetTimestamp = utcTimestamp + (offsetInMinutes * 1 * 60 * 1000);
return offsetTimestamp;
};
9 changes: 9 additions & 0 deletions x-pack/legacy/plugins/monitoring/server/lib/get_timezone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* 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.
*/

export async function getTimezone(req) {
return await req.getUiSettingsService().get('dateFormat:tz');
}
7 changes: 6 additions & 1 deletion x-pack/legacy/plugins/monitoring/server/lib/logs/get_logs.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,31 @@
* you may not use this file except in compliance with the Elastic License.
*/

import moment from 'moment';
import { get } from 'lodash';
import { checkParam } from '../error_missing_required';
import { createTimeFilter } from '../create_query';
import { detectReason } from './detect_reason';
import { formatUTCTimestampForTimezone } from '../format_timezone';
import { getTimezone } from '../get_timezone';

async function handleResponse(response, req, filebeatIndexPattern, opts) {
const result = {
enabled: false,
logs: []
};

const timezone = await getTimezone(req);
const hits = get(response, 'hits.hits', []);
if (hits.length) {
result.enabled = true;
result.logs = hits.map(hit => {
const source = hit._source;
const type = get(source, 'event.dataset').split('.')[1];
const utcTimestamp = moment(get(source, '@timestamp')).valueOf();

return {
timestamp: get(source, '@timestamp'),
timestamp: formatUTCTimestampForTimezone(utcTimestamp, timezone),
component: get(source, 'elasticsearch.component'),
node: get(source, 'elasticsearch.node.name'),
index: get(source, 'elasticsearch.index.name'),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"enabled": true,
"logs": [{
"timestamp": "2019-03-15T17:07:21.089Z",
"timestamp": 1552669641089,
"component": "o.e.n.Node",
"node": "Elastic-MBP.local",
"index": ".monitoring-es",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,74 +1,74 @@
{
"enabled": true,
"logs": [{
"timestamp": "2019-03-15T17:19:07.365Z",
"timestamp": 1552670347365,
"component": "o.e.d.x.m.r.a.RestMonitoringBulkAction",
"node": "Elastic-MBP.local",
"level": "WARN",
"type": "deprecation",
"message": "[POST /_xpack/monitoring/_bulk] is deprecated! Use [POST /_monitoring/bulk] instead."
}, {
"timestamp": "2019-03-15T17:18:57.366Z",
"timestamp": 1552670337366,
"component": "o.e.d.x.m.r.a.RestMonitoringBulkAction",
"node": "Elastic-MBP.local",
"level": "WARN",
"type": "deprecation",
"message": "[POST /_xpack/monitoring/_bulk] is deprecated! Use [POST /_monitoring/bulk] instead."
}, {
"timestamp": "2019-03-15T17:18:47.400Z",
"timestamp": 1552670327400,
"component": "o.e.c.m.MetaDataCreateIndexService",
"node": "Elastic-MBP.local",
"index": ".monitoring-beats-7-2019.03.15",
"level": "INFO",
"type": "server",
"message": "creating index, cause [auto(bulk api)], templates [.monitoring-beats], shards [1]/[0], mappings [_doc]"
}, {
"timestamp": "2019-03-15T17:18:47.387Z",
"timestamp": 1552670327387,
"component": "o.e.d.x.m.r.a.RestMonitoringBulkAction",
"node": "Elastic-MBP.local",
"level": "WARN",
"type": "deprecation",
"message": "[POST /_xpack/monitoring/_bulk] is deprecated! Use [POST /_monitoring/bulk] instead."
}, {
"timestamp": "2019-03-15T17:18:42.084Z",
"timestamp": 1552670322084,
"component": "o.e.c.m.MetaDataMappingService",
"node": "Elastic-MBP.local",
"index": "filebeat-8.0.0-2019.03.15-000001",
"level": "INFO",
"type": "server",
"message": "update_mapping [_doc]"
}, {
"timestamp": "2019-03-15T17:18:41.811Z",
"timestamp": 1552670321811,
"component": "o.e.c.m.MetaDataMappingService",
"node": "Elastic-MBP.local",
"index": "filebeat-8.0.0-2019.03.15-000001",
"level": "INFO",
"type": "server",
"message": "update_mapping [_doc]"
}, {
"timestamp": "2019-03-15T17:18:41.447Z",
"timestamp": 1552670321447,
"component": "o.e.c.m.MetaDataCreateIndexService",
"node": "Elastic-MBP.local",
"index": "filebeat-8.0.0-2019.03.15-000001",
"level": "INFO",
"type": "server",
"message": "creating index, cause [api], templates [filebeat-8.0.0], shards [1]/[1], mappings [_doc]"
}, {
"timestamp": "2019-03-15T17:18:41.385Z",
"timestamp": 1552670321385,
"component": "o.e.c.m.MetaDataIndexTemplateService",
"node": "Elastic-MBP.local",
"level": "INFO",
"type": "server",
"message": "adding template [filebeat-8.0.0] for index patterns [filebeat-8.0.0-*]"
}, {
"timestamp": "2019-03-15T17:18:41.185Z",
"timestamp": 1552670321185,
"component": "o.e.x.i.a.TransportPutLifecycleAction",
"node": "Elastic-MBP.local",
"level": "INFO",
"type": "server",
"message": "adding index lifecycle policy [filebeat-8.0.0]"
}, {
"timestamp": "2019-03-15T17:18:36.137Z",
"timestamp": 1552670316137,
"component": "o.e.c.r.a.AllocationService",
"node": "Elastic-MBP.local",
"level": "INFO",
Expand Down

0 comments on commit 908ec75

Please sign in to comment.