diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap new file mode 100644 index 000000000000..80d38edee404 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap @@ -0,0 +1,128 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Ccr that it renders normally 1`] = ` + + + + + + + + + +`; diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.css b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.css new file mode 100644 index 000000000000..285eb066c698 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.css @@ -0,0 +1,7 @@ +/** + * [1] - We want the collapsed table (that shows the shard data) to be inline + * with the columns from the main table so we need to remove the padding + */ +.monitoringElasticsearchCcrListingTable .euiTableRow-isExpandedRow > .euiTableRowCell > .euiTableCellContent { + padding: 0; /* [1] */ +} diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.js new file mode 100644 index 000000000000..f57ba2caec38 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.js @@ -0,0 +1,211 @@ +/* + * 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 React, { Fragment, Component } from 'react'; +import { + EuiInMemoryTable, + EuiLink, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiIcon, + EuiIconTip, + EuiTextColor +} from '@elastic/eui'; + +import './ccr.css'; + +function toSeconds(ms) { + return Math.floor(ms / 1000) + 's'; +} + +export class Ccr extends Component { + constructor(props) { + super(props); + this.state = { + itemIdToExpandedRowMap: {}, + }; + } + + toggleShards(index, shards) { + const itemIdToExpandedRowMap = { + ...this.state.itemIdToExpandedRowMap + }; + + if (itemIdToExpandedRowMap[index]) { + delete itemIdToExpandedRowMap[index]; + } else { + let pagination = { + initialPageSize: 5, + pageSizeOptions: [5, 10, 20] + }; + + if (shards.length <= pagination.initialPageSize) { + pagination = false; + } + + itemIdToExpandedRowMap[index] = ( + { + return ( + + {shardId} + + ); + } + }, + { + render: () => null + }, + { + field: 'opsSynced', + name: 'Ops synced' + }, + { + field: 'syncLagTime', + name: 'Last fetch time', + render: syncLagTime => {toSeconds(syncLagTime)} + }, + { + field: 'syncLagOps', + name: 'Sync Lag (ops)', + render: (syncLagOps, data) => ( + + {syncLagOps} +    + + Leader lag: {data.syncLagOpsLeader} +
+ Follower lag: {data.syncLagOpsFollower} + + )} + position="right" + /> +
+ ) + }, + { + field: 'error', + name: 'Error', + render: error => ( + + {error} + + ) + } + ]} + sorting={true} + pagination={pagination} + /> + ); + } + this.setState({ itemIdToExpandedRowMap }); + } + + renderTable() { + const { data } = this.props; + const items = data; + + let pagination = { + initialPageSize: 5, + pageSizeOptions: [5, 10, 20] + }; + + if (items.length <= pagination.initialPageSize) { + pagination = false; + } + + const sorting = { + sort: { + field: 'index', + direction: 'asc', + }, + }; + + return ( + { + const expanded = !!this.state.itemIdToExpandedRowMap[index]; + return ( + this.toggleShards(index, shards)}> + {index} +   + { expanded ? : } + + ); + } + }, + { + field: 'follows', + sortable: true, + name: 'Follows' + }, + { + field: 'opsSynced', + sortable: true, + name: 'Ops synced' + }, + { + field: 'syncLagTime', + sortable: true, + name: 'Last fetch time', + render: syncLagTime => {toSeconds(syncLagTime)} + }, + { + field: 'syncLagOps', + sortable: true, + name: 'Sync Lag (ops)', + }, + { + field: 'error', + sortable: true, + name: 'Error', + render: error => ( + + {error} + + ) + } + ]} + items={items} + pagination={pagination} + sorting={sorting} + itemId="id" + itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap} + /> + ); + } + + render() { + return ( + + + + + {this.renderTable()} + + + + + ); + } +} diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.test.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.test.js new file mode 100644 index 000000000000..8df42974c663 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.test.js @@ -0,0 +1,73 @@ +/* + * 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 React from 'react'; +import { shallow } from 'enzyme'; +import { Ccr } from './ccr'; + +describe('Ccr', () => { + test('that it renders normally', () => { + const data = [ + { + follows: 'leader', + id: 'follower', + index: 'follower', + opsSynced: 400, + syncLagOps: 5, + syncLagTime: 60000, + shards: [ + { + opsSynced: 200, + shardId: 0, + syncLagOps: 2, + syncLagOpsFollower: 1, + syncLagOpsLeader: 1, + syncLagTime: 45000 + }, + { + opsSynced: 200, + shardId: 1, + syncLagOps: 1, + syncLagOpsFollower: 0, + syncLagOpsLeader: 1, + syncLagTime: 60000 + } + ] + }, + { + follows: 'leader2', + id: 'follower2', + index: 'follower2', + opsSynced: 50, + syncLagOps: 1, + syncLagTime: 12000, + error: 'not_working_properly', + shards: [ + { + opsSynced: 20, + shardId: 1, + syncLagOps: 0, + syncLagOpsFollower: 0, + syncLagOpsLeader: 0, + syncLagTime: 11000 + }, + { + opsSynced: 30, + shardId: 2, + syncLagOps: 5, + syncLagOpsFollower: 5, + syncLagOpsLeader: 0, + syncLagTime: 1000, + error: 'not_working_properly' + } + ] + } + ]; + + const component = shallow(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/index.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/index.js new file mode 100644 index 000000000000..11f150fadf75 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/index.js @@ -0,0 +1,7 @@ +/* + * 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 { Ccr } from './ccr'; diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap new file mode 100644 index 000000000000..9a929395f6a0 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap @@ -0,0 +1,215 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CcrShard that is renders an exception properly 1`] = ` + + +

+ + Errors + +

+
+ + +
+`; + +exports[`CcrShard that it renders normally 1`] = ` + + + + + + + + + + + + + + + + + + + + +

+ Advanced +

+ + } + id="ccrLatestStat" + initialIsOpen={false} + paddingSize="l" + > + + +

+ September 27, 2018 9:32:09 AM +

+
+ + + { + "fetch_exceptions": [], + "follower_global_checkpoint": 3049, + "follower_index": "follower", + "follower_max_seq_no": 3049, + "last_requested_seq_no": 3049, + "leader_global_checkpoint": 3049, + "leader_index": "leader", + "leader_max_seq_no": 3049, + "mapping_version": 2, + "number_of_concurrent_reads": 1, + "number_of_concurrent_writes": 0, + "number_of_failed_bulk_operations": 0, + "number_of_failed_fetches": 0, + "number_of_operations_indexed": 3050, + "number_of_queued_writes": 0, + "number_of_successful_bulk_operations": 3050, + "number_of_successful_fetches": 3050, + "operations_received": 3050, + "shard_id": 0, + "time_since_last_fetch_millis": 9402, + "total_fetch_time_millis": 44128980, + "total_index_time_millis": 41827, + "total_transferred_bytes": 234156 +} + +
+
+
+
+`; diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.js new file mode 100644 index 000000000000..8f34ca07320b --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.js @@ -0,0 +1,125 @@ +/* + * 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 React, { Fragment, PureComponent } from 'react'; +import { + EuiPage, + EuiPageBody, + EuiPanel, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTitle, + EuiBasicTable, + EuiCodeBlock, + EuiTextColor, + EuiHorizontalRule, + EuiAccordion, +} from '@elastic/eui'; +import { MonitoringTimeseriesContainer } from '../../chart'; +import { Status } from './status'; +import { formatDateTimeLocal } from '../../../../common/formatting'; + +export class CcrShard extends PureComponent { + renderCharts() { + const { metrics } = this.props; + const seriesToShow = [ + metrics.ccr_sync_lag_time, + metrics.ccr_sync_lag_ops + ]; + + const charts = seriesToShow.map((data, index) => ( + + + + + + )); + + return ( + + {charts} + + ); + } + + renderErrors() { + const { stat } = this.props; + if (stat.fetch_exceptions && stat.fetch_exceptions.length > 0) { + return ( + + + +

+ Errors +

+
+ + +
+ +
+ ); + } + return null; + } + + renderLatestStat() { + const { stat, timestamp } = this.props; + + return ( +

Advanced

} + paddingSize="l" + > + + +

{formatDateTimeLocal(timestamp)}

+
+ + + {JSON.stringify(stat, null, 2)} + +
+
+ ); + } + + render() { + const { stat, oldestStat, formattedLeader } = this.props; + + return ( + + + + + {this.renderErrors()} + + {this.renderCharts()} + + + {this.renderLatestStat()} + + + ); + } +} diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.test.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.test.js new file mode 100644 index 000000000000..41ca63aea4ee --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard.test.js @@ -0,0 +1,69 @@ +/* + * 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 React from 'react'; +import { shallow } from 'enzyme'; +import { CcrShard } from './ccr_shard'; + +describe('CcrShard', () => { + const props = { + formattedLeader: 'leader on remote', + metrics: [], + stat: { + fetch_exceptions: [], + follower_global_checkpoint: 3049, + follower_index: 'follower', + follower_max_seq_no: 3049, + last_requested_seq_no: 3049, + leader_global_checkpoint: 3049, + leader_index: 'leader', + leader_max_seq_no: 3049, + mapping_version: 2, + number_of_concurrent_reads: 1, + number_of_concurrent_writes: 0, + number_of_failed_bulk_operations: 0, + number_of_failed_fetches: 0, + number_of_operations_indexed: 3050, + number_of_queued_writes: 0, + number_of_successful_bulk_operations: 3050, + number_of_successful_fetches: 3050, + operations_received: 3050, + shard_id: 0, + time_since_last_fetch_millis: 9402, + total_fetch_time_millis: 44128980, + total_index_time_millis: 41827, + total_transferred_bytes: 234156, + }, + oldestStat: { + number_of_failed_fetches: 0, + number_of_operations_indexed: 2976 + }, + timestamp: '2018-09-27T13:32:09.412Z' + }; + + test('that it renders normally', () => { + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + + test('that is renders an exception properly', () => { + const localProps = { + ...props, + stat: { + ...props.stat, + fetch_exceptions: [ + { + type: 'something_is_wrong', + reason: 'not sure but something happened' + } + ] + } + }; + + const component = shallow(); + expect(component.find('EuiPanel').get(0)).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/index.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/index.js new file mode 100644 index 000000000000..98600c4163d3 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/index.js @@ -0,0 +1,7 @@ +/* + * 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 { CcrShard } from './ccr_shard'; diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js new file mode 100644 index 000000000000..1606e778f3a2 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js @@ -0,0 +1,58 @@ +/* + * 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 React from 'react'; +import { SummaryStatus } from '../../summary_status'; +import { formatMetric } from '../../../lib/format_number'; + +export function Status({ stat, formattedLeader, oldestStat }) { + const { + follower_index: followerIndex, + shard_id: shardId, + number_of_operations_indexed: operationsReceived, + number_of_failed_fetches: failedFetches + } = stat; + + const { + number_of_operations_indexed: oldestOperationsReceived, + number_of_failed_fetches: oldestFailedFetches + } = oldestStat; + + const metrics = [ + { + label: 'Follower Index', + value: followerIndex, + dataTestSubj: 'followerIndex' + }, + { + label: 'Shard Id', + value: shardId, + dataTestSubj: 'shardId' + }, + { + label: 'Leader Index', + value: formattedLeader, + dataTestSubj: 'leaderIndex' + }, + { + label: 'Ops Synced', + value: formatMetric(operationsReceived - oldestOperationsReceived, 'int_commas'), + dataTestSubj: 'operationsReceived' + }, + { + label: 'Failed Fetches', + value: formatMetric(failedFetches - oldestFailedFetches, 'int_commas'), + dataTestSubj: 'failedFetches' + }, + ]; + + return ( + + ); +} diff --git a/x-pack/plugins/monitoring/public/directives/main/index.html b/x-pack/plugins/monitoring/public/directives/main/index.html index 96fee7dcbb9c..9a1333a3dd57 100644 --- a/x-pack/plugins/monitoring/public/directives/main/index.html +++ b/x-pack/plugins/monitoring/public/directives/main/index.html @@ -35,6 +35,7 @@ Nodes Indices Jobs + CCR response.data) + .catch((err) => { + const Private = $injector.get('Private'); + const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); + return ajaxErrorHandlers(err); + }); +} diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.html new file mode 100644 index 000000000000..ca0b036ae39e --- /dev/null +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.html @@ -0,0 +1,7 @@ + +
+
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js new file mode 100644 index 000000000000..0c0f496af467 --- /dev/null +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js @@ -0,0 +1,41 @@ +/* + * 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 React from 'react'; +import uiRoutes from 'ui/routes'; +import { getPageData } from './get_page_data'; +import template from './index.html'; +import { Ccr } from '../../../components/elasticsearch/ccr'; +import { MonitoringViewBaseController } from '../../base_controller'; + +uiRoutes.when('/elasticsearch/ccr', { + template, + resolve: { + pageData: getPageData, + }, + controllerAs: 'elasticsearchCcr', + controller: class ElasticsearchCcrController extends MonitoringViewBaseController { + constructor($injector, $scope) { + super({ + title: 'Elasticsearch - Ccr', + reactNodeId: 'elasticsearchCcrReact', + getPageData, + $scope, + $injector + }); + + $scope.$watch(() => this.data, data => { + this.renderReact(data); + }); + + this.renderReact = ({ data }) => { + super.renderReact( + + ); + }; + } + } +}); diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js new file mode 100644 index 000000000000..1b109995b89e --- /dev/null +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/get_page_data.js @@ -0,0 +1,31 @@ +/* + * 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 { ajaxErrorHandlersProvider } from 'plugins/monitoring/lib/ajax_error_handler'; +import { timefilter } from 'ui/timefilter'; + +export function getPageData($injector) { + const $http = $injector.get('$http'); + const $route = $injector.get('$route'); + const globalState = $injector.get('globalState'); + const timeBounds = timefilter.getBounds(); + const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/ccr/${$route.current.params.index}/shard/${$route.current.params.shardId}`; // eslint-disable-line max-len + + return $http.post(url, { + ccs: globalState.ccs, + timeRange: { + min: timeBounds.min.toISOString(), + max: timeBounds.max.toISOString() + } + }) + .then(response => response.data) + .catch((err) => { + const Private = $injector.get('Private'); + const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); + return ajaxErrorHandlers(err); + }); + +} diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.html b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.html new file mode 100644 index 000000000000..76469e5d9add --- /dev/null +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.html @@ -0,0 +1,8 @@ + +
+
diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js new file mode 100644 index 000000000000..4242ef16405a --- /dev/null +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js @@ -0,0 +1,49 @@ +/* + * 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 React from 'react'; +import { get } from 'lodash'; +import uiRoutes from 'ui/routes'; +import { getPageData } from './get_page_data'; +import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; +import template from './index.html'; +import { MonitoringViewBaseController } from '../../../base_controller'; +import { CcrShard } from '../../../../components/elasticsearch/ccr_shard'; + +uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', { + template, + resolve: { + clusters: function (Private) { + const routeInit = Private(routeInitProvider); + return routeInit(); + }, + pageData: getPageData, + }, + controllerAs: 'elasticsearchCcr', + controller: class ElasticsearchCcrController extends MonitoringViewBaseController { + constructor($injector, $scope, pageData) { + super({ + title: 'Elasticsearch - Ccr - Shard', + reactNodeId: 'elasticsearchCcrShardReact', + getPageData, + $scope, + $injector + }); + + $scope.instance = `Index: ${get(pageData, 'stat.follower_index')} Shard: ${get(pageData, 'stat.shard_id')}`; + + $scope.$watch(() => this.data, data => { + this.renderReact(data); + }); + + this.renderReact = (props) => { + super.renderReact( + + ); + }; + } + } +}); diff --git a/x-pack/plugins/monitoring/server/lib/details/get_series.js b/x-pack/plugins/monitoring/server/lib/details/get_series.js index df6bad401f09..306e93273c15 100644 --- a/x-pack/plugins/monitoring/server/lib/details/get_series.js +++ b/x-pack/plugins/monitoring/server/lib/details/get_series.js @@ -184,6 +184,7 @@ function handleSeries(metric, min, max, bucketSizeInSeconds, response) { const lastUsableBucketIndex = findLastUsableBucketIndex(buckets, max, firstUsableBucketIndex, bucketSizeInSeconds * 1000); let data = []; + if (firstUsableBucketIndex <= lastUsableBucketIndex) { // map buckets to values for charts const key = derivative ? 'metric_deriv.normalized_value' : 'metric.value'; diff --git a/x-pack/plugins/monitoring/server/lib/metrics/__test__/__snapshots__/metrics.test.js.snap b/x-pack/plugins/monitoring/server/lib/metrics/__test__/__snapshots__/metrics.test.js.snap index 26586c824fde..a3590788367c 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/__test__/__snapshots__/metrics.test.js.snap +++ b/x-pack/plugins/monitoring/server/lib/metrics/__test__/__snapshots__/metrics.test.js.snap @@ -1777,6 +1777,48 @@ Object { "units": "", "uuidField": "beats_stats.beat.uuid", }, + "ccr_sync_lag_ops": DifferenceMetric { + "aggs": Object { + "metric2_max": Object { + "max": Object { + "field": "ccr_stats.follower_global_checkpoint", + }, + }, + "metric_max": Object { + "max": Object { + "field": "ccr_stats.leader_max_seq_no", + }, + }, + }, + "app": "elasticsearch", + "calculation": [Function], + "derivative": false, + "description": "The number of operations the follower index is lagging behind the leader.", + "field": "", + "format": "0,0.[00]", + "label": "Ops delay", + "metricAgg": "sum", + "timestampField": "timestamp", + "title": "Ops delay", + "type": "ccr", + "units": "", + "uuidField": "source_node.uuid", + }, + "ccr_sync_lag_time": MillisecondsToSecondsMetric { + "app": "elasticsearch", + "calculation": [Function], + "derivative": false, + "description": "The amount of time the follower index is lagging behind the leader.", + "field": "ccr_stats.time_since_last_fetch_millis", + "format": "0.[00]", + "label": "Fetch delay", + "metricAgg": "max", + "timestampField": "timestamp", + "title": "Fetch delay", + "type": "ccr", + "units": "s", + "uuidField": "source_node.uuid", + }, "cluster_index_latency": LatencyMetric { "aggs": Object { "event_time_in_millis": Object { diff --git a/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.js b/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.js index b68cf3c94867..656465ba841d 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.js @@ -33,6 +33,35 @@ export class ElasticsearchMetric extends Metric { } } +export class DifferenceMetric extends ElasticsearchMetric { + constructor({ fieldSource, metric, metric2, ...opts }) { + super({ + ...opts, + field: '', // NOTE: this is not used for this + format: LARGE_FLOAT, + metricAgg: 'sum', // NOTE: this is used for a pointless aggregation + }); + + this.checkRequiredParams({ + metric, + metric2 + }); + + this.aggs = { + metric_max: { + max: { field: `${fieldSource}.${metric}` } + }, + metric2_max: { + max: { field: `${fieldSource}.${metric2}` } + }, + }; + + this.calculation = (bucket) => { + return _.get(bucket, 'metric_max.value') - _.get(bucket, 'metric2_max.value'); + }; + } +} + export class LatencyMetric extends ElasticsearchMetric { constructor({ metric, fieldSource, ...opts }) { super({ @@ -293,3 +322,16 @@ export class WriteThreadPoolRejectedMetric extends ElasticsearchMetric { }; } } + +export class MillisecondsToSecondsMetric extends ElasticsearchMetric { + constructor(opts) { + super({ + ...opts, + units: 's', + }); + + this.calculation = bucket => { + return _.get(bucket, 'metric.value') / 1000; + }; + } +} diff --git a/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/metrics.js b/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/metrics.js index 592b4147edd9..1a7639f3e097 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/metrics.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/metrics.js @@ -15,7 +15,9 @@ import { ThreadPoolQueueMetric, ThreadPoolRejectedMetric, WriteThreadPoolQueueMetric, - WriteThreadPoolRejectedMetric + WriteThreadPoolRejectedMetric, + DifferenceMetric, + MillisecondsToSecondsMetric, } from './classes'; import { LARGE_FLOAT, @@ -944,5 +946,29 @@ export const metrics = { units: '', type: 'index', derivative: true - }) + }), + + // CCR + ccr_sync_lag_time: new MillisecondsToSecondsMetric({ + title: 'Fetch delay', // title to use for the chart + type: 'ccr', + field: 'ccr_stats.time_since_last_fetch_millis', + label: 'Fetch delay', + description: 'The amount of time the follower index is lagging behind the leader.', + format: SMALL_FLOAT, + metricAgg: 'max', + units: 'ms' + }), + ccr_sync_lag_ops: new DifferenceMetric({ + title: 'Ops delay', // title to use for the chart + type: 'ccr', + fieldSource: 'ccr_stats', + metric: 'leader_max_seq_no', + metric2: 'follower_global_checkpoint', + label: 'Ops delay', + description: 'The number of operations the follower index is lagging behind the leader.', + format: SMALL_FLOAT, + metricAgg: 'max', + units: '' + }), }; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.js new file mode 100644 index 000000000000..c2481d71acee --- /dev/null +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.js @@ -0,0 +1,258 @@ +/* + * 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 Joi from 'joi'; +import moment from 'moment'; +import { get, groupBy } from 'lodash'; +import { handleError } from '../../../../lib/errors/handle_error'; +import { prefixIndexPattern } from '../../../../lib/ccs_utils'; + +function getBucketScript(max, min) { + return { + bucket_script: { + buckets_path: { + max, + min, + }, + script: 'params.max - params.min' + } + }; +} + +function buildRequest(req, config, esIndexPattern) { + const min = moment.utc(req.payload.timeRange.min).valueOf(); + const max = moment.utc(req.payload.timeRange.max).valueOf(); + const maxBucketSize = config.get('xpack.monitoring.max_bucket_size'); + const aggs = { + ops_synced_max: { + max: { + field: 'ccr_stats.number_of_operations_indexed' + } + }, + ops_synced_min: { + min: { + field: 'ccr_stats.number_of_operations_indexed' + } + }, + + last_fetch_time_max: { + max: { + field: 'ccr_stats.time_since_last_fetch_millis' + } + }, + last_fetch_time_min: { + min: { + field: 'ccr_stats.time_since_last_fetch_millis' + } + }, + lag_ops_leader_max: { + max: { + field: 'ccr_stats.leader_max_seq_no' + } + }, + lag_ops_leader_min: { + min: { + field: 'ccr_stats.leader_max_seq_no' + } + }, + lag_ops_global_max: { + max: { + field: 'ccr_stats.follower_global_checkpoint' + } + }, + lag_ops_global_min: { + min: { + field: 'ccr_stats.follower_global_checkpoint' + } + }, + leader_lag_ops_checkpoint_max: { + max: { + field: 'ccr_stats.leader_global_checkpoint' + } + }, + leader_lag_ops_checkpoint_min: { + min: { + field: 'ccr_stats.leader_global_checkpoint' + } + }, + + last_fetch_time: getBucketScript('last_fetch_time_max', 'last_fetch_time_min'), + ops_synced: getBucketScript('ops_synced_max', 'ops_synced_min'), + lag_ops_leader: getBucketScript('lag_ops_leader_max', 'lag_ops_leader_min'), + lag_ops_global: getBucketScript('lag_ops_global_max', 'lag_ops_global_min'), + lag_ops: getBucketScript('lag_ops_leader', 'lag_ops_global'), + lag_ops_leader_checkpoint: getBucketScript('leader_lag_ops_checkpoint_max', 'leader_lag_ops_checkpoint_min'), + leader_lag_ops: getBucketScript('lag_ops_leader', 'lag_ops_leader_checkpoint'), + follower_lag_ops: getBucketScript('lag_ops_leader_checkpoint', 'lag_ops_global'), + }; + + return { + index: esIndexPattern, + size: maxBucketSize, + filterPath: [ + 'hits.hits.inner_hits.by_shard.hits.hits._source.ccr_stats.fetch_exceptions', + 'hits.hits.inner_hits.by_shard.hits.hits._source.ccr_stats.follower_index', + 'hits.hits.inner_hits.by_shard.hits.hits._source.ccr_stats.shard_id', + 'aggregations.by_follower_index.buckets.key', + 'aggregations.by_follower_index.buckets.leader_index.buckets.key', + 'aggregations.by_follower_index.buckets.by_shard_id.buckets.key', + 'aggregations.by_follower_index.buckets.by_shard_id.buckets.last_fetch_time.value', + 'aggregations.by_follower_index.buckets.by_shard_id.buckets.ops_synced.value', + 'aggregations.by_follower_index.buckets.by_shard_id.buckets.lag_ops.value', + 'aggregations.by_follower_index.buckets.by_shard_id.buckets.leader_lag_ops.value', + 'aggregations.by_follower_index.buckets.by_shard_id.buckets.follower_lag_ops.value', + ], + body: { + sort: [{ timestamp: { order: 'desc' } }], + query: { + bool: { + must: [ + { + term: { + type: { + value: 'ccr_stats' + } + } + }, + { + range: { + timestamp: { + format: 'epoch_millis', + gte: min, + lte: max, + } + } + } + ] + } + }, + collapse: { + field: 'ccr_stats.follower_index', + inner_hits: { + name: 'by_shard', + sort: [{ timestamp: 'desc' }], + size: maxBucketSize, + collapse: { + field: 'ccr_stats.shard_id', + } + } + }, + aggs: { + by_follower_index: { + terms: { + field: 'ccr_stats.follower_index', + size: maxBucketSize, + }, + aggs: { + leader_index: { + terms: { + field: 'ccr_stats.leader_index', + size: 1 + } + }, + by_shard_id: { + terms: { + field: 'ccr_stats.shard_id', + size: 10 + }, + aggs, + } + } + } + } + } + }; +} + +export function ccrRoute(server) { + server.route({ + method: 'POST', + path: '/api/monitoring/v1/clusters/{clusterUuid}/elasticsearch/ccr', + config: { + validate: { + params: Joi.object({ + clusterUuid: Joi.string().required() + }), + payload: Joi.object({ + ccs: Joi.string().optional(), + timeRange: Joi.object({ + min: Joi.date().required(), + max: Joi.date().required() + }).required() + }) + } + }, + async handler(req, reply) { + const config = server.config(); + const ccs = req.payload.ccs; + const esIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.elasticsearch.index_pattern', ccs); + + try { + const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); + const response = await callWithRequest(req, 'search', buildRequest(req, config, esIndexPattern)); + + if (!response || Object.keys(response).length === 0) { + reply({ data: [] }); + return; + } + + const fullStats = get(response, 'hits.hits').reduce((accum, hit) => { + const innerHits = get(hit, 'inner_hits.by_shard.hits.hits'); + const innerHitsSource = innerHits.map(innerHit => get(innerHit, '_source.ccr_stats')); + const grouped = groupBy(innerHitsSource, stat => `${stat.follower_index}:${stat.shard_id}`); + + return { + ...accum, + ...grouped + }; + }, {}); + + const buckets = get(response, 'aggregations.by_follower_index.buckets'); + const data = buckets.reduce((accum, bucket) => { + const leaderIndex = get(bucket, 'leader_index.buckets[0].key'); + let follows = leaderIndex; + if (follows.includes(':')) { + const followsSplit = follows.split(':'); + follows = `${followsSplit[1]} on ${followsSplit[0]}`; + } + + const stat = { + id: bucket.key, + index: bucket.key, + follows, + }; + + stat.shards = get(bucket, 'by_shard_id.buckets').reduce((accum, shardBucket) => { + const fullStat = get(fullStats[`${bucket.key}:${shardBucket.key}`], '[0]', {}); + const shardStat = { + shardId: shardBucket.key, + error: fullStat.fetch_exceptions.length ? fullStat.fetch_exceptions[0].exception.type : null, + opsSynced: get(shardBucket, 'ops_synced.value'), + syncLagTime: get(shardBucket, 'last_fetch_time.value'), + syncLagOps: get(shardBucket, 'lag_ops.value'), + syncLagOpsLeader: get(shardBucket, 'leader_lag_ops.value'), + syncLagOpsFollower: get(shardBucket, 'follower_lag_ops.value'), + }; + accum.push(shardStat); + return accum; + }, []); + + stat.error = (stat.shards.find(shard => shard.error) || {}).error; + stat.opsSynced = stat.shards.reduce((sum, { opsSynced }) => sum + opsSynced, 0); + stat.syncLagTime = stat.shards.reduce((max, { syncLagTime }) => Math.max(max, syncLagTime), 0); + stat.syncLagOps = stat.shards.reduce((max, { syncLagOps }) => Math.max(max, syncLagOps), 0); + + accum.push(stat); + return accum; + }, []); + + reply({ data }); + } catch(err) { + reply(handleError(err, req)); + } + } + }); +} diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.js new file mode 100644 index 000000000000..87b4d91515ea --- /dev/null +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.js @@ -0,0 +1,149 @@ +/* + * 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 { get } from 'lodash'; +import moment from 'moment'; +import Joi from 'joi'; +import { handleError } from '../../../../lib/errors/handle_error'; +import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { getMetrics } from '../../../../lib/details/get_metrics'; + +function getFormattedLeaderIndex(leaderIndex) { + let leader = leaderIndex; + if (leader.includes(':')) { + const leaderSplit = leader.split(':'); + leader = `${leaderSplit[1]} on ${leaderSplit[0]}`; + } + return leader; +} + +async function getCcrStat(req, esIndexPattern, filters) { + const min = moment.utc(req.payload.timeRange.min).valueOf(); + const max = moment.utc(req.payload.timeRange.max).valueOf(); + + const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); + + const params = { + index: esIndexPattern, + size: 1, + filterPath: [ + 'hits.hits._source.ccr_stats', + 'hits.hits._source.timestamp', + 'hits.hits.inner_hits.oldest.hits.hits._source.ccr_stats.number_of_operations_indexed', + 'hits.hits.inner_hits.oldest.hits.hits._source.ccr_stats.number_of_failed_fetches', + ], + body: { + sort: [{ timestamp: { order: 'desc' } }], + query: { + bool: { + must: [ + ...filters, + { + range: { + timestamp: { + format: 'epoch_millis', + gte: min, + lte: max, + } + } + } + ] + } + }, + collapse: { + field: 'ccr_stats.follower_index', + inner_hits: { + name: 'oldest', + size: 1, + sort: [{ timestamp: 'asc' }] + } + } + } + }; + + return await callWithRequest(req, 'search', params); +} + +export function ccrShardRoute(server) { + server.route({ + method: 'POST', + path: '/api/monitoring/v1/clusters/{clusterUuid}/elasticsearch/ccr/{index}/shard/{shardId}', + config: { + validate: { + params: Joi.object({ + clusterUuid: Joi.string().required(), + index: Joi.string().required(), + shardId: Joi.string().required() + }), + payload: Joi.object({ + ccs: Joi.string().optional(), + timeRange: Joi.object({ + min: Joi.date().required(), + max: Joi.date().required() + }).required(), + }) + } + }, + async handler(req, reply) { + const config = server.config(); + const index = req.params.index; + const shardId = req.params.shardId; + const ccs = req.payload.ccs; + const esIndexPattern = prefixIndexPattern(config, 'xpack.monitoring.elasticsearch.index_pattern', ccs); + + const filters = [ + { + term: { + type: { + value: 'ccr_stats' + } + } + }, + { + term: { + 'ccr_stats.follower_index': { + value: index, + } + } + }, + { + term: { + 'ccr_stats.shard_id': { + value: shardId, + } + } + } + ]; + + try { + + const [ + metrics, + ccrResponse + ] = await Promise.all([ + getMetrics(req, esIndexPattern, [ + { keys: ['ccr_sync_lag_time'], name: 'ccr_sync_lag_time' }, + { keys: ['ccr_sync_lag_ops'], name: 'ccr_sync_lag_ops' }, + ], filters), + getCcrStat(req, esIndexPattern, filters) + ]); + + const stat = get(ccrResponse, 'hits.hits[0]._source.ccr_stats', {}); + const oldestStat = get(ccrResponse, 'hits.hits[0].inner_hits.oldest.hits.hits[0]._source.ccr_stats', {}); + + reply({ + metrics, + stat, + formattedLeader: getFormattedLeaderIndex(stat.leader_index), + timestamp: get(ccrResponse, 'hits.hits[0]._source.timestamp'), + oldestStat, + }); + } catch(err) { + reply(handleError(err, req)); + } + } + }); +} diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index.js index aeab4d383210..b4697f1de51c 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index.js @@ -10,3 +10,5 @@ export { esNodeRoute } from './node_detail'; export { esNodesRoute } from './nodes'; export { esOverviewRoute } from './overview'; export { mlJobRoute } from './ml_jobs'; +export { ccrRoute } from './ccr'; +export { ccrShardRoute } from './ccr_shard'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/ui.js b/x-pack/plugins/monitoring/server/routes/api/v1/ui.js index b62406841955..c07a35109643 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/ui.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/ui.js @@ -26,7 +26,9 @@ export { esNodeRoute, esNodesRoute, esOverviewRoute, - mlJobRoute + mlJobRoute, + ccrRoute, + ccrShardRoute } from './elasticsearch'; export { clusterSettingsCheckRoute, diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/ccr.js b/x-pack/test/api_integration/apis/monitoring/elasticsearch/ccr.js new file mode 100644 index 000000000000..249baa8c2791 --- /dev/null +++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/ccr.js @@ -0,0 +1,41 @@ +/* + * 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 expect from 'expect.js'; +import ccrFixture from './fixtures/ccr'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('ccr', () => { + const archive = 'monitoring/ccr'; + const timeRange = { + min: '2018-09-19T00:00:00.000Z', + max: '2018-09-19T23:59:59.000Z' + }; + + before('load archive', () => { + return esArchiver.load(archive); + }); + + after('unload archive', () => { + return esArchiver.unload(archive); + }); + + it('should return all followers and a grouping of stats by follower index', async () => { + const { body } = await supertest + .post('/api/monitoring/v1/clusters/YCxj-RAgSZCP6GuOQ8M1EQ/elasticsearch/ccr') + .set('kbn-xsrf', 'xxx') + .send({ + timeRange, + }) + .expect(200); + + expect(body).to.eql(ccrFixture); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/ccr_shard.js b/x-pack/test/api_integration/apis/monitoring/elasticsearch/ccr_shard.js new file mode 100644 index 000000000000..e5a2418f215b --- /dev/null +++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/ccr_shard.js @@ -0,0 +1,41 @@ +/* + * 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 expect from 'expect.js'; +import ccrShardFixture from './fixtures/ccr_shard'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('ccr shard', () => { + const archive = 'monitoring/ccr'; + const timeRange = { + min: '2018-09-19T00:00:00.000Z', + max: '2018-09-19T23:59:59.000Z' + }; + + before('load archive', () => { + return esArchiver.load(archive); + }); + + after('unload archive', () => { + return esArchiver.unload(archive); + }); + + it('should return specific shard details', async () => { + const { body } = await supertest + .post('/api/monitoring/v1/clusters/YCxj-RAgSZCP6GuOQ8M1EQ/elasticsearch/ccr/follower/shard/0') + .set('kbn-xsrf', 'xxx') + .send({ + timeRange, + }) + .expect(200); + + expect(body).to.eql(ccrShardFixture); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/ccr.json b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/ccr.json new file mode 100644 index 000000000000..6ac135c8bc85 --- /dev/null +++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/ccr.json @@ -0,0 +1,83 @@ +{ + "data": [{ + "id": "follower2", + "index": "follower2", + "follows": "leader2", + "shards": [{ + "shardId": 0, + "error": null, + "opsSynced": 52, + "syncLagTime": 59881, + "syncLagOps": 0, + "syncLagOpsLeader": 0, + "syncLagOpsFollower": 0 + }, { + "shardId": 1, + "error": null, + "opsSynced": 47, + "syncLagTime": 59959, + "syncLagOps": 0, + "syncLagOpsLeader": 0, + "syncLagOpsFollower": 0 + }, { + "shardId": 2, + "error": null, + "opsSynced": 51, + "syncLagTime": 55229, + "syncLagOps": 0, + "syncLagOpsLeader": 0, + "syncLagOpsFollower": 0 + }, { + "shardId": 3, + "error": null, + "opsSynced": 50, + "syncLagTime": 50483, + "syncLagOps": 0, + "syncLagOpsLeader": 0, + "syncLagOpsFollower": 0 + }, { + "shardId": 4, + "error": null, + "opsSynced": 55, + "syncLagTime": 55554, + "syncLagOps": 0, + "syncLagOpsLeader": 0, + "syncLagOpsFollower": 0 + }], + "opsSynced": 255, + "syncLagTime": 59959, + "syncLagOps": 0 + }, { + "id": "follower", + "index": "follower", + "follows": "leader", + "shards": [{ + "shardId": 0, + "error": null, + "opsSynced": 85, + "syncLagTime": 45513, + "syncLagOps": 0, + "syncLagOpsLeader": 0, + "syncLagOpsFollower": 0 + }, { + "shardId": 1, + "error": null, + "opsSynced": 94, + "syncLagTime": 55205, + "syncLagOps": 0, + "syncLagOpsLeader": 0, + "syncLagOpsFollower": 0 + }, { + "shardId": 2, + "error": null, + "opsSynced": 76, + "syncLagTime": 50003, + "syncLagOps": 0, + "syncLagOpsLeader": 0, + "syncLagOpsFollower": 0 + }], + "opsSynced": 255, + "syncLagTime": 55205, + "syncLagOps": 0 + }] +} diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/ccr_shard.json b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/ccr_shard.json new file mode 100644 index 000000000000..e0f0f8d8ff94 --- /dev/null +++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/ccr_shard.json @@ -0,0 +1,75 @@ +{ + "formattedLeader": "leader", + "metrics": { + "ccr_sync_lag_time": [{ + "bucket_size": "10 min", + "timeRange": { + "min": 1537315200000, + "max": 1537401599000 + }, + "metric": { + "app": "elasticsearch", + "field": "ccr_stats.time_since_last_fetch_millis", + "metricAgg": "max", + "label": "Fetch delay", + "title": "Fetch delay", + "description": "The amount of time the follower index is lagging behind the leader.", + "units": "s", + "format": "0.[00]", + "hasCalculation": true, + "isDerivative": false + }, + "data": [] + }], + "ccr_sync_lag_ops": [{ + "bucket_size": "10 min", + "timeRange": { + "min": 1537315200000, + "max": 1537401599000 + }, + "metric": { + "app": "elasticsearch", + "field": "", + "metricAgg": "sum", + "label": "Ops delay", + "title": "Ops delay", + "description": "The amount of time the follower index is lagging behind the leader.", + "units": "ms", + "format": "0,0.[00]", + "hasCalculation": true, + "isDerivative": false + }, + "data": [] + }] + }, + "stat": { + "leader_index": "leader", + "follower_index": "follower", + "shard_id": 0, + "leader_global_checkpoint": 85, + "leader_max_seq_no": 85, + "follower_global_checkpoint": 85, + "follower_max_seq_no": 85, + "last_requested_seq_no": 85, + "number_of_concurrent_reads": 1, + "number_of_concurrent_writes": 0, + "number_of_queued_writes": 0, + "mapping_version": 2, + "total_fetch_time_millis": 1265908, + "number_of_successful_fetches": 86, + "number_of_failed_fetches": 0, + "operations_received": 86, + "total_transferred_bytes": 6602, + "total_index_time_millis": 1096, + "number_of_successful_bulk_operations": 86, + "number_of_failed_bulk_operations": 0, + "number_of_operations_indexed": 86, + "fetch_exceptions": [], + "time_since_last_fetch_millis": 19886 + }, + "timestamp": "2018-09-19T20:01:00.440Z", + "oldestStat": { + "number_of_failed_fetches": 0, + "number_of_operations_indexed": 1 + } +} diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/index.js b/x-pack/test/api_integration/apis/monitoring/elasticsearch/index.js index 1d3a0f7f5a58..6253c2542ce0 100644 --- a/x-pack/test/api_integration/apis/monitoring/elasticsearch/index.js +++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/index.js @@ -12,5 +12,7 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./node_detail_advanced')); loadTestFile(require.resolve('./indices')); loadTestFile(require.resolve('./index_detail')); + loadTestFile(require.resolve('./ccr')); + loadTestFile(require.resolve('./ccr_shard')); }); } diff --git a/x-pack/test/functional/es_archives/monitoring/ccr/data.json.gz b/x-pack/test/functional/es_archives/monitoring/ccr/data.json.gz new file mode 100644 index 000000000000..2ff098caffa5 Binary files /dev/null and b/x-pack/test/functional/es_archives/monitoring/ccr/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/monitoring/ccr/mappings.json b/x-pack/test/functional/es_archives/monitoring/ccr/mappings.json new file mode 100644 index 000000000000..45542a21f87a --- /dev/null +++ b/x-pack/test/functional/es_archives/monitoring/ccr/mappings.json @@ -0,0 +1,1019 @@ +{ + "type": "index", + "value": { + "index": ".monitoring-es-6-2018.09.19", + "settings": { + "index": { + "codec": "best_compression", + "number_of_shards": "1", + "auto_expand_replicas": "0-1", + "format": "6", + "number_of_replicas": "0" + } + }, + "mappings": { + "doc": { + "dynamic": "false", + "date_detection": false, + "properties": { + "ccr_stats": { + "properties": { + "fetch_exceptions": { + "type": "nested", + "properties": { + "exception": { + "properties": { + "reason": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "from_seq_no": { + "type": "long" + }, + "retries": { + "type": "integer" + } + } + }, + "follower_global_checkpoint": { + "type": "long" + }, + "follower_index": { + "type": "keyword" + }, + "follower_max_seq_no": { + "type": "long" + }, + "last_requested_seq_no": { + "type": "long" + }, + "leader_global_checkpoint": { + "type": "long" + }, + "leader_index": { + "type": "keyword" + }, + "leader_max_seq_no": { + "type": "long" + }, + "mapping_version": { + "type": "long" + }, + "number_of_concurrent_reads": { + "type": "long" + }, + "number_of_concurrent_writes": { + "type": "long" + }, + "number_of_failed_bulk_operations": { + "type": "long" + }, + "number_of_failed_fetches": { + "type": "long" + }, + "number_of_operations_indexed": { + "type": "long" + }, + "number_of_queued_writes": { + "type": "long" + }, + "number_of_successful_bulk_operations": { + "type": "long" + }, + "number_of_successful_fetches": { + "type": "long" + }, + "operations_received": { + "type": "long" + }, + "shard_id": { + "type": "integer" + }, + "time_since_last_fetch_millis": { + "type": "long" + }, + "total_fetch_time_millis": { + "type": "long" + }, + "total_index_time_millis": { + "type": "long" + }, + "total_transferred_bytes": { + "type": "long" + } + } + }, + "cluster_state": { + "properties": { + "master_node": { + "type": "keyword" + }, + "nodes": { + "type": "object" + }, + "nodes_hash": { + "type": "integer" + }, + "shards": { + "type": "object" + }, + "state_uuid": { + "type": "keyword" + }, + "status": { + "type": "keyword" + }, + "version": { + "type": "long" + } + } + }, + "cluster_stats": { + "properties": { + "indices": { + "type": "object" + }, + "nodes": { + "type": "object" + } + } + }, + "cluster_uuid": { + "type": "keyword" + }, + "index_recovery": { + "type": "object" + }, + "index_stats": { + "properties": { + "index": { + "type": "keyword" + }, + "primaries": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + } + } + }, + "fielddata": { + "properties": { + "evictions": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + } + } + }, + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + }, + "throttle_time_in_millis": { + "type": "long" + } + } + }, + "merges": { + "properties": { + "total_size_in_bytes": { + "type": "long" + } + } + }, + "query_cache": { + "properties": { + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { + "type": "long" + } + } + }, + "request_cache": { + "properties": { + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + }, + "segments": { + "properties": { + "count": { + "type": "integer" + }, + "doc_values_memory_in_bytes": { + "type": "long" + }, + "fixed_bit_set_memory_in_bytes": { + "type": "long" + }, + "index_writer_memory_in_bytes": { + "type": "long" + }, + "memory_in_bytes": { + "type": "long" + }, + "norms_memory_in_bytes": { + "type": "long" + }, + "points_memory_in_bytes": { + "type": "long" + }, + "stored_fields_memory_in_bytes": { + "type": "long" + }, + "term_vectors_memory_in_bytes": { + "type": "long" + }, + "terms_memory_in_bytes": { + "type": "long" + }, + "version_map_memory_in_bytes": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + } + } + }, + "fielddata": { + "properties": { + "evictions": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + } + } + }, + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + }, + "throttle_time_in_millis": { + "type": "long" + } + } + }, + "merges": { + "properties": { + "total_size_in_bytes": { + "type": "long" + } + } + }, + "query_cache": { + "properties": { + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { + "type": "long" + } + } + }, + "request_cache": { + "properties": { + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + }, + "segments": { + "properties": { + "count": { + "type": "integer" + }, + "doc_values_memory_in_bytes": { + "type": "long" + }, + "fixed_bit_set_memory_in_bytes": { + "type": "long" + }, + "index_writer_memory_in_bytes": { + "type": "long" + }, + "memory_in_bytes": { + "type": "long" + }, + "norms_memory_in_bytes": { + "type": "long" + }, + "points_memory_in_bytes": { + "type": "long" + }, + "stored_fields_memory_in_bytes": { + "type": "long" + }, + "term_vectors_memory_in_bytes": { + "type": "long" + }, + "terms_memory_in_bytes": { + "type": "long" + }, + "version_map_memory_in_bytes": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "type": "long" + } + } + } + } + } + } + }, + "indices_stats": { + "properties": { + "_all": { + "properties": { + "primaries": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + } + } + }, + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + } + } + }, + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "interval_ms": { + "type": "long" + }, + "job_stats": { + "properties": { + "data_counts": { + "properties": { + "bucket_count": { + "type": "long" + }, + "earliest_record_timestamp": { + "type": "date" + }, + "empty_bucket_count": { + "type": "long" + }, + "input_bytes": { + "type": "long" + }, + "latest_record_timestamp": { + "type": "date" + }, + "processed_record_count": { + "type": "long" + }, + "sparse_bucket_count": { + "type": "long" + } + } + }, + "job_id": { + "type": "keyword" + }, + "model_size_stats": { + "properties": { + "bucket_allocation_failures_count": { + "type": "long" + }, + "model_bytes": { + "type": "long" + } + } + }, + "node": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "state": { + "type": "keyword" + } + } + }, + "node_stats": { + "properties": { + "fs": { + "properties": { + "data": { + "properties": { + "spins": { + "type": "boolean" + } + } + }, + "io_stats": { + "properties": { + "total": { + "properties": { + "operations": { + "type": "long" + }, + "read_kilobytes": { + "type": "long" + }, + "read_operations": { + "type": "long" + }, + "write_kilobytes": { + "type": "long" + }, + "write_operations": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + }, + "free_in_bytes": { + "type": "long" + }, + "total_in_bytes": { + "type": "long" + } + } + } + } + }, + "indices": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + } + } + }, + "fielddata": { + "properties": { + "evictions": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + } + } + }, + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + }, + "throttle_time_in_millis": { + "type": "long" + } + } + }, + "query_cache": { + "properties": { + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, + "request_cache": { + "properties": { + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + }, + "segments": { + "properties": { + "count": { + "type": "integer" + }, + "doc_values_memory_in_bytes": { + "type": "long" + }, + "fixed_bit_set_memory_in_bytes": { + "type": "long" + }, + "index_writer_memory_in_bytes": { + "type": "long" + }, + "memory_in_bytes": { + "type": "long" + }, + "norms_memory_in_bytes": { + "type": "long" + }, + "points_memory_in_bytes": { + "type": "long" + }, + "stored_fields_memory_in_bytes": { + "type": "long" + }, + "term_vectors_memory_in_bytes": { + "type": "long" + }, + "terms_memory_in_bytes": { + "type": "long" + }, + "version_map_memory_in_bytes": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "type": "long" + } + } + } + } + }, + "jvm": { + "properties": { + "gc": { + "properties": { + "collectors": { + "properties": { + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } + }, + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } + } + } + } + } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "half_float" + } + } + } + } + }, + "mlockall": { + "type": "boolean" + }, + "node_id": { + "type": "keyword" + }, + "node_master": { + "type": "boolean" + }, + "os": { + "properties": { + "cgroup": { + "properties": { + "cpu": { + "properties": { + "cfs_quota_micros": { + "type": "long" + }, + "control_group": { + "type": "keyword" + }, + "stat": { + "properties": { + "number_of_elapsed_periods": { + "type": "long" + }, + "number_of_times_throttled": { + "type": "long" + }, + "time_throttled_nanos": { + "type": "long" + } + } + } + } + }, + "cpuacct": { + "properties": { + "control_group": { + "type": "keyword" + }, + "usage_nanos": { + "type": "long" + } + } + }, + "memory": { + "properties": { + "control_group": { + "type": "keyword" + }, + "limit_in_bytes": { + "type": "keyword" + }, + "usage_in_bytes": { + "type": "keyword" + } + } + } + } + }, + "cpu": { + "properties": { + "load_average": { + "properties": { + "15m": { + "type": "half_float" + }, + "1m": { + "type": "half_float" + }, + "5m": { + "type": "half_float" + } + } + } + } + } + } + }, + "process": { + "properties": { + "cpu": { + "properties": { + "percent": { + "type": "half_float" + } + } + }, + "max_file_descriptors": { + "type": "long" + }, + "open_file_descriptors": { + "type": "long" + } + } + }, + "thread_pool": { + "properties": { + "bulk": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + }, + "threads": { + "type": "integer" + } + } + }, + "generic": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + }, + "threads": { + "type": "integer" + } + } + }, + "get": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + }, + "threads": { + "type": "integer" + } + } + }, + "index": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + }, + "threads": { + "type": "integer" + } + } + }, + "maanagement": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + }, + "threads": { + "type": "integer" + } + } + }, + "search": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + }, + "threads": { + "type": "integer" + } + } + }, + "watcher": { + "properties": { + "queue": { + "type": "integer" + }, + "rejected": { + "type": "long" + }, + "threads": { + "type": "integer" + } + } + } + } + } + } + }, + "shard": { + "properties": { + "index": { + "type": "keyword" + }, + "node": { + "type": "keyword" + }, + "primary": { + "type": "boolean" + }, + "relocating_node": { + "type": "keyword" + }, + "shard": { + "type": "long" + }, + "state": { + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "host": { + "type": "keyword" + }, + "ip": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "timestamp": { + "type": "date", + "format": "date_time" + }, + "transport_address": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + } + } + }, + "state_uuid": { + "type": "keyword" + }, + "timestamp": { + "type": "date", + "format": "date_time" + }, + "type": { + "type": "keyword" + } + } + } + }, + "aliases": {} + } +} \ No newline at end of file