Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Monitoring] Ensure all charts use the configured timezone #45949

Merged
merged 7 commits into from
Oct 2, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -44,13 +44,19 @@ function getMockReq(metricsBuckets = []) {
)
})
}
}
},
uiSettingsServiceFactory: () => ({
get: () => 'Browser'
}),
},
payload: {
timeRange: { min, max }
},
params: {
clusterUuid: '1234xyz'
},
getSavedObjectsClient: () => {

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