Skip to content

Commit 38a1f7f

Browse files
[Metrics UI] Filter out APM nodes from the inventory view (#110300) (#111074)
* [Metrics UI] Filter out APM nodes from the inventory view * Update jest snapshots * Add tests for fs for filtering out APM nodes Co-authored-by: Zacqary Adam Xeper <Zacqary@users.noreply.github.com>
1 parent 8955d54 commit 38a1f7f

File tree

9 files changed

+204
-91
lines changed

9 files changed

+204
-91
lines changed

x-pack/plugins/infra/common/http_api/metrics_api.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ export const MetricsAPISeriesRT = rt.intersection([
7878
]);
7979

8080
export const MetricsAPIResponseRT = rt.type({
81-
series: rt.array(MetricsAPISeriesRT),
81+
series: rt.array(
82+
rt.intersection([MetricsAPISeriesRT, rt.partial({ metricsets: rt.array(rt.string) })])
83+
),
8284
info: MetricsAPIPageInfoRT,
8385
});
8486

x-pack/plugins/infra/server/lib/metrics/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,14 @@ export const query = async (
9191
return {
9292
series: groupings.buckets.map((bucket) => {
9393
const keys = Object.values(bucket.key);
94-
return convertHistogramBucketsToTimeseries(
94+
const metricsetNames = bucket.metricsets.buckets.map((m) => m.key);
95+
const timeseries = convertHistogramBucketsToTimeseries(
9596
keys,
9697
options,
9798
bucket.histogram.buckets,
9899
bucketSize * 1000
99100
);
101+
return { ...timeseries, metricsets: metricsetNames };
100102
}),
101103
info: {
102104
afterKey: returnAfterKey ? afterKey : null,

x-pack/plugins/infra/server/lib/metrics/lib/__snapshots__/create_aggregations.test.ts.snap

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/plugins/infra/server/lib/metrics/lib/create_aggregations.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export const createAggregations = (options: MetricsAPIRequest) => {
2525
},
2626
aggregations: createMetricsAggregations(options),
2727
},
28+
metricsets: {
29+
terms: {
30+
field: 'metricset.name',
31+
},
32+
},
2833
};
2934

3035
if (Array.isArray(options.groupBy) && options.groupBy.length) {

x-pack/plugins/infra/server/lib/metrics/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ export const HistogramResponseRT = rt.type({
6363
histogram: rt.type({
6464
buckets: rt.array(HistogramBucketRT),
6565
}),
66+
metricsets: rt.type({
67+
buckets: rt.array(
68+
rt.type({
69+
key: rt.string,
70+
doc_count: rt.number,
71+
})
72+
),
73+
}),
6674
});
6775

6876
const GroupingBucketRT = rt.intersection([

x-pack/plugins/infra/server/routes/snapshot/lib/get_nodes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { ESSearchClient } from '../../../lib/metrics/types';
1010
import { InfraSource } from '../../../lib/sources';
1111
import { transformRequestToMetricsAPIRequest } from './transform_request_to_metrics_api_request';
1212
import { queryAllData } from './query_all_data';
13-
import { transformMetricsApiResponseToSnapshotResponse } from './trasform_metrics_ui_response';
13+
import { transformMetricsApiResponseToSnapshotResponse } from './transform_metrics_ui_response';
1414
import { copyMissingMetrics } from './copy_missing_metrics';
1515
import { LogQueryFields } from '../../../services/log_queries/get_log_query_fields';
1616

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { transformMetricsApiResponseToSnapshotResponse } from './transform_metrics_ui_response';
9+
10+
jest.mock('./apply_metadata_to_last_path', () => ({
11+
applyMetadataToLastPath: (series: any) => [{ label: series.id }],
12+
}));
13+
14+
const now = 1630597319235;
15+
16+
describe('transformMetricsApiResponseToSnapshotResponse', () => {
17+
test('filters out nodes from APM which report no data', () => {
18+
const result = transformMetricsApiResponseToSnapshotResponse(
19+
{
20+
// @ts-ignore
21+
metrics: [{ id: 'cpu' }],
22+
},
23+
{
24+
includeTimeseries: false,
25+
nodeType: 'host',
26+
},
27+
{},
28+
{
29+
info: {
30+
interval: 60,
31+
},
32+
series: [
33+
{
34+
metricsets: ['app'],
35+
id: 'apm-node-with-no-data',
36+
columns: [],
37+
rows: [
38+
{
39+
timestamp: now,
40+
cpu: null,
41+
},
42+
],
43+
},
44+
{
45+
metricsets: ['app'],
46+
id: 'apm-node-with-data',
47+
columns: [],
48+
rows: [
49+
{
50+
timestamp: now,
51+
cpu: 1.0,
52+
},
53+
],
54+
},
55+
{
56+
metricsets: ['cpu'],
57+
id: 'metricbeat-node',
58+
columns: [],
59+
rows: [
60+
{
61+
timestamp: now,
62+
cpu: 1.0,
63+
},
64+
],
65+
},
66+
],
67+
}
68+
);
69+
const nodeNames = result.nodes.map((n) => n.name);
70+
expect(nodeNames).toEqual(expect.arrayContaining(['metricbeat-node', 'apm-node-with-data']));
71+
expect(nodeNames).not.toEqual(expect.arrayContaining(['apm-node']));
72+
});
73+
});
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { get, max, sum, last, isNumber } from 'lodash';
9+
import { SnapshotMetricType } from '../../../../common/inventory_models/types';
10+
import {
11+
MetricsAPIResponse,
12+
SnapshotNodeResponse,
13+
MetricsAPIRequest,
14+
MetricsExplorerColumnType,
15+
MetricsAPIRow,
16+
SnapshotRequest,
17+
SnapshotNodePath,
18+
SnapshotNodeMetric,
19+
SnapshotNode,
20+
} from '../../../../common/http_api';
21+
import { META_KEY } from './constants';
22+
import { InfraSource } from '../../../lib/sources';
23+
import { applyMetadataToLastPath } from './apply_metadata_to_last_path';
24+
25+
const getMetricValue = (row: MetricsAPIRow) => {
26+
if (!isNumber(row.metric_0)) return null;
27+
const value = row.metric_0;
28+
return isFinite(value) ? value : null;
29+
};
30+
31+
const calculateMax = (rows: MetricsAPIRow[]) => {
32+
return max(rows.map(getMetricValue)) || 0;
33+
};
34+
35+
const calculateAvg = (rows: MetricsAPIRow[]): number => {
36+
return sum(rows.map(getMetricValue)) / rows.length || 0;
37+
};
38+
39+
const getLastValue = (rows: MetricsAPIRow[]) => {
40+
const row = last(rows);
41+
if (!row) return null;
42+
return getMetricValue(row);
43+
};
44+
45+
export const transformMetricsApiResponseToSnapshotResponse = (
46+
options: MetricsAPIRequest,
47+
snapshotRequest: SnapshotRequest,
48+
source: InfraSource,
49+
metricsApiResponse: MetricsAPIResponse
50+
): SnapshotNodeResponse => {
51+
const nodes = metricsApiResponse.series
52+
.map((series) => {
53+
const node = {
54+
metrics: options.metrics
55+
.filter((m) => m.id !== META_KEY)
56+
.map((metric) => {
57+
const name = metric.id as SnapshotMetricType;
58+
const timeseries = {
59+
id: name,
60+
columns: [
61+
{ name: 'timestamp', type: 'date' as MetricsExplorerColumnType },
62+
{ name: 'metric_0', type: 'number' as MetricsExplorerColumnType },
63+
],
64+
rows: series.rows.map((row) => {
65+
return { timestamp: row.timestamp, metric_0: get(row, metric.id, null) };
66+
}),
67+
};
68+
const maxValue = calculateMax(timeseries.rows);
69+
const avg = calculateAvg(timeseries.rows);
70+
const value = getLastValue(timeseries.rows);
71+
const nodeMetric: SnapshotNodeMetric = { name, max: maxValue, value, avg };
72+
if (snapshotRequest.includeTimeseries) {
73+
nodeMetric.timeseries = timeseries;
74+
}
75+
return nodeMetric;
76+
}),
77+
path:
78+
series.keys?.map((key) => {
79+
return { value: key, label: key } as SnapshotNodePath;
80+
}) ?? [],
81+
name: '',
82+
};
83+
84+
const isNoData = node.metrics.every((m) => m.value === null);
85+
const isAPMNode = series.metricsets?.includes('app');
86+
if (isNoData && isAPMNode) return null;
87+
88+
const path = applyMetadataToLastPath(series, node, snapshotRequest, source);
89+
const lastPath = last(path);
90+
const name = lastPath?.label ?? 'N/A';
91+
92+
return { ...node, path, name };
93+
})
94+
.filter((n) => n !== null) as SnapshotNode[];
95+
return { nodes, interval: `${metricsApiResponse.info.interval}s` };
96+
};

x-pack/plugins/infra/server/routes/snapshot/lib/trasform_metrics_ui_response.ts

Lines changed: 0 additions & 88 deletions
This file was deleted.

0 commit comments

Comments
 (0)