[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:
Zacqary Adam Xeper 2021-09-02 19:28:11 -05:00 committed by GitHub
parent 95423242ac
commit a99360fa32
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 204 additions and 91 deletions

View file

@ -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,
});

View file

@ -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,

View file

@ -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",
},
},
}
`;

View file

@ -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) {

View file

@ -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([

View file

@ -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';

View file

@ -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']));
});
});

View file

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

View file

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