[Metrics UI] Filter out APM nodes from the inventory view (#110300)
* [Metrics UI] Filter out APM nodes from the inventory view * Update jest snapshots * Add tests for fs for filtering out APM nodes
This commit is contained in:
parent
95423242ac
commit
a99360fa32
|
@ -78,7 +78,9 @@ export const MetricsAPISeriesRT = rt.intersection([
|
|||
]);
|
||||
|
||||
export const MetricsAPIResponseRT = rt.type({
|
||||
series: rt.array(MetricsAPISeriesRT),
|
||||
series: rt.array(
|
||||
rt.intersection([MetricsAPISeriesRT, rt.partial({ metricsets: rt.array(rt.string) })])
|
||||
),
|
||||
info: MetricsAPIPageInfoRT,
|
||||
});
|
||||
|
||||
|
|
|
@ -91,12 +91,14 @@ export const query = async (
|
|||
return {
|
||||
series: groupings.buckets.map((bucket) => {
|
||||
const keys = Object.values(bucket.key);
|
||||
return convertHistogramBucketsToTimeseries(
|
||||
const metricsetNames = bucket.metricsets.buckets.map((m) => m.key);
|
||||
const timeseries = convertHistogramBucketsToTimeseries(
|
||||
keys,
|
||||
options,
|
||||
bucket.histogram.buckets,
|
||||
bucketSize * 1000
|
||||
);
|
||||
return { ...timeseries, metricsets: metricsetNames };
|
||||
}),
|
||||
info: {
|
||||
afterKey: returnAfterKey ? afterKey : null,
|
||||
|
|
|
@ -20,6 +20,11 @@ Object {
|
|||
"offset": "-60000ms",
|
||||
},
|
||||
},
|
||||
"metricsets": Object {
|
||||
"terms": Object {
|
||||
"field": "metricset.name",
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -45,6 +50,11 @@ Object {
|
|||
"offset": "0s",
|
||||
},
|
||||
},
|
||||
"metricsets": Object {
|
||||
"terms": Object {
|
||||
"field": "metricset.name",
|
||||
},
|
||||
},
|
||||
},
|
||||
"composite": Object {
|
||||
"size": 20,
|
||||
|
@ -82,5 +92,10 @@ Object {
|
|||
"offset": "0s",
|
||||
},
|
||||
},
|
||||
"metricsets": Object {
|
||||
"terms": Object {
|
||||
"field": "metricset.name",
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -25,6 +25,11 @@ export const createAggregations = (options: MetricsAPIRequest) => {
|
|||
},
|
||||
aggregations: createMetricsAggregations(options),
|
||||
},
|
||||
metricsets: {
|
||||
terms: {
|
||||
field: 'metricset.name',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (Array.isArray(options.groupBy) && options.groupBy.length) {
|
||||
|
|
|
@ -63,6 +63,14 @@ export const HistogramResponseRT = rt.type({
|
|||
histogram: rt.type({
|
||||
buckets: rt.array(HistogramBucketRT),
|
||||
}),
|
||||
metricsets: rt.type({
|
||||
buckets: rt.array(
|
||||
rt.type({
|
||||
key: rt.string,
|
||||
doc_count: rt.number,
|
||||
})
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
const GroupingBucketRT = rt.intersection([
|
||||
|
|
|
@ -10,7 +10,7 @@ import { ESSearchClient } from '../../../lib/metrics/types';
|
|||
import { InfraSource } from '../../../lib/sources';
|
||||
import { transformRequestToMetricsAPIRequest } from './transform_request_to_metrics_api_request';
|
||||
import { queryAllData } from './query_all_data';
|
||||
import { transformMetricsApiResponseToSnapshotResponse } from './trasform_metrics_ui_response';
|
||||
import { transformMetricsApiResponseToSnapshotResponse } from './transform_metrics_ui_response';
|
||||
import { copyMissingMetrics } from './copy_missing_metrics';
|
||||
import { LogQueryFields } from '../../../services/log_queries/get_log_query_fields';
|
||||
|
||||
|
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { transformMetricsApiResponseToSnapshotResponse } from './transform_metrics_ui_response';
|
||||
|
||||
jest.mock('./apply_metadata_to_last_path', () => ({
|
||||
applyMetadataToLastPath: (series: any) => [{ label: series.id }],
|
||||
}));
|
||||
|
||||
const now = 1630597319235;
|
||||
|
||||
describe('transformMetricsApiResponseToSnapshotResponse', () => {
|
||||
test('filters out nodes from APM which report no data', () => {
|
||||
const result = transformMetricsApiResponseToSnapshotResponse(
|
||||
{
|
||||
// @ts-ignore
|
||||
metrics: [{ id: 'cpu' }],
|
||||
},
|
||||
{
|
||||
includeTimeseries: false,
|
||||
nodeType: 'host',
|
||||
},
|
||||
{},
|
||||
{
|
||||
info: {
|
||||
interval: 60,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
metricsets: ['app'],
|
||||
id: 'apm-node-with-no-data',
|
||||
columns: [],
|
||||
rows: [
|
||||
{
|
||||
timestamp: now,
|
||||
cpu: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
metricsets: ['app'],
|
||||
id: 'apm-node-with-data',
|
||||
columns: [],
|
||||
rows: [
|
||||
{
|
||||
timestamp: now,
|
||||
cpu: 1.0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
metricsets: ['cpu'],
|
||||
id: 'metricbeat-node',
|
||||
columns: [],
|
||||
rows: [
|
||||
{
|
||||
timestamp: now,
|
||||
cpu: 1.0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
const nodeNames = result.nodes.map((n) => n.name);
|
||||
expect(nodeNames).toEqual(expect.arrayContaining(['metricbeat-node', 'apm-node-with-data']));
|
||||
expect(nodeNames).not.toEqual(expect.arrayContaining(['apm-node']));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { get, max, sum, last, isNumber } from 'lodash';
|
||||
import { SnapshotMetricType } from '../../../../common/inventory_models/types';
|
||||
import {
|
||||
MetricsAPIResponse,
|
||||
SnapshotNodeResponse,
|
||||
MetricsAPIRequest,
|
||||
MetricsExplorerColumnType,
|
||||
MetricsAPIRow,
|
||||
SnapshotRequest,
|
||||
SnapshotNodePath,
|
||||
SnapshotNodeMetric,
|
||||
SnapshotNode,
|
||||
} from '../../../../common/http_api';
|
||||
import { META_KEY } from './constants';
|
||||
import { InfraSource } from '../../../lib/sources';
|
||||
import { applyMetadataToLastPath } from './apply_metadata_to_last_path';
|
||||
|
||||
const getMetricValue = (row: MetricsAPIRow) => {
|
||||
if (!isNumber(row.metric_0)) return null;
|
||||
const value = row.metric_0;
|
||||
return isFinite(value) ? value : null;
|
||||
};
|
||||
|
||||
const calculateMax = (rows: MetricsAPIRow[]) => {
|
||||
return max(rows.map(getMetricValue)) || 0;
|
||||
};
|
||||
|
||||
const calculateAvg = (rows: MetricsAPIRow[]): number => {
|
||||
return sum(rows.map(getMetricValue)) / rows.length || 0;
|
||||
};
|
||||
|
||||
const getLastValue = (rows: MetricsAPIRow[]) => {
|
||||
const row = last(rows);
|
||||
if (!row) return null;
|
||||
return getMetricValue(row);
|
||||
};
|
||||
|
||||
export const transformMetricsApiResponseToSnapshotResponse = (
|
||||
options: MetricsAPIRequest,
|
||||
snapshotRequest: SnapshotRequest,
|
||||
source: InfraSource,
|
||||
metricsApiResponse: MetricsAPIResponse
|
||||
): SnapshotNodeResponse => {
|
||||
const nodes = metricsApiResponse.series
|
||||
.map((series) => {
|
||||
const node = {
|
||||
metrics: options.metrics
|
||||
.filter((m) => m.id !== META_KEY)
|
||||
.map((metric) => {
|
||||
const name = metric.id as SnapshotMetricType;
|
||||
const timeseries = {
|
||||
id: name,
|
||||
columns: [
|
||||
{ name: 'timestamp', type: 'date' as MetricsExplorerColumnType },
|
||||
{ name: 'metric_0', type: 'number' as MetricsExplorerColumnType },
|
||||
],
|
||||
rows: series.rows.map((row) => {
|
||||
return { timestamp: row.timestamp, metric_0: get(row, metric.id, null) };
|
||||
}),
|
||||
};
|
||||
const maxValue = calculateMax(timeseries.rows);
|
||||
const avg = calculateAvg(timeseries.rows);
|
||||
const value = getLastValue(timeseries.rows);
|
||||
const nodeMetric: SnapshotNodeMetric = { name, max: maxValue, value, avg };
|
||||
if (snapshotRequest.includeTimeseries) {
|
||||
nodeMetric.timeseries = timeseries;
|
||||
}
|
||||
return nodeMetric;
|
||||
}),
|
||||
path:
|
||||
series.keys?.map((key) => {
|
||||
return { value: key, label: key } as SnapshotNodePath;
|
||||
}) ?? [],
|
||||
name: '',
|
||||
};
|
||||
|
||||
const isNoData = node.metrics.every((m) => m.value === null);
|
||||
const isAPMNode = series.metricsets?.includes('app');
|
||||
if (isNoData && isAPMNode) return null;
|
||||
|
||||
const path = applyMetadataToLastPath(series, node, snapshotRequest, source);
|
||||
const lastPath = last(path);
|
||||
const name = lastPath?.label ?? 'N/A';
|
||||
|
||||
return { ...node, path, name };
|
||||
})
|
||||
.filter((n) => n !== null) as SnapshotNode[];
|
||||
return { nodes, interval: `${metricsApiResponse.info.interval}s` };
|
||||
};
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { get, max, sum, last, isNumber } from 'lodash';
|
||||
import { SnapshotMetricType } from '../../../../common/inventory_models/types';
|
||||
import {
|
||||
MetricsAPIResponse,
|
||||
SnapshotNodeResponse,
|
||||
MetricsAPIRequest,
|
||||
MetricsExplorerColumnType,
|
||||
MetricsAPIRow,
|
||||
SnapshotRequest,
|
||||
SnapshotNodePath,
|
||||
SnapshotNodeMetric,
|
||||
} from '../../../../common/http_api';
|
||||
import { META_KEY } from './constants';
|
||||
import { InfraSource } from '../../../lib/sources';
|
||||
import { applyMetadataToLastPath } from './apply_metadata_to_last_path';
|
||||
|
||||
const getMetricValue = (row: MetricsAPIRow) => {
|
||||
if (!isNumber(row.metric_0)) return null;
|
||||
const value = row.metric_0;
|
||||
return isFinite(value) ? value : null;
|
||||
};
|
||||
|
||||
const calculateMax = (rows: MetricsAPIRow[]) => {
|
||||
return max(rows.map(getMetricValue)) || 0;
|
||||
};
|
||||
|
||||
const calculateAvg = (rows: MetricsAPIRow[]): number => {
|
||||
return sum(rows.map(getMetricValue)) / rows.length || 0;
|
||||
};
|
||||
|
||||
const getLastValue = (rows: MetricsAPIRow[]) => {
|
||||
const row = last(rows);
|
||||
if (!row) return null;
|
||||
return getMetricValue(row);
|
||||
};
|
||||
|
||||
export const transformMetricsApiResponseToSnapshotResponse = (
|
||||
options: MetricsAPIRequest,
|
||||
snapshotRequest: SnapshotRequest,
|
||||
source: InfraSource,
|
||||
metricsApiResponse: MetricsAPIResponse
|
||||
): SnapshotNodeResponse => {
|
||||
const nodes = metricsApiResponse.series.map((series) => {
|
||||
const node = {
|
||||
metrics: options.metrics
|
||||
.filter((m) => m.id !== META_KEY)
|
||||
.map((metric) => {
|
||||
const name = metric.id as SnapshotMetricType;
|
||||
const timeseries = {
|
||||
id: name,
|
||||
columns: [
|
||||
{ name: 'timestamp', type: 'date' as MetricsExplorerColumnType },
|
||||
{ name: 'metric_0', type: 'number' as MetricsExplorerColumnType },
|
||||
],
|
||||
rows: series.rows.map((row) => {
|
||||
return { timestamp: row.timestamp, metric_0: get(row, metric.id, null) };
|
||||
}),
|
||||
};
|
||||
const maxValue = calculateMax(timeseries.rows);
|
||||
const avg = calculateAvg(timeseries.rows);
|
||||
const value = getLastValue(timeseries.rows);
|
||||
const nodeMetric: SnapshotNodeMetric = { name, max: maxValue, value, avg };
|
||||
if (snapshotRequest.includeTimeseries) {
|
||||
nodeMetric.timeseries = timeseries;
|
||||
}
|
||||
return nodeMetric;
|
||||
}),
|
||||
path:
|
||||
series.keys?.map((key) => {
|
||||
return { value: key, label: key } as SnapshotNodePath;
|
||||
}) ?? [],
|
||||
name: '',
|
||||
};
|
||||
|
||||
const path = applyMetadataToLastPath(series, node, snapshotRequest, source);
|
||||
const lastPath = last(path);
|
||||
const name = (lastPath && lastPath.label) || 'N/A';
|
||||
return { ...node, path, name };
|
||||
});
|
||||
return { nodes, interval: `${metricsApiResponse.info.interval}s` };
|
||||
};
|
Loading…
Reference in a new issue