[Monitoring] Added cgroup option for APM cpu usage (#90873)

* Added APM cpu cgroup

* Fixed tests

* Fixed i18n

* Removed agent logic and fixed tests

* Fixed test

* Fixed tests and backup field

* Removed backup field fix

* Fixed cluster tests

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
igoristic 2021-02-23 12:19:10 -05:00 committed by GitHub
parent 45155f089d
commit 0e6c38d630
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 252 additions and 108 deletions

View file

@ -0,0 +1,85 @@
/*
* 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 React from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiFlexItem,
EuiPanel,
EuiSpacer,
EuiPage,
EuiPageBody,
EuiFlexGroup,
EuiPageContent,
EuiScreenReaderOnly,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
// @ts-ignore could not find declaration file
import { MonitoringTimeseriesContainer } from '../chart';
// @ts-ignore could not find declaration file
import { Status } from './instance/status';
interface Props {
stats: unknown;
metrics: { [key: string]: unknown };
seriesToShow: unknown[];
title: string;
}
const createCharts = (series: unknown[], props: Partial<Props>) => {
return series.map((data, index) => {
return (
<EuiFlexItem style={{ minWidth: '45%' }} key={index}>
<MonitoringTimeseriesContainer {...props} series={data} />
</EuiFlexItem>
);
});
};
export const ApmMetrics = ({ stats, metrics, seriesToShow, title, ...props }: Props) => {
const topSeries = [metrics.apm_cpu, metrics.apm_memory, metrics.apm_os_load];
return (
<EuiPage>
<EuiPageBody>
<EuiScreenReaderOnly>
<h1>
<FormattedMessage
id="xpack.monitoring.apm.metrics.heading"
defaultMessage="APM server"
/>
</h1>
</EuiScreenReaderOnly>
<EuiPanel>
<Status stats={stats} />
</EuiPanel>
<EuiSpacer size="m" />
<EuiPanel>
<EuiTitle>
<h3>
{i18n.translate('xpack.monitoring.apm.metrics.topCharts.nonAgentTitle', {
defaultMessage: 'APM Server - Resource Usage',
})}
</h3>
</EuiTitle>
<EuiSpacer size="m" />
<EuiFlexGroup wrap>{createCharts(topSeries, props)}</EuiFlexGroup>
</EuiPanel>
<EuiSpacer size="m" />
<EuiPageContent>
<EuiTitle>
<h3>{title}</h3>
</EuiTitle>
<EuiSpacer size="m" />
<EuiFlexGroup wrap>{createCharts(seriesToShow, props)}</EuiFlexGroup>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
};

View file

@ -6,66 +6,28 @@
*/
import React from 'react';
import { MonitoringTimeseriesContainer } from '../../chart';
import {
EuiFlexItem,
EuiPanel,
EuiSpacer,
EuiPage,
EuiPageBody,
EuiFlexGroup,
EuiPageContent,
EuiScreenReaderOnly,
} from '@elastic/eui';
import { Status } from './status';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { ApmMetrics } from '../apm_metrics';
export function ApmServerInstance({ summary, metrics, ...props }) {
const title = i18n.translate('xpack.monitoring.apm.instance.panels.title', {
defaultMessage: 'APM Server - Metrics',
});
export function ApmServerInstance(props) {
const { metrics } = props;
const seriesToShow = [
metrics.apm_requests,
metrics.apm_responses_valid,
metrics.apm_responses_errors,
metrics.apm_acm_request_count,
metrics.apm_acm_response,
metrics.apm_acm_response_errors,
metrics.apm_output_events_rate_success,
metrics.apm_output_events_rate_failure,
metrics.apm_transformations,
metrics.apm_cpu,
metrics.apm_memory,
metrics.apm_os_load,
];
const charts = seriesToShow.map((data, index) => (
<EuiFlexItem style={{ minWidth: '45%' }} key={index}>
<MonitoringTimeseriesContainer series={data} {...props} />
</EuiFlexItem>
));
return (
<EuiPage>
<EuiPageBody>
<EuiScreenReaderOnly>
<h1>
<FormattedMessage
id="xpack.monitoring.apm.instance.heading"
defaultMessage="APM server instance"
/>
</h1>
</EuiScreenReaderOnly>
<EuiPanel>
<Status stats={summary} />
</EuiPanel>
<EuiSpacer size="m" />
<EuiPageContent>
<EuiFlexGroup wrap>{charts}</EuiFlexGroup>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
const stats = props.summary;
const metricProps = { ...props, title, seriesToShow, stats };
return <ApmMetrics {...metricProps} />;
}

View file

@ -15,7 +15,7 @@ import { CALCULATE_DURATION_SINCE } from '../../../../common/constants';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
export function Status({ alerts, stats }) {
export function Status({ alerts = null, stats }) {
const { name, output, version, uptime, timeOfLastEvent } = stats;
const metrics = [

View file

@ -6,62 +6,24 @@
*/
import React from 'react';
import { MonitoringTimeseriesContainer } from '../../chart';
import {
EuiSpacer,
EuiPage,
EuiFlexGroup,
EuiFlexItem,
EuiPageBody,
EuiPanel,
EuiPageContent,
EuiScreenReaderOnly,
} from '@elastic/eui';
import { Status } from '../instances/status';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { ApmMetrics } from '../apm_metrics';
export function ApmOverview({ stats, metrics, alerts, ...props }) {
const title = i18n.translate('xpack.monitoring.apm.overview.panels.title', {
defaultMessage: 'APM Server - Metrics',
});
export function ApmOverview(props) {
const { metrics } = props;
const seriesToShow = [
metrics.apm_responses_valid,
metrics.apm_responses_errors,
metrics.apm_output_events_rate_success,
metrics.apm_output_events_rate_failure,
metrics.apm_requests,
metrics.apm_transformations,
metrics.apm_cpu,
metrics.apm_memory,
metrics.apm_os_load,
];
const charts = seriesToShow.map((data, index) => (
<EuiFlexItem style={{ minWidth: '45%' }} key={index}>
<MonitoringTimeseriesContainer series={data} {...props} />
</EuiFlexItem>
));
return (
<EuiPage>
<EuiPageBody>
<EuiScreenReaderOnly>
<h1>
<FormattedMessage
id="xpack.monitoring.apm.overview.heading"
defaultMessage="APM server overview"
/>
</h1>
</EuiScreenReaderOnly>
<EuiPanel>
<Status stats={stats} alerts={alerts} />
</EuiPanel>
<EuiSpacer size="m" />
<EuiPageContent>
<EuiFlexGroup wrap>{charts}</EuiFlexGroup>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
const metricProps = { ...props, title, seriesToShow };
return <ApmMetrics {...metricProps} />;
}

View file

@ -135,7 +135,7 @@ export function ApmPanel(props) {
<FormattedMessage
id="xpack.monitoring.cluster.overview.apmPanel.serversTotalLinkLabel"
defaultMessage="APM servers: {apmsTotal}"
values={{ apmsTotal: <span data-test-subj="apmsTotal">{apmsTotal}</span> }}
values={{ apmsTotal }}
/>
</EuiLink>
</h3>

View file

@ -56,6 +56,9 @@ describe('config schema', () => {
"enabled": true,
},
"container": Object {
"apm": Object {
"enabled": false,
},
"elasticsearch": Object {
"enabled": false,
},

View file

@ -40,6 +40,9 @@ export const configSchema = schema.object({
elasticsearch: schema.object({
enabled: schema.boolean({ defaultValue: false }),
}),
apm: schema.object({
enabled: schema.boolean({ defaultValue: false }),
}),
logstash: schema.object({
enabled: schema.boolean({ defaultValue: false }),
}),

View file

@ -18,7 +18,11 @@ import { getTimeOfLastEvent } from './_get_time_of_last_event';
import { LegacyRequest } from '../../types';
import { ElasticsearchResponse } from '../../../common/types/es';
export function handleResponse(response: ElasticsearchResponse, apmUuid: string) {
export function handleResponse(
response: ElasticsearchResponse,
apmUuid: string,
config: { get: (key: string) => string | undefined }
) {
if (!response.hits || response.hits.hits.length === 0) {
return {};
}
@ -59,6 +63,9 @@ export function handleResponse(response: ElasticsearchResponse, apmUuid: string)
eventsEmitted: getDiffCalculation(eventsEmittedLast, eventsEmittedFirst),
eventsDropped: getDiffCalculation(eventsDroppedLast, eventsDroppedFirst),
bytesWritten: getDiffCalculation(bytesWrittenLast, bytesWrittenFirst),
config: {
container: config.get('monitoring.ui.container.apm.enabled'),
},
};
}
@ -138,7 +145,7 @@ export async function getApmInfo(
}),
]);
const formattedResponse = handleResponse(response, apmUuid);
const formattedResponse = handleResponse(response, apmUuid, req.server.config());
return {
...formattedResponse,
timeOfLastEvent,

View file

@ -73,6 +73,9 @@ export function getApmsForClusters(req, apmIndexPattern, clusters) {
const formattedResponse = handleResponse(clusterUuid, response);
return {
...formattedResponse,
config: {
container: config.get('monitoring.ui.container.apm.enabled'),
},
stats: {
...formattedResponse.stats,
timeOfLastEvent,

View file

@ -258,7 +258,11 @@ export async function getClustersFromRequest(
: [];
apmsByCluster.forEach((apm) => {
const clusterIndex = findIndex(clusters, { cluster_uuid: apm.clusterUuid });
set(clusters[clusterIndex], 'apm', apm.stats);
const { stats, config } = apm;
clusters[clusterIndex].apm = {
...stats,
config,
};
});
// check ccr configuration

View file

@ -412,6 +412,55 @@ Object {
"units": "/s",
"uuidField": "cluster_uuid",
},
"apm_cgroup_cpu": QuotaMetric {
"aggs": Object {
"periods": Object {
"max": Object {
"field": "beats_stats.metrics.beat.cgroup.cpu.stats.periods",
},
},
"periods_deriv": Object {
"derivative": Object {
"buckets_path": "periods",
"gap_policy": "skip",
"unit": "1s",
},
},
"quota": Object {
"min": Object {
"field": "beats_stats.metrics.beat.cgroup.cpu.cfs.quota.us",
},
},
"usage": Object {
"max": Object {
"field": "beats_stats.metrics.beat.cgroup.cpuacct.total.ns",
},
},
"usage_deriv": Object {
"derivative": Object {
"buckets_path": "usage",
"gap_policy": "skip",
"unit": "1s",
},
},
},
"app": "apm",
"calculation": [Function],
"derivative": true,
"description": "CPU Usage time compared to the CPU quota shown in percentage. If CPU quotas are not set, then no data will be shown.",
"field": "beats_stats.metrics.beat.cpu.total.value",
"fieldSource": "beats_stats.metrics.beat.cgroup",
"format": "0,0.[00]",
"label": "Cgroup CPU Utilization",
"metricAgg": "max",
"periodsField": "cpu.stats.periods",
"quotaField": "cpu.cfs.quota.us",
"timestampField": "beats_stats.timestamp",
"title": "CPU Utilization",
"units": "%",
"usageField": "cpuacct.total.ns",
"uuidField": "beats_stats.beat.uuid",
},
"apm_cpu_total": ApmCpuUtilizationMetric {
"app": "apm",
"calculation": [Function],

View file

@ -8,6 +8,7 @@
import { LARGE_BYTES, LARGE_FLOAT } from '../../../../common/formatting';
import { ApmMetric, ApmCpuUtilizationMetric, ApmEventsRateClusterMetric } from './classes';
import { i18n } from '@kbn/i18n';
import { QuotaMetric } from '../classes';
const instanceSystemLoadTitle = i18n.translate(
'xpack.monitoring.metrics.apmInstance.systemLoadTitle',
@ -39,6 +40,31 @@ export const metrics = {
),
field: 'beats_stats.metrics.beat.cpu.total.value',
}),
apm_cgroup_cpu: new QuotaMetric({
app: 'apm',
...ApmMetric.getMetricFields(),
fieldSource: 'beats_stats.metrics.beat.cgroup',
usageField: 'cpuacct.total.ns',
periodsField: 'cpu.stats.periods',
quotaField: 'cpu.cfs.quota.us',
field: 'beats_stats.metrics.beat.cpu.total.value', // backup field if quota is not configured
title: i18n.translate('xpack.monitoring.metrics.apmInstance.cpuUtilizationTitle', {
defaultMessage: 'CPU Utilization',
}),
label: i18n.translate(
'xpack.monitoring.metrics.apmInstance.cpuUtilization.cgroupCpuUtilizationLabel',
{
defaultMessage: 'Cgroup CPU Utilization',
}
),
description: i18n.translate(
'xpack.monitoring.metrics.apmInstance.cpuUtilization.cgroupCpuUtilizationDescription',
{
defaultMessage:
'CPU Usage time compared to the CPU quota shown in percentage. If CPU quotas are not set, then no data will be shown.',
}
),
}),
apm_system_os_load_1: new ApmMetric({
field: 'beats_stats.metrics.system.load.1',
label: i18n.translate('xpack.monitoring.metrics.apmInstance.systemLoad.last1MinuteLabel', {

View file

@ -5,10 +5,15 @@
* 2.0.
*/
import { get } from 'lodash';
import { getApmsForClusters } from '../../../../lib/apm/get_apms_for_clusters';
export const getApmClusterStatus = (req, apmIndexPattern, { clusterUuid }) => {
const clusters = [{ cluster_uuid: clusterUuid }];
return getApmsForClusters(req, apmIndexPattern, clusters).then((apms) => get(apms, '[0].stats'));
return getApmsForClusters(req, apmIndexPattern, clusters).then((apms) => {
const [{ stats, config }] = apms;
return {
...stats,
config,
};
});
};

View file

@ -39,6 +39,12 @@ export function apmInstanceRoute(server) {
const ccs = req.payload.ccs;
const apmIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_BEATS, ccs);
const showCgroupMetrics = config.get('monitoring.ui.container.apm.enabled');
if (showCgroupMetrics) {
const metricCpu = metricSet.find((m) => m.name === 'apm_cpu');
metricCpu.keys = ['apm_cgroup_cpu'];
}
try {
const [metrics, apmSummary] = await Promise.all([
getMetrics(req, apmIndexPattern, metricSet, [

View file

@ -37,6 +37,12 @@ export function apmOverviewRoute(server) {
const clusterUuid = req.params.clusterUuid;
const apmIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_BEATS, ccs);
const showCgroupMetrics = config.get('monitoring.ui.container.apm.enabled');
if (showCgroupMetrics) {
const metricCpu = metricSet.find((m) => m.name === 'apm_cpu');
metricCpu.keys = ['apm_cgroup_cpu'];
}
try {
const [stats, metrics] = await Promise.all([
getApmClusterStatus(req, apmIndexPattern, { clusterUuid }),

View file

@ -14451,7 +14451,6 @@
"xpack.monitoring.alerts.validation.threshold": "有効な数字が必要です。",
"xpack.monitoring.alerts.writeThreadPoolRejections.description": "書き込みスレッドプールの拒否数がしきい値を超過するときにアラートを発行します。",
"xpack.monitoring.apm.healthStatusLabel": "ヘルス: {status}",
"xpack.monitoring.apm.instance.heading": "APMサーバーインスタンス",
"xpack.monitoring.apm.instance.pageTitle": "APMサーバーインスタンス{instanceName}",
"xpack.monitoring.apm.instance.routeTitle": "{apm} - インスタンス",
"xpack.monitoring.apm.instance.status.lastEventDescription": "{timeOfLastEvent} 前",
@ -14480,7 +14479,6 @@
"xpack.monitoring.apm.instances.totalEventsRateTitle": "合計イベントレート",
"xpack.monitoring.apm.instances.versionFilter": "バージョン",
"xpack.monitoring.apm.instances.versionTitle": "バージョン",
"xpack.monitoring.apm.overview.heading": "APMサーバー概要",
"xpack.monitoring.apm.overview.pageTitle": "APMサーバー概要",
"xpack.monitoring.apm.overview.routeTitle": "APMサーバー",
"xpack.monitoring.apmNavigation.instancesLinkText": "インスタンス",

View file

@ -14492,7 +14492,6 @@
"xpack.monitoring.alerts.validation.threshold": "需要有效的数字。",
"xpack.monitoring.alerts.writeThreadPoolRejections.description": "当写入线程池中的拒绝数量超过阈值时告警。",
"xpack.monitoring.apm.healthStatusLabel": "运行状况:{status}",
"xpack.monitoring.apm.instance.heading": "APM 服务器实例",
"xpack.monitoring.apm.instance.pageTitle": "APM 服务器实例:{instanceName}",
"xpack.monitoring.apm.instance.routeTitle": "{apm} - 实例",
"xpack.monitoring.apm.instance.status.lastEventDescription": "{timeOfLastEvent}前",
@ -14521,7 +14520,6 @@
"xpack.monitoring.apm.instances.totalEventsRateTitle": "事件合计速率",
"xpack.monitoring.apm.instances.versionFilter": "版本",
"xpack.monitoring.apm.instances.versionTitle": "版本",
"xpack.monitoring.apm.overview.heading": "APM 服务器概览",
"xpack.monitoring.apm.overview.pageTitle": "APM 服务器概览",
"xpack.monitoring.apm.overview.routeTitle": "APM 服务器",
"xpack.monitoring.apmNavigation.instancesLinkText": "实例",

View file

@ -6,6 +6,9 @@
"apms": {
"total": 2
},
"config": {
"container": false
},
"timeOfLastEvent": "2018-08-31T13:59:21.201Z"
},
"metrics": {

View file

@ -899,6 +899,9 @@
"eventsEmitted": 6,
"eventsDropped": 0,
"bytesWritten": 10478,
"config": {
"container": false
},
"timeOfLastEvent": "2018-08-31T13:59:21.201Z"
}
}

View file

@ -98,6 +98,9 @@
"memTotal": null,
"apms": {
"total": null
},
"config": {
"container": false
}
},
"alerts": {
@ -214,6 +217,9 @@
"memTotal": null,
"apms": {
"total": null
},
"config": {
"container": false
}
},
"alerts": {
@ -326,6 +332,9 @@
"memTotal": null,
"apms": {
"total": null
},
"config": {
"container": false
}
},
"alerts": {

View file

@ -112,6 +112,9 @@
"memTotal": null,
"apms": {
"total": null
},
"config": {
"container": false
}
},
"isCcrEnabled": true,

View file

@ -43,6 +43,9 @@
"memTotal": 0,
"apms": {
"total": 0
},
"config": {
"container": false
}
},
"isPrimary": false

View file

@ -98,6 +98,9 @@
"memTotal": 0,
"apms": {
"total": 0
},
"config": {
"container": false
}
},
"alerts": {
@ -166,6 +169,9 @@
"memTotal": 0,
"apms": {
"total": 0
},
"config": {
"container": false
}
},
"alerts": {