Skip to content

Commit

Permalink
[Monitoring] Ignore Duplicate Shards (#21057)
Browse files Browse the repository at this point in the history
[Monitoring] Ignore Duplicate Shards

This eliminates duplicate shards from the shard table by actively ignoring
them from the response.
  • Loading branch information
pickypg authored Jul 24, 2018
1 parent ecc0f5e commit 2f08220
Show file tree
Hide file tree
Showing 14 changed files with 136 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

import { uiModules } from 'ui/modules';
import { labels } from './lib/labels';
import { indicesByNodes } from './transformers/indicesByNodes';
import { nodesByIndices } from './transformers/nodesByIndices';
import { indicesByNodes } from './transformers/indices_by_nodes';
import { nodesByIndices } from './transformers/nodes_by_indices';
import template from './index.html';

const uiModule = uiModules.get('monitoring/directives', []);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function decorateShards(shards, nodes) {
}

return shards.map((shard) => {
const node = nodes[shard.resolver];
const node = nodes[shard.node];
shard.nodeName = (node && node.name) || null;
shard.type = 'shard';
shard.tooltip_message = getTooltipMessage(shard);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,23 @@
* you may not use this file except in compliance with the Elastic License.
*/

import expect from 'expect.js';
import { decorateShards } from '../decorateShards';
import { decorateShards } from './decorate_shards';

const nodes = {
'127.0.0.1:9300': {
'8WuXSoE6Q_-etoIhx0R3ag': {
attributes: {},
indexCount: 8,
name: 'node01',
node_ids: [ '8WuXSoE6Q_-etoIhx0R3ag' ],
resolver: '127.0.0.1:9300',
shardCount: 10,
transport_address: '127.0.0.1:9300',
type: 'master'
},
'127.0.0.1:9301': {
'ZRnQRUBBQHugqD-rqicFJw': {
attributes: {},
indexCount: 7,
name: 'node02',
node_ids: [ 'ZRnQRUBBQHugqD-rqicFJw' ],
resolver: '127.0.0.1:9301',
shardCount: 8,
transport_address: '127.0.0.1:9301',
type: 'node'
Expand All @@ -37,18 +34,16 @@ describe('decorateShards', () => {
node: '8WuXSoE6Q_-etoIhx0R3ag',
primary: true,
relocating_node: 'ZRnQRUBBQHugqD-rqicFJw',
resolver: '127.0.0.1:9300',
shard: 0,
state: 'RELOCATING'
};
const result = decorateShards([ shard ], nodes);
expect(result[0]).to.be.eql({
expect(result[0]).toMatchObject({
index: 'test',
node: '8WuXSoE6Q_-etoIhx0R3ag',
nodeName: 'node01',
primary: true,
relocating_node: 'ZRnQRUBBQHugqD-rqicFJw',
resolver: '127.0.0.1:9300',
shard: 0,
state: 'RELOCATING',
tooltip_message: 'Relocating to node02',
Expand All @@ -62,19 +57,17 @@ describe('decorateShards', () => {
node: '8WuXSoE6Q_-etoIhx0R3ag',
primary: true,
relocating_node: 'ZRnQRUBBQHugqD-rqicFJw',
resolver: '127.0.0.1:9300',
shard: 0,
state: 'RELOCATING'
};
// pass nodes object with only node01 value
const result = decorateShards([ shard ], { '127.0.0.1:9300': nodes['127.0.0.1:9300'] });
expect(result[0]).to.be.eql({
const result = decorateShards([ shard ], { '8WuXSoE6Q_-etoIhx0R3ag': nodes['8WuXSoE6Q_-etoIhx0R3ag'] });
expect(result[0]).toMatchObject({
index: 'test',
node: '8WuXSoE6Q_-etoIhx0R3ag',
nodeName: 'node01',
primary: true,
relocating_node: 'ZRnQRUBBQHugqD-rqicFJw',
resolver: '127.0.0.1:9300',
shard: 0,
state: 'RELOCATING',
tooltip_message: 'Relocating',
Expand All @@ -88,18 +81,16 @@ describe('decorateShards', () => {
node: 'ZRnQRUBBQHugqD-rqicFJw',
primary: true,
relocating_node: null,
resolver: '127.0.0.1:9301',
shard: 3,
state: 'STARTED'
};
const result = decorateShards([ shard ], nodes);
expect(result[0]).to.be.eql({
expect(result[0]).toMatchObject({
index: 'test2',
node: 'ZRnQRUBBQHugqD-rqicFJw',
nodeName: 'node02',
primary: true,
relocating_node: null,
resolver: '127.0.0.1:9301',
shard: 3,
state: 'STARTED',
tooltip_message: 'Started',
Expand All @@ -113,18 +104,16 @@ describe('decorateShards', () => {
node: '8WuXSoE6Q_-etoIhx0R3ag',
primary: false,
relocating_node: null,
resolver: '127.0.0.1:9300',
shard: 3,
state: 'INITIALIZING'
};
const result = decorateShards([ shard ], nodes);
expect(result[0]).to.be.eql({
expect(result[0]).toMatchObject({
index: 'test2',
node: '8WuXSoE6Q_-etoIhx0R3ag',
nodeName: 'node01',
primary: false,
relocating_node: null,
resolver: '127.0.0.1:9300',
shard: 3,
state: 'INITIALIZING',
tooltip_message: 'Initializing',
Expand All @@ -142,7 +131,7 @@ describe('decorateShards', () => {
state: 'UNASSIGNED'
};
const result = decorateShards([ shard ], nodes);
expect(result[0]).to.be.eql({
expect(result[0]).toMatchObject({
index: 'test2',
node: null,
nodeName: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


import _ from 'lodash';
import { decorateShards } from '../lib/decorateShards';
import { decorateShards } from '../lib/decorate_shards';

export function indicesByNodes() {
return function indicesByNodesFn(shards, nodes) {
Expand All @@ -29,8 +29,7 @@ export function indicesByNodes() {
}

function createNodeAddShard(obj, shard) {
const node = shard.resolver;
const index = shard.index;
const { node, index } = shard;

// If the node is null then it's an unassigned shard and we need to
// add it to the unassigned array.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import _ from 'lodash';
import { hasPrimaryChildren } from '../lib/hasPrimaryChildren';
import { decorateShards } from '../lib/decorateShards';
import { decorateShards } from '../lib/decorate_shards';

export function nodesByIndices() {
return function nodesByIndicesFn(shards, nodes) {
Expand All @@ -30,7 +30,7 @@ export function nodesByIndices() {
}

function createIndexAddShard(obj, shard) {
const node = shard.resolver || 'unassigned';
const node = shard.node || 'unassigned';
const index = shard.index;
if (!obj[node]) {
createNode(obj, nodes[node], node);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ import { getDefaultNodeFromId } from './get_default_node_from_id';
import { calculateNodeType } from './calculate_node_type';
import { getNodeTypeClassLabel } from './get_node_type_class_label';

export function handleResponse(clusterState, shardStats, resolver) {
export function handleResponse(clusterState, shardStats, nodeUuid) {
return response => {
let nodeSummary = {};
const nodeStatsHits = get(response, 'hits.hits', []);
const nodes = nodeStatsHits.map(hit => hit._source.source_node); // using [0] value because query results are sorted desc per timestamp
const node = nodes[0] || getDefaultNodeFromId(resolver);
const node = nodes[0] || getDefaultNodeFromId(nodeUuid);
const sourceStats = get(response, 'hits.hits[0]._source.node_stats');
const clusterNode = get(clusterState, [ 'nodes', resolver ]);
const clusterNode = get(clusterState, [ 'nodes', nodeUuid ]);
const stats = {
resolver,
resolver: nodeUuid,
node_ids: nodes.map(node => node.uuid),
attributes: node.attributes,
transport_address: node.transport_address,
Expand All @@ -30,7 +30,7 @@ export function handleResponse(clusterState, shardStats, resolver) {
};

if (clusterNode) {
const _shardStats = get(shardStats, [ 'nodes', resolver ], {});
const _shardStats = get(shardStats, [ 'nodes', nodeUuid ], {});
const calculatedNodeType = calculateNodeType(stats, get(clusterState, 'master_node')); // set type for labeling / iconography
const { nodeType, nodeTypeLabel, nodeTypeClass } = getNodeTypeClassLabel(node, calculatedNodeType);

Expand Down Expand Up @@ -62,17 +62,13 @@ export function handleResponse(clusterState, shardStats, resolver) {
};
}

export function getNodeSummary(req, esIndexPattern, clusterState, shardStats, { clusterUuid, resolver, start, end }) {
export function getNodeSummary(req, esIndexPattern, clusterState, shardStats, { clusterUuid, nodeUuid, start, end }) {
checkParam(esIndexPattern, 'esIndexPattern in elasticsearch/getNodeSummary');

// Get the params from the POST body for the request
const config = req.server.config();

// Build up the Elasticsearch request
const resolverKey = config.get('xpack.monitoring.node_resolver');
const metric = ElasticsearchMetric.getMetricFields();
const filters = [{
term: { [`source_node.${resolverKey}`]: resolver }
term: { 'source_node.uuid': nodeUuid }
}];
const params = {
index: esIndexPattern,
Expand All @@ -86,6 +82,6 @@ export function getNodeSummary(req, esIndexPattern, clusterState, shardStats, {

const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring');
return callWithRequest(req, 'search', params)
.then(handleResponse(clusterState, shardStats, resolver));
.then(handleResponse(clusterState, shardStats, nodeUuid));
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,34 @@ import { checkParam } from '../../error_missing_required';
import { createQuery } from '../../create_query';
import { ElasticsearchMetric } from '../../metrics';

export function handleResponse(nodeResolver) {
return response => {
const hits = get(response, 'hits.hits');
if (!hits) {
return [];
export function handleResponse(response) {
const hits = get(response, 'hits.hits');
if (!hits) {
return [];
}

// deduplicate any shards from earlier days with the same cluster state state_uuid
const uniqueShards = new Set();

// map into object with shard and source properties
return hits.reduce((shards, hit) => {
const shard = hit._source.shard;

if (shard) {
// note: if the request is for a node, then it's enough to deduplicate without primary, but for indices it displays both
const shardId = `${shard.index}-${shard.shard}-${shard.primary}-${shard.relocating_node}`;

if (!uniqueShards.has(shardId)) {
shards.push(shard);
uniqueShards.add(shardId);
}
}

// map into object with shard and source properties
return hits.map(hit => {
return {
...hit._source.shard,
resolver: get(hit, `_source.source_node[${nodeResolver}]`)
};
});
};
return shards;
}, []);
}

export function getShardAllocation(req, esIndexPattern, { nodeResolver, shardFilter, stateUuid, showSystemIndices = false }) {
export function getShardAllocation(req, esIndexPattern, { shardFilter, stateUuid, showSystemIndices = false }) {
checkParam(esIndexPattern, 'esIndexPattern in elasticsearch/getShardAllocation');

const filters = [ { term: { state_uuid: stateUuid } }, shardFilter ];
Expand All @@ -52,5 +62,5 @@ export function getShardAllocation(req, esIndexPattern, { nodeResolver, shardFil

const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring');
return callWithRequest(req, 'search', params)
.then(handleResponse(nodeResolver));
.then(handleResponse);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 { handleResponse } from './get_shard_allocation';

describe('get_shard_allocation', () => {

const exampleShardSource = {
cluster_uuid: "Xb_iFZeMSDialSlUwnH53w",
state_uuid: "Xobm9shMQGa2p52j-Vh61A",
type: "shards",
timestamp: "2018-07-05T23:59:51.259Z",
};

const shards = [
{
node: "X7Cq5UJ9TrS6gWVLItV-0A",
index: "my-index-v1",
relocating_node: null,
state: "STARTED",
shard: 0,
primary: true,
},
{
node: "Y8Cq5UJ9TrS6gWVLItV-0A",
index: "my-index-v1",
relocating_node: null,
state: "STARTED",
shard: 0,
primary: false,
},
];

describe('handleResponse', () => {

it('deduplicates shards', () => {
const nextTimestamp = "2018-07-06T00:00:01.259Z";
const hits = shards.map(shard => {
return {
_source: {
...exampleShardSource,
shard
}
};
});

// duplicate all of them; this is how a response would really come back, with only the timestamp changed
hits.concat(hits.map(hit => {
return {
...hit,
timestamp: nextTimestamp,
};
}));

const deduplicatedShards = handleResponse({ hits: { hits } });

expect(deduplicatedShards).toHaveLength(shards.length);
deduplicatedShards.forEach((shard, index) => {
expect(shard).toMatchObject(shards[index]);
});
});

});

});
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
/*
* @param {Object} config - Kibana config service
* @param {Boolean} includeNodes - whether to add the aggs for node shards
* @param {String} nodeResolver
*/
export function getShardAggs(config, includeNodes, nodeResolver) {
export function getShardAggs(config, includeNodes) {
const maxBucketSize = config.get('xpack.monitoring.max_bucket_size');
const aggSize = 10;
const indicesAgg = {
Expand All @@ -26,7 +25,7 @@ export function getShardAggs(config, includeNodes, nodeResolver) {
};
const nodesAgg = {
terms: {
field: `source_node.${nodeResolver}`,
field: 'shard.node',
size: maxBucketSize
},
aggs: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export function getShardStats(req, esIndexPattern, cluster, { includeNodes = fal
checkParam(esIndexPattern, 'esIndexPattern in elasticsearch/getShardStats');

const config = req.server.config();
const nodeResolver = config.get('xpack.monitoring.node_resolver');
const metric = ElasticsearchMetric.getMetricFields();
const params = {
index: esIndexPattern,
Expand All @@ -53,7 +52,7 @@ export function getShardStats(req, esIndexPattern, cluster, { includeNodes = fal
filters: [ { term: { state_uuid: get(cluster, 'cluster_state.state_uuid') } } ]
}),
aggs: {
...getShardAggs(config, includeNodes, nodeResolver)
...getShardAggs(config, includeNodes)
}
}
};
Expand Down
Loading

0 comments on commit 2f08220

Please sign in to comment.