[8.8] [APM] Check if metric fields exist (#145348) (#156006)

# Backport

This will backport the following commits from `main` to `8.8`:
- [[APM] Check if metric fields exist
(#145348)](https://github.com/elastic/kibana/pull/145348)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Katerina
Patticha","email":"aikaterini.patticha@elastic.co"},"sourceCommit":{"committedDate":"2023-04-27T12:55:14Z","message":"[APM]
Check if metric fields exist (#145348)\n\nFixes the error
\r\n```\r\n\"script_stack\": [\r\n
\"org.elasticsearch.server@8.5.0-SNAPSHOT/org.elasticsearch.index.fielddata.ScriptDocValues.throwIfEmpty(ScriptDocValues.java:92)\",\r\n
\"org.elasticsearch.server@8.5.0-SNAPSHOT/org.elasticsearch.index.fielddata.ScriptDocValues$Longs.get(ScriptDocValues.java:110)\",\r\n
\"org.elasticsearch.server@8.5.0-SNAPSHOT/org.elasticsearch.index.fielddata.ScriptDocValues$Longs.getValue(ScriptDocValues.java:105)\",\r\n
\"\"\"total = useCgroupLimit ? doc[limitKey].value :
doc['system.memory.total'].value;\r\n\r\n double \"\"\",\r\n \" ^----
HERE\"\r\n\r\n\r\n```\r\n\r\nconnected
https://github.com/elastic/sdh-apm/issues/765
(internal)\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by:
Søren Louv-Jansen
<sorenlouv@gmail.com>","sha":"98165d26846367b98a259f76b664545a43d2434b","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:fix","Team:APM","backport:prev-minor","v8.8.0","v8.7.1","v8.9.0"],"number":145348,"url":"https://github.com/elastic/kibana/pull/145348","mergeCommit":{"message":"[APM]
Check if metric fields exist (#145348)\n\nFixes the error
\r\n```\r\n\"script_stack\": [\r\n
\"org.elasticsearch.server@8.5.0-SNAPSHOT/org.elasticsearch.index.fielddata.ScriptDocValues.throwIfEmpty(ScriptDocValues.java:92)\",\r\n
\"org.elasticsearch.server@8.5.0-SNAPSHOT/org.elasticsearch.index.fielddata.ScriptDocValues$Longs.get(ScriptDocValues.java:110)\",\r\n
\"org.elasticsearch.server@8.5.0-SNAPSHOT/org.elasticsearch.index.fielddata.ScriptDocValues$Longs.getValue(ScriptDocValues.java:105)\",\r\n
\"\"\"total = useCgroupLimit ? doc[limitKey].value :
doc['system.memory.total'].value;\r\n\r\n double \"\"\",\r\n \" ^----
HERE\"\r\n\r\n\r\n```\r\n\r\nconnected
https://github.com/elastic/sdh-apm/issues/765
(internal)\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by:
Søren Louv-Jansen
<sorenlouv@gmail.com>","sha":"98165d26846367b98a259f76b664545a43d2434b"}},"sourceBranch":"main","suggestedTargetBranches":["8.8","8.7"],"targetPullRequestStates":[{"branch":"8.8","label":"v8.8.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.7","label":"v8.7.1","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/145348","number":145348,"mergeCommit":{"message":"[APM]
Check if metric fields exist (#145348)\n\nFixes the error
\r\n```\r\n\"script_stack\": [\r\n
\"org.elasticsearch.server@8.5.0-SNAPSHOT/org.elasticsearch.index.fielddata.ScriptDocValues.throwIfEmpty(ScriptDocValues.java:92)\",\r\n
\"org.elasticsearch.server@8.5.0-SNAPSHOT/org.elasticsearch.index.fielddata.ScriptDocValues$Longs.get(ScriptDocValues.java:110)\",\r\n
\"org.elasticsearch.server@8.5.0-SNAPSHOT/org.elasticsearch.index.fielddata.ScriptDocValues$Longs.getValue(ScriptDocValues.java:105)\",\r\n
\"\"\"total = useCgroupLimit ? doc[limitKey].value :
doc['system.memory.total'].value;\r\n\r\n double \"\"\",\r\n \" ^----
HERE\"\r\n\r\n\r\n```\r\n\r\nconnected
https://github.com/elastic/sdh-apm/issues/765
(internal)\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by:
Søren Louv-Jansen
<sorenlouv@gmail.com>","sha":"98165d26846367b98a259f76b664545a43d2434b"}}]}]
BACKPORT-->

Co-authored-by: Katerina Patticha <aikaterini.patticha@elastic.co>
This commit is contained in:
Kibana Machine 2023-05-03 13:17:11 -04:00 committed by GitHub
parent e2047079d7
commit b64f2f2d0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 263 additions and 35 deletions

View file

@ -12,6 +12,8 @@ export type ApmApplicationMetricFields = Partial<{
'system.process.memory.size': number;
'system.memory.actual.free': number;
'system.memory.total': number;
'system.process.cgroup.memory.mem.limit.bytes': number;
'system.process.cgroup.memory.mem.usage.bytes': number;
'system.cpu.total.norm.pct': number;
'system.process.memory.rss.bytes': number;
'system.process.cpu.total.norm.pct': number;

View file

@ -332,8 +332,27 @@ Object {
},
},
Object {
"exists": Object {
"field": "system.process.cgroup.memory.mem.usage.bytes",
"bool": Object {
"filter": Array [
Object {
"exists": Object {
"field": "system.process.cgroup.memory.mem.usage.bytes",
},
},
],
"minimum_should_match": 1,
"should": Array [
Object {
"exists": Object {
"field": "system.process.cgroup.memory.mem.limit.bytes",
},
},
Object {
"exists": Object {
"field": "system.memory.total",
},
},
],
},
},
],
@ -869,8 +888,27 @@ Object {
},
},
Object {
"exists": Object {
"field": "system.process.cgroup.memory.mem.usage.bytes",
"bool": Object {
"filter": Array [
Object {
"exists": Object {
"field": "system.process.cgroup.memory.mem.usage.bytes",
},
},
],
"minimum_should_match": 1,
"should": Array [
Object {
"exists": Object {
"field": "system.process.cgroup.memory.mem.limit.bytes",
},
},
Object {
"exists": Object {
"field": "system.memory.total",
},
},
],
},
},
],
@ -1385,8 +1423,27 @@ Object {
},
},
Object {
"exists": Object {
"field": "system.process.cgroup.memory.mem.usage.bytes",
"bool": Object {
"filter": Array [
Object {
"exists": Object {
"field": "system.process.cgroup.memory.mem.usage.bytes",
},
},
],
"minimum_should_match": 1,
"should": Array [
Object {
"exists": Object {
"field": "system.process.cgroup.memory.mem.limit.bytes",
},
},
Object {
"exists": Object {
"field": "system.memory.total",
},
},
],
},
},
],

View file

@ -47,6 +47,15 @@ const chartBase: ChartBase = {
series,
};
export const systemMemoryFilter = {
bool: {
filter: [
{ exists: { field: METRIC_SYSTEM_FREE_MEMORY } },
{ exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } },
],
},
};
export const percentSystemMemoryUsedScript = {
lang: 'painless',
source: `
@ -60,6 +69,17 @@ export const percentSystemMemoryUsedScript = {
`,
} as const;
export const cgroupMemoryFilter = {
bool: {
filter: [{ exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES } }],
should: [
{ exists: { field: METRIC_CGROUP_MEMORY_LIMIT_BYTES } },
{ exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } },
],
minimum_should_match: 1,
},
};
export const percentCgroupMemoryUsedScript = {
lang: 'painless',
source: `
@ -147,7 +167,7 @@ export async function getMemoryChartData({
memoryUsedMax: { max: { script: percentCgroupMemoryUsedScript } },
},
additionalFilters: [
{ exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES } },
cgroupMemoryFilter,
...termQuery(FAAS_ID, serverlessId),
],
operationName: 'get_cgroup_memory_metrics_charts',
@ -169,8 +189,7 @@ export async function getMemoryChartData({
memoryUsedMax: { max: { script: percentSystemMemoryUsedScript } },
},
additionalFilters: [
{ exists: { field: METRIC_SYSTEM_FREE_MEMORY } },
{ exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } },
systemMemoryFilter,
...termQuery(FAAS_ID, serverlessId),
],
operationName: 'get_system_memory_metrics_charts',

View file

@ -9,10 +9,7 @@ import type { ESFilter } from '@kbn/es-types';
import { rangeQuery } from '@kbn/observability-plugin/server';
import { ProcessorEvent } from '@kbn/observability-plugin/common';
import {
METRIC_CGROUP_MEMORY_USAGE_BYTES,
METRIC_SYSTEM_CPU_PERCENT,
METRIC_SYSTEM_FREE_MEMORY,
METRIC_SYSTEM_TOTAL_MEMORY,
SERVICE_NAME,
TRANSACTION_TYPE,
} from '../../../common/es_fields/apm';
@ -31,6 +28,8 @@ import { withApmSpan } from '../../utils/with_apm_span';
import {
percentCgroupMemoryUsedScript,
percentSystemMemoryUsedScript,
systemMemoryFilter,
cgroupMemoryFilter,
} from '../metrics/by_agent/shared/memory';
import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client';
import { ApmDocumentType } from '../../../common/document_type';
@ -358,19 +357,14 @@ function getMemoryStats({
};
let memoryUsage = await getMemoryUsage({
additionalFilters: [
{ exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES } },
],
script: percentCgroupMemoryUsedScript,
additionalFilters: [cgroupMemoryFilter],
});
if (!memoryUsage) {
memoryUsage = await getMemoryUsage({
additionalFilters: [
{ exists: { field: METRIC_SYSTEM_FREE_MEMORY } },
{ exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } },
],
script: percentSystemMemoryUsedScript,
additionalFilters: [systemMemoryFilter],
});
}

View file

@ -9,10 +9,7 @@ import type { AggregationOptionsByType } from '@kbn/es-types';
import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server';
import { ProcessorEvent } from '@kbn/observability-plugin/common';
import {
METRIC_CGROUP_MEMORY_USAGE_BYTES,
METRIC_PROCESS_CPU_PERCENT,
METRIC_SYSTEM_FREE_MEMORY,
METRIC_SYSTEM_TOTAL_MEMORY,
SERVICE_NAME,
SERVICE_NODE_NAME,
} from '../../../../common/es_fields/apm';
@ -24,6 +21,8 @@ import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm
import {
percentCgroupMemoryUsedScript,
percentSystemMemoryUsedScript,
systemMemoryFilter,
cgroupMemoryFilter,
} from '../../metrics/by_agent/shared/memory';
import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms';
@ -82,19 +81,6 @@ export async function getServiceInstancesSystemMetricStatistics<
numBuckets,
});
const systemMemoryFilter = {
bool: {
filter: [
{ exists: { field: METRIC_SYSTEM_FREE_MEMORY } },
{ exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } },
],
},
};
const cgroupMemoryFilter = {
exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES },
};
const cpuUsageFilter = { exists: { field: METRIC_PROCESS_CPU_PERCENT } };
function withTimeseries<TParams extends AggregationOptionsByType['avg']>(

View file

@ -0,0 +1,77 @@
/*
* 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 { apm, timerange } from '@kbn/apm-synthtrace-client';
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
const gigabytesToBytes = (value: number) => value * Math.pow(1024, 3);
export const config = {
memoryTotal: gigabytesToBytes(64),
memoryFree: gigabytesToBytes(5.5),
cGroupMemoryLimit: gigabytesToBytes(8),
cGroupMemoryUsage: gigabytesToBytes(1.5),
};
export const expectedValues = {
expectedMemoryUsedRate: (config.memoryTotal - config.memoryFree) / config.memoryTotal,
expectedMemoryUsed: config.memoryTotal - config.memoryFree,
};
export async function generateData({
synthtraceEsClient,
start,
end,
}: {
synthtraceEsClient: ApmSynthtraceEsClient;
start: number;
end: number;
}) {
const { memoryTotal, memoryFree, cGroupMemoryLimit, cGroupMemoryUsage } = config;
const systemMetricOnlyInstance = apm
.service({ name: 'system-metric-only-service', environment: 'production', agentName: 'go' })
.instance('system-metric-only-production');
const cGroupMemoryOnlyInstance = apm
.service({ name: 'cgroup-memory-only-service', environment: 'production', agentName: 'go' })
.instance('cgroup-memory-only-production');
const cGroupMemoryWithLimitInstance = apm
.service({
name: 'cgroup-memory-with-limit-production',
environment: 'production',
agentName: 'go',
})
.instance('cgroup-memory-with-limit-production');
const transactionsEvents = timerange(start, end)
.ratePerMinute(1)
.generator((timestamp) => [
systemMetricOnlyInstance
.appMetrics({
'system.memory.actual.free': memoryFree,
'system.memory.total': memoryTotal,
})
.timestamp(timestamp),
cGroupMemoryOnlyInstance
.appMetrics({
'system.process.cgroup.memory.mem.usage.bytes': cGroupMemoryUsage,
})
.timestamp(timestamp),
cGroupMemoryWithLimitInstance
.appMetrics({
'system.process.cgroup.memory.mem.usage.bytes': cGroupMemoryUsage,
'system.process.cgroup.memory.mem.limit.bytes': cGroupMemoryLimit,
'system.memory.total': memoryTotal,
'system.memory.actual.free': memoryFree,
})
.timestamp(timestamp),
]);
await synthtraceEsClient.index(transactionsEvents);
}

View file

@ -0,0 +1,83 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../common/ftr_provider_context';
import { config, generateData } from './generate_data';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
const synthtraceEsClient = getService('synthtraceEsClient');
const start = new Date('2023-01-01T00:00:00.000Z').getTime();
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;
async function callMetricChartsAPI(serviceName: string) {
return await apmApiClient.readUser({
endpoint: `GET /internal/apm/services/{serviceName}/metrics/charts`,
params: {
path: { serviceName },
query: {
environment: 'production',
start: new Date(start).toISOString(),
end: new Date(end).toISOString(),
kuery: '',
agentName: 'go',
},
},
});
}
registry.when('Memory', { config: 'trial', archives: [] }, () => {
before(async () => {
await generateData({ start, end, synthtraceEsClient });
});
after(() => synthtraceEsClient.clean());
it('returns system memory stats', async () => {
const expectedFreeMemory = 1 - config.memoryFree / config.memoryTotal;
const { status, body } = await callMetricChartsAPI('system-metric-only-service');
const memoryChart = body.charts.find(({ key }) => key === 'memory_usage_chart');
expect(status).to.be(200);
[
{ title: 'Max', expectedValue: expectedFreeMemory },
{ title: 'Average', expectedValue: expectedFreeMemory },
].map(({ title, expectedValue }) => {
const series = memoryChart?.series.find((item) => item.title === title);
expect(series?.overallValue).to.eql(expectedValue);
});
});
it('returns cgroup memory with system.process.cgroup.memory.mem.limit.bytes', async () => {
const expectedFreeMemory = config.cGroupMemoryUsage / config.cGroupMemoryLimit;
const { status, body } = await callMetricChartsAPI('cgroup-memory-with-limit-production');
const memoryChart = body.charts.find(({ key }) => key === 'memory_usage_chart');
expect(status).to.be(200);
[
{ title: 'Max', expectedValue: expectedFreeMemory },
{ title: 'Average', expectedValue: expectedFreeMemory },
].map(({ title, expectedValue }) => {
const series = memoryChart?.series.find((item) => item.title === title);
expect(series?.overallValue).to.eql(expectedValue);
});
});
it('handles cgroup memory stats when system.process.cgroup.memory.mem.limit.bytes and system.memory.total are not present', async () => {
const { status, body } = await callMetricChartsAPI('cgroup-memory-only-production');
expect(status).to.be(200);
const memoryChart = body.charts.find(({ key }) => key === 'memory_usage_chart');
expect(memoryChart?.series).to.eql([]);
});
});
}

View file

@ -418,6 +418,16 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(javaStats).to.be(undefined);
});
it('does not return metrics', () => {
const goAStats = body.currentPeriod.find(
(stat) => stat.serviceNodeName === 'go-instance-a'
);
expect(goAStats).to.not.be(undefined);
expect(goAStats?.memoryUsage).to.be(undefined);
expect(goAStats?.cpuUsage).to.be(undefined);
});
it('does not return data for missing service node name', () => {
const missingNameStats = body.currentPeriod.find(
(stat) => stat.serviceNodeName === SERVICE_NODE_NAME_MISSING