mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Monitoring] CCR UI (#23013)
* Initial version of CCR monitoring UI * Adding missing files * Use icons * Use new column header text * Update tests * Basic of shard detail page * Do these in parallel * Disable time picker on ccr page * Remove summary for now * Remove unnecessary code here * Fix a few things on the shard page * Only send down what we need * update snapshot * Handle no ccr_stats documents * Ensure we fetch the latest * Updates * Format the time * Add api integration tests * Adding pagination and sorting * Updated query logic * Change this back * Add specific information about the follower and leader lag ops * Update tests * UI updates * Address PR issues * Fix tests * Update shapshots * Add timestamp * Update tests * Add a few snapshot tests * Use timezone formatter * Fix tests * Fix aligment of shard table * PR feedback * Update snapshots * Update snapshot
This commit is contained in:
parent
a648d0bff3
commit
2caf6ecb4f
34 changed files with 2856 additions and 3 deletions
|
@ -0,0 +1,128 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Ccr that it renders normally 1`] = `
|
||||
<EuiPage
|
||||
restrictWidth={false}
|
||||
>
|
||||
<EuiPageBody
|
||||
restrictWidth={false}
|
||||
>
|
||||
<EuiPageContent
|
||||
panelPaddingSize="l"
|
||||
>
|
||||
<EuiPageContentBody>
|
||||
<EuiInMemoryTable
|
||||
className="monitoringElasticsearchCcrListingTable"
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"field": "index",
|
||||
"name": "Index",
|
||||
"render": [Function],
|
||||
"sortable": true,
|
||||
},
|
||||
Object {
|
||||
"field": "follows",
|
||||
"name": "Follows",
|
||||
"sortable": true,
|
||||
},
|
||||
Object {
|
||||
"field": "opsSynced",
|
||||
"name": "Ops synced",
|
||||
"sortable": true,
|
||||
},
|
||||
Object {
|
||||
"field": "syncLagTime",
|
||||
"name": "Last fetch time",
|
||||
"render": [Function],
|
||||
"sortable": true,
|
||||
},
|
||||
Object {
|
||||
"field": "syncLagOps",
|
||||
"name": "Sync Lag (ops)",
|
||||
"sortable": true,
|
||||
},
|
||||
Object {
|
||||
"field": "error",
|
||||
"name": "Error",
|
||||
"render": [Function],
|
||||
"sortable": true,
|
||||
},
|
||||
]
|
||||
}
|
||||
itemId="id"
|
||||
itemIdToExpandedRowMap={Object {}}
|
||||
items={
|
||||
Array [
|
||||
Object {
|
||||
"follows": "leader",
|
||||
"id": "follower",
|
||||
"index": "follower",
|
||||
"opsSynced": 400,
|
||||
"shards": Array [
|
||||
Object {
|
||||
"opsSynced": 200,
|
||||
"shardId": 0,
|
||||
"syncLagOps": 2,
|
||||
"syncLagOpsFollower": 1,
|
||||
"syncLagOpsLeader": 1,
|
||||
"syncLagTime": 45000,
|
||||
},
|
||||
Object {
|
||||
"opsSynced": 200,
|
||||
"shardId": 1,
|
||||
"syncLagOps": 1,
|
||||
"syncLagOpsFollower": 0,
|
||||
"syncLagOpsLeader": 1,
|
||||
"syncLagTime": 60000,
|
||||
},
|
||||
],
|
||||
"syncLagOps": 5,
|
||||
"syncLagTime": 60000,
|
||||
},
|
||||
Object {
|
||||
"error": "not_working_properly",
|
||||
"follows": "leader2",
|
||||
"id": "follower2",
|
||||
"index": "follower2",
|
||||
"opsSynced": 50,
|
||||
"shards": Array [
|
||||
Object {
|
||||
"opsSynced": 20,
|
||||
"shardId": 1,
|
||||
"syncLagOps": 0,
|
||||
"syncLagOpsFollower": 0,
|
||||
"syncLagOpsLeader": 0,
|
||||
"syncLagTime": 11000,
|
||||
},
|
||||
Object {
|
||||
"error": "not_working_properly",
|
||||
"opsSynced": 30,
|
||||
"shardId": 2,
|
||||
"syncLagOps": 5,
|
||||
"syncLagOpsFollower": 5,
|
||||
"syncLagOpsLeader": 0,
|
||||
"syncLagTime": 1000,
|
||||
},
|
||||
],
|
||||
"syncLagOps": 1,
|
||||
"syncLagTime": 12000,
|
||||
},
|
||||
]
|
||||
}
|
||||
pagination={false}
|
||||
responsive={true}
|
||||
sorting={
|
||||
Object {
|
||||
"sort": Object {
|
||||
"direction": "asc",
|
||||
"field": "index",
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiPageContentBody>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
`;
|
|
@ -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] */
|
||||
}
|
|
@ -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] = (
|
||||
<EuiInMemoryTable
|
||||
items={shards}
|
||||
columns={[
|
||||
{
|
||||
field: 'shardId',
|
||||
name: 'Shard',
|
||||
render: shardId => {
|
||||
return (
|
||||
<EuiLink href={`#/elasticsearch/ccr/${index}/shard/${shardId}`}>
|
||||
{shardId}
|
||||
</EuiLink>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
render: () => null
|
||||
},
|
||||
{
|
||||
field: 'opsSynced',
|
||||
name: 'Ops synced'
|
||||
},
|
||||
{
|
||||
field: 'syncLagTime',
|
||||
name: 'Last fetch time',
|
||||
render: syncLagTime => <span>{toSeconds(syncLagTime)}</span>
|
||||
},
|
||||
{
|
||||
field: 'syncLagOps',
|
||||
name: 'Sync Lag (ops)',
|
||||
render: (syncLagOps, data) => (
|
||||
<span>
|
||||
{syncLagOps}
|
||||
|
||||
<EuiIconTip
|
||||
size="m"
|
||||
type="iInCircle"
|
||||
content={(
|
||||
<Fragment>
|
||||
<span>Leader lag: {data.syncLagOpsLeader}</span>
|
||||
<br/>
|
||||
<span>Follower lag: {data.syncLagOpsFollower}</span>
|
||||
</Fragment>
|
||||
)}
|
||||
position="right"
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
field: 'error',
|
||||
name: 'Error',
|
||||
render: error => (
|
||||
<EuiTextColor color="danger">
|
||||
{error}
|
||||
</EuiTextColor>
|
||||
)
|
||||
}
|
||||
]}
|
||||
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 (
|
||||
<EuiInMemoryTable
|
||||
className="monitoringElasticsearchCcrListingTable"
|
||||
columns={[
|
||||
{
|
||||
field: 'index',
|
||||
name: 'Index',
|
||||
sortable: true,
|
||||
render: (index, { shards }) => {
|
||||
const expanded = !!this.state.itemIdToExpandedRowMap[index];
|
||||
return (
|
||||
<EuiLink onClick={() => this.toggleShards(index, shards)}>
|
||||
{index}
|
||||
|
||||
{ expanded ? <EuiIcon type="arrowUp" /> : <EuiIcon type="arrowDown" /> }
|
||||
</EuiLink>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'follows',
|
||||
sortable: true,
|
||||
name: 'Follows'
|
||||
},
|
||||
{
|
||||
field: 'opsSynced',
|
||||
sortable: true,
|
||||
name: 'Ops synced'
|
||||
},
|
||||
{
|
||||
field: 'syncLagTime',
|
||||
sortable: true,
|
||||
name: 'Last fetch time',
|
||||
render: syncLagTime => <span>{toSeconds(syncLagTime)}</span>
|
||||
},
|
||||
{
|
||||
field: 'syncLagOps',
|
||||
sortable: true,
|
||||
name: 'Sync Lag (ops)',
|
||||
},
|
||||
{
|
||||
field: 'error',
|
||||
sortable: true,
|
||||
name: 'Error',
|
||||
render: error => (
|
||||
<EuiTextColor color="danger">
|
||||
{error}
|
||||
</EuiTextColor>
|
||||
)
|
||||
}
|
||||
]}
|
||||
items={items}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
itemId="id"
|
||||
itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<EuiPage>
|
||||
<EuiPageBody>
|
||||
<EuiPageContent>
|
||||
<EuiPageContentBody>
|
||||
{this.renderTable()}
|
||||
</EuiPageContentBody>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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(<Ccr data={data} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -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';
|
|
@ -0,0 +1,215 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CcrShard that is renders an exception properly 1`] = `
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="m"
|
||||
>
|
||||
<EuiTitle
|
||||
color="danger"
|
||||
size="s"
|
||||
>
|
||||
<h3>
|
||||
<EuiTextColor
|
||||
color="danger"
|
||||
component="span"
|
||||
>
|
||||
Errors
|
||||
</EuiTextColor>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiBasicTable
|
||||
columns={
|
||||
Array [
|
||||
Object {
|
||||
"field": "exception.type",
|
||||
"name": "Type",
|
||||
},
|
||||
Object {
|
||||
"field": "exception.reason",
|
||||
"name": "Reason",
|
||||
"width": "75%",
|
||||
},
|
||||
]
|
||||
}
|
||||
items={
|
||||
Array [
|
||||
Object {
|
||||
"reason": "not sure but something happened",
|
||||
"type": "something_is_wrong",
|
||||
},
|
||||
]
|
||||
}
|
||||
noItemsMessage="No items found"
|
||||
responsive={true}
|
||||
/>
|
||||
</EuiPanel>
|
||||
`;
|
||||
|
||||
exports[`CcrShard that it renders normally 1`] = `
|
||||
<EuiPage
|
||||
restrictWidth={false}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "white",
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiPageBody
|
||||
restrictWidth={false}
|
||||
>
|
||||
<Status
|
||||
formattedLeader="leader on remote"
|
||||
oldestStat={
|
||||
Object {
|
||||
"number_of_failed_fetches": 0,
|
||||
"number_of_operations_indexed": 2976,
|
||||
}
|
||||
}
|
||||
stat={
|
||||
Object {
|
||||
"fetch_exceptions": Array [],
|
||||
"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,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
alignItems="stretch"
|
||||
component="div"
|
||||
direction="row"
|
||||
gutterSize="l"
|
||||
justifyContent="flexStart"
|
||||
responsive={true}
|
||||
wrap={true}
|
||||
>
|
||||
<React.Fragment>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
key="0"
|
||||
style={
|
||||
Object {
|
||||
"minWidth": "45%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="m"
|
||||
>
|
||||
<MonitoringTimeseriesContainer />
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
key="1"
|
||||
style={
|
||||
Object {
|
||||
"minWidth": "45%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="m"
|
||||
>
|
||||
<MonitoringTimeseriesContainer />
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</React.Fragment>
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule
|
||||
margin="l"
|
||||
size="full"
|
||||
/>
|
||||
<EuiAccordion
|
||||
buttonContent={
|
||||
<EuiTitle
|
||||
size="m"
|
||||
>
|
||||
<h2>
|
||||
Advanced
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
}
|
||||
id="ccrLatestStat"
|
||||
initialIsOpen={false}
|
||||
paddingSize="l"
|
||||
>
|
||||
<React.Fragment>
|
||||
<EuiTitle
|
||||
size="s"
|
||||
>
|
||||
<h4>
|
||||
September 27, 2018 9:32:09 AM
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiHorizontalRule
|
||||
margin="l"
|
||||
size="full"
|
||||
/>
|
||||
<EuiCodeBlock
|
||||
language="json"
|
||||
>
|
||||
{
|
||||
"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
|
||||
}
|
||||
</EuiCodeBlock>
|
||||
</React.Fragment>
|
||||
</EuiAccordion>
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
`;
|
|
@ -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) => (
|
||||
<EuiFlexItem style={{ minWidth: '45%' }} key={index}>
|
||||
<EuiPanel>
|
||||
<MonitoringTimeseriesContainer
|
||||
series={data}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
));
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{charts}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
renderErrors() {
|
||||
const { stat } = this.props;
|
||||
if (stat.fetch_exceptions && stat.fetch_exceptions.length > 0) {
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiPanel>
|
||||
<EuiTitle size="s" color="danger">
|
||||
<h3>
|
||||
<EuiTextColor color="danger">Errors</EuiTextColor>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiBasicTable
|
||||
items={stat.fetch_exceptions}
|
||||
columns={[
|
||||
{
|
||||
name: 'Type',
|
||||
field: 'exception.type'
|
||||
},
|
||||
{
|
||||
name: 'Reason',
|
||||
field: 'exception.reason',
|
||||
width: '75%'
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</EuiPanel>
|
||||
<EuiHorizontalRule/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderLatestStat() {
|
||||
const { stat, timestamp } = this.props;
|
||||
|
||||
return (
|
||||
<EuiAccordion
|
||||
id="ccrLatestStat"
|
||||
buttonContent={<EuiTitle><h2>Advanced</h2></EuiTitle>}
|
||||
paddingSize="l"
|
||||
>
|
||||
<Fragment>
|
||||
<EuiTitle size="s">
|
||||
<h4>{formatDateTimeLocal(timestamp)}</h4>
|
||||
</EuiTitle>
|
||||
<EuiHorizontalRule/>
|
||||
<EuiCodeBlock language="json">
|
||||
{JSON.stringify(stat, null, 2)}
|
||||
</EuiCodeBlock>
|
||||
</Fragment>
|
||||
</EuiAccordion>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { stat, oldestStat, formattedLeader } = this.props;
|
||||
|
||||
return (
|
||||
<EuiPage style={{ backgroundColor: 'white' }}>
|
||||
<EuiPageBody>
|
||||
<Status stat={stat} formattedLeader={formattedLeader} oldestStat={oldestStat}/>
|
||||
<EuiSpacer size="s"/>
|
||||
{this.renderErrors()}
|
||||
<EuiFlexGroup wrap>
|
||||
{this.renderCharts()}
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule/>
|
||||
{this.renderLatestStat()}
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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(<CcrShard {...props} />);
|
||||
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(<CcrShard {...localProps} />);
|
||||
expect(component.find('EuiPanel').get(0)).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -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';
|
|
@ -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 (
|
||||
<SummaryStatus
|
||||
metrics={metrics}
|
||||
data-test-subj="ccrDetailStatus"
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -35,6 +35,7 @@
|
|||
<a ng-if="!monitoringMain.instance" kbn-href="#/elasticsearch/nodes" class="kuiLocalTab" ng-class="{'kuiLocalTab-isSelected': monitoringMain.isActiveTab('nodes')}">Nodes</a>
|
||||
<a ng-if="!monitoringMain.instance" kbn-href="#/elasticsearch/indices" class="kuiLocalTab" ng-class="{'kuiLocalTab-isSelected': monitoringMain.isActiveTab('indices')}">Indices</a>
|
||||
<a ng-if="!monitoringMain.instance && monitoringMain.isMlSupported()" kbn-href="#/elasticsearch/ml_jobs" class="kuiLocalTab" ng-class="{'kuiLocalTab-isSelected': monitoringMain.isActiveTab('ml')}">Jobs</a>
|
||||
<a ng-if="!monitoringMain.instance" kbn-href="#/elasticsearch/ccr" class="kuiLocalTab" ng-class="{'kuiLocalTab-isSelected': monitoringMain.isActiveTab('ccr')}">CCR</a>
|
||||
<a
|
||||
ng-if="monitoringMain.instance && (monitoringMain.name === 'nodes' || monitoringMain.name === 'indices')"
|
||||
kbn-href="#/elasticsearch/{{ monitoringMain.name }}/{{ monitoringMain.resolver }}"
|
||||
|
|
|
@ -27,6 +27,8 @@ function getElasticsearchBreadcrumbs(mainInstance) {
|
|||
} else if (mainInstance.name === 'ml') {
|
||||
// ML Instance (for user later)
|
||||
breadcrumbs.push(createCrumb('#/elasticsearch/ml_jobs', 'Jobs'));
|
||||
} else if (mainInstance.name === 'ccr_shard') {
|
||||
breadcrumbs.push(createCrumb('#/elasticsearch/ccr', 'CCR'));
|
||||
}
|
||||
breadcrumbs.push(createCrumb(null, mainInstance.instance));
|
||||
} else {
|
||||
|
|
|
@ -17,6 +17,8 @@ import './elasticsearch/index/advanced';
|
|||
import './elasticsearch/nodes';
|
||||
import './elasticsearch/node';
|
||||
import './elasticsearch/node/advanced';
|
||||
import './elasticsearch/ccr';
|
||||
import './elasticsearch/ccr/shard';
|
||||
import './elasticsearch/ml_jobs';
|
||||
import './kibana/overview';
|
||||
import './kibana/instances';
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 globalState = $injector.get('globalState');
|
||||
const timeBounds = timefilter.getBounds();
|
||||
const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/elasticsearch/ccr`;
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<monitoring-main
|
||||
product="elasticsearch"
|
||||
name="ccr"
|
||||
data-test-subj="elasticsearchCcrListingPage"
|
||||
>
|
||||
<div id="elasticsearchCcrReact"></div>
|
||||
</monitoring-main>
|
|
@ -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(
|
||||
<Ccr data={data} />
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<monitoring-main
|
||||
product="elasticsearch"
|
||||
name="ccr_shard"
|
||||
instance="{{ instance }}"
|
||||
data-test-subj="elasticsearchCcrShardPage"
|
||||
>
|
||||
<div id="elasticsearchCcrShardReact"></div>
|
||||
</monitoring-main>
|
|
@ -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(
|
||||
<CcrShard {...props} />
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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';
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: ''
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -26,7 +26,9 @@ export {
|
|||
esNodeRoute,
|
||||
esNodesRoute,
|
||||
esOverviewRoute,
|
||||
mlJobRoute
|
||||
mlJobRoute,
|
||||
ccrRoute,
|
||||
ccrShardRoute
|
||||
} from './elasticsearch';
|
||||
export {
|
||||
clusterSettingsCheckRoute,
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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
|
||||
}]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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'));
|
||||
});
|
||||
}
|
||||
|
|
BIN
x-pack/test/functional/es_archives/monitoring/ccr/data.json.gz
Normal file
BIN
x-pack/test/functional/es_archives/monitoring/ccr/data.json.gz
Normal file
Binary file not shown.
1019
x-pack/test/functional/es_archives/monitoring/ccr/mappings.json
Normal file
1019
x-pack/test/functional/es_archives/monitoring/ccr/mappings.json
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue