Skip to content

Commit 3f98ac6

Browse files
authored
[8.15] [APM UI] Fix OpenTelemetry agent names (#193134) (#193511)
# Backport This will backport the following commits from `main` to `8.15`: - [[APM UI] Fix OpenTelemetry agent names (#193134)](#193134) <!--- Backport version: 8.9.8 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Sergi Romeu","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-09-20T07:07:14Z","message":"[APM UI] Fix OpenTelemetry agent names (#193134)\n\n## Summary\r\n\r\nFixes https://github.com/elastic/kibana/issues/180444\r\n\r\nThis PR fixes the agent names not being able to properly be retrieved by\r\nthe APM UI, changing the way we map OpenTelemetry agent names.\r\nAs the format changed from `(opentelemetry|otlp)/{agentName}` to\r\n`(opentelemetry|otlp)/{agentName}/{details}`, we now get the second part\r\nsplitting by `/`.\r\n\r\nAdded mappings for RUM, Android, and iOS OpenTelemetry client, also\r\nfixed `get_service_metadata_details` to get the correct OpenTelemetry\r\ndetails.\r\n\r\n|Before|After|\r\n|-|-|\r\n\r\n|![image](https://github.com/user-attachments/assets/28732018-511b-44e0-ac86-cdbe7ed0d1e0)|![image](https://github.com/user-attachments/assets/45a29cc6-f939-4c52-bcc7-54dc15b1a403)|\r\n\r\n## How to test\r\n1. Checkout to this branch\r\n2. Run `node scripts/synthtrace many_otel_services.ts --live --clean`\r\nwhich will fill some APM Otel services.\r\n3. Check that the icon is now rendering","sha":"735e216a952670eb57eaea1229be16e89f9bf1cd","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","v9.0.0","apm:opentelemetry","backport:prev-major","ci:project-deploy-observability","Team:obs-ux-infra_services","backport:version"],"number":193134,"url":"https://github.com/elastic/kibana/pull/193134","mergeCommit":{"message":"[APM UI] Fix OpenTelemetry agent names (#193134)\n\n## Summary\r\n\r\nFixes https://github.com/elastic/kibana/issues/180444\r\n\r\nThis PR fixes the agent names not being able to properly be retrieved by\r\nthe APM UI, changing the way we map OpenTelemetry agent names.\r\nAs the format changed from `(opentelemetry|otlp)/{agentName}` to\r\n`(opentelemetry|otlp)/{agentName}/{details}`, we now get the second part\r\nsplitting by `/`.\r\n\r\nAdded mappings for RUM, Android, and iOS OpenTelemetry client, also\r\nfixed `get_service_metadata_details` to get the correct OpenTelemetry\r\ndetails.\r\n\r\n|Before|After|\r\n|-|-|\r\n\r\n|![image](https://github.com/user-attachments/assets/28732018-511b-44e0-ac86-cdbe7ed0d1e0)|![image](https://github.com/user-attachments/assets/45a29cc6-f939-4c52-bcc7-54dc15b1a403)|\r\n\r\n## How to test\r\n1. Checkout to this branch\r\n2. Run `node scripts/synthtrace many_otel_services.ts --live --clean`\r\nwhich will fill some APM Otel services.\r\n3. Check that the icon is now rendering","sha":"735e216a952670eb57eaea1229be16e89f9bf1cd"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/193134","number":193134,"mergeCommit":{"message":"[APM UI] Fix OpenTelemetry agent names (#193134)\n\n## Summary\r\n\r\nFixes https://github.com/elastic/kibana/issues/180444\r\n\r\nThis PR fixes the agent names not being able to properly be retrieved by\r\nthe APM UI, changing the way we map OpenTelemetry agent names.\r\nAs the format changed from `(opentelemetry|otlp)/{agentName}` to\r\n`(opentelemetry|otlp)/{agentName}/{details}`, we now get the second part\r\nsplitting by `/`.\r\n\r\nAdded mappings for RUM, Android, and iOS OpenTelemetry client, also\r\nfixed `get_service_metadata_details` to get the correct OpenTelemetry\r\ndetails.\r\n\r\n|Before|After|\r\n|-|-|\r\n\r\n|![image](https://github.com/user-attachments/assets/28732018-511b-44e0-ac86-cdbe7ed0d1e0)|![image](https://github.com/user-attachments/assets/45a29cc6-f939-4c52-bcc7-54dc15b1a403)|\r\n\r\n## How to test\r\n1. Checkout to this branch\r\n2. Run `node scripts/synthtrace many_otel_services.ts --live --clean`\r\nwhich will fill some APM Otel services.\r\n3. Check that the icon is now rendering","sha":"735e216a952670eb57eaea1229be16e89f9bf1cd"}},{"url":"https://github.com/elastic/kibana/pull/193509","number":193509,"branch":"8.x","state":"OPEN"}]}] BACKPORT-->
1 parent 2f4316a commit 3f98ac6

File tree

15 files changed

+541
-30
lines changed

15 files changed

+541
-30
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
import { ApmFields, apm, Instance } from '@kbn/apm-synthtrace-client';
10+
import { flatten, random, times } from 'lodash';
11+
import { Scenario } from '../cli/scenario';
12+
import { getSynthtraceEnvironment } from '../lib/utils/get_synthtrace_environment';
13+
import { withClient } from '../lib/utils/with_client';
14+
import { getRandomNameForIndex } from './helpers/random_names';
15+
16+
const ENVIRONMENT = getSynthtraceEnvironment(__filename);
17+
18+
const scenario: Scenario<ApmFields> = async ({ logger, scenarioOpts = { services: 2000 } }) => {
19+
const numServices = scenarioOpts.services;
20+
const transactionName = 'GET /order/{id}';
21+
const languages = [
22+
'go',
23+
'dotnet',
24+
'java',
25+
'python',
26+
'nodejs',
27+
'php',
28+
'webjs',
29+
'swift',
30+
'android',
31+
];
32+
const agentVersions: Record<string, string[]> = {
33+
go: ['2.1.0', '2.0.0', '1.15.0', '1.14.0', '1.13.1'],
34+
dotnet: ['1.18.0', '1.17.0', '1.16.1', '1.16.0', '1.15.0'],
35+
java: ['1.34.1', '1.34.0', '1.33.0', '1.32.0', '1.32.0'],
36+
python: ['6.12.0', '6.11.0', '6.10.2', '6.10.1', '6.10.0'],
37+
nodejs: ['1.34.1', '1.34.0', '1.33.0', '1.32.0', '1.32.0'],
38+
php: ['1.34.1', '1.34.0', '1.33.0', '1.32.0', '1.32.0'],
39+
webjs: ['6.12.0', '6.11.0', '6.10.2', '6.10.1', '6.10.0'],
40+
swift: ['1.18.0', '1.17.0', '1.16.1', '1.16.0', '1.15.0'],
41+
android: ['6.12.0', '6.11.0', '6.10.2', '6.10.1', '6.10.0'],
42+
};
43+
44+
return {
45+
generate: ({ range, clients: { apmEsClient } }) => {
46+
const instances = flatten(
47+
times(numServices).map((index) => {
48+
const language = languages[index % languages.length];
49+
const agentLanguageVersions = agentVersions[language];
50+
const agentVersion = agentLanguageVersions[index % agentLanguageVersions.length];
51+
52+
const numOfInstances = (index % 3) + 1;
53+
return times(numOfInstances).map((instanceIndex) =>
54+
apm
55+
.service({
56+
name: `${getRandomNameForIndex(index)}-${language}-${index}`,
57+
environment: ENVIRONMENT,
58+
agentName:
59+
index % 2 ? `opentelemetry/${language}/elastic` : `otlp/${language}/elastic`,
60+
})
61+
.instance(`instance-${index}-${instanceIndex}`)
62+
.defaults({ 'agent.version': agentVersion, 'service.language.name': language })
63+
);
64+
})
65+
);
66+
67+
const instanceSpans = (instance: Instance) => {
68+
const hasHighDuration = Math.random() > 0.5;
69+
const throughput = random(1, 10);
70+
71+
return range.ratePerMinute(throughput).generator((timestamp) => {
72+
const parentDuration = hasHighDuration ? random(1000, 5000) : random(100, 1000);
73+
const generateError = random(1, 4) % 3 === 0;
74+
const span = instance
75+
.transaction({ transactionName })
76+
.timestamp(timestamp)
77+
.duration(parentDuration);
78+
79+
return !generateError
80+
? span.success()
81+
: span.failure().errors(
82+
instance
83+
.error({
84+
message: `No handler for ${transactionName}`,
85+
type: 'No handler',
86+
culprit: 'request',
87+
})
88+
.timestamp(timestamp + 50)
89+
);
90+
});
91+
};
92+
93+
return withClient(
94+
apmEsClient,
95+
logger.perf('generating_apm_events', () => instances.map(instanceSpans))
96+
);
97+
},
98+
};
99+
};
100+
101+
export default scenario;

packages/kbn-custom-icons/src/components/agent_icon/get_agent_icon.test.ts

+10
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ const examples = {
2424
'opentelemetry/python': 'python',
2525
'opentelemetry/ruby': 'ruby',
2626
'opentelemetry/rust': 'rust',
27+
'opentelemetry/cpp/elastic': 'cpp', // Tests for additional arguments on OpenTelemetry agents
28+
'opentelemetry/dotnet/elastic': 'dotnet',
29+
'opentelemetry/erlang/elastic': 'erlang',
30+
'opentelemetry/go/elastic': 'go',
31+
'opentelemetry/nodejs/elastic': 'nodejs',
32+
'opentelemetry/php/elastic': 'php',
33+
'opentelemetry/python/elastic': 'python',
34+
'opentelemetry/ruby/elastic': 'ruby',
35+
'opentelemetry/rust/elastic': 'rust',
36+
opentelemetry: 'opentelemetry',
2737
otlp: 'opentelemetry',
2838
php: 'php',
2939
python: 'python',

packages/kbn-custom-icons/src/components/agent_icon/get_agent_icon.ts

+13-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
isIosAgentName,
1111
isJavaAgentName,
1212
isRumAgentName,
13+
hasOpenTelemetryPrefix,
1314
OpenTelemetryAgentName,
1415
OPEN_TELEMETRY_AGENT_NAMES,
1516
} from '@kbn/elastic-agent-utils';
@@ -64,6 +65,15 @@ const darkAgentIcons: { [key: string]: string } = {
6465
rust: darkRustIcon,
6566
};
6667

68+
const sanitizeAgentName = (agentName: string) => {
69+
if (hasOpenTelemetryPrefix(agentName)) {
70+
// for OpenTelemetry only split the agent name by `/` and take the second part, format is `(opentelemetry|otlp)/{agentName}/{details}`
71+
return agentName.split('/')[1];
72+
}
73+
74+
return agentName;
75+
};
76+
6777
// This only needs to be exported for testing purposes, since we stub the SVG
6878
// import values in test.
6979
export function getAgentIconKey(agentName: string) {
@@ -88,11 +98,10 @@ export function getAgentIconKey(agentName: string) {
8898
return 'android';
8999
}
90100

91-
// Remove "opentelemetry/" prefix
92-
const agentNameWithoutPrefix = lowercasedAgentName.replace(/^opentelemetry\//, '');
101+
const cleanAgentName = sanitizeAgentName(lowercasedAgentName);
93102

94-
if (Object.keys(agentIcons).includes(agentNameWithoutPrefix)) {
95-
return agentNameWithoutPrefix;
103+
if (Object.keys(agentIcons).includes(cleanAgentName)) {
104+
return cleanAgentName;
96105
}
97106

98107
// OpenTelemetry-only agents

packages/kbn-elastic-agent-utils/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
export {
99
isOpenTelemetryAgentName,
10+
hasOpenTelemetryPrefix,
1011
isJavaAgentName,
1112
isRumAgentName,
1213
isMobileAgentName,

packages/kbn-elastic-agent-utils/src/agent_guards.test.ts

+36-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import {
10+
hasOpenTelemetryPrefix,
1011
isAndroidAgentName,
1112
isAWSLambdaAgentName,
1213
isAzureFunctionsAgentName,
@@ -21,23 +22,42 @@ import {
2122
} from './agent_guards';
2223

2324
describe('Agents guards', () => {
25+
it('hasOpenTelemetryPrefix should guard if the passed agent has an OpenTelemetry prefix.', () => {
26+
expect(hasOpenTelemetryPrefix('otlp')).toBe(false);
27+
expect(hasOpenTelemetryPrefix('otlp/nodejs')).toBe(true);
28+
expect(hasOpenTelemetryPrefix('otlp/nodejs/elastic')).toBe(true);
29+
expect(hasOpenTelemetryPrefix('opentelemetry')).toBe(false);
30+
expect(hasOpenTelemetryPrefix('opentelemetry/nodejs')).toBe(true);
31+
expect(hasOpenTelemetryPrefix('opentelemetry/nodejs/elastic')).toBe(true);
32+
expect(hasOpenTelemetryPrefix('not-an-agent')).toBe(false);
33+
});
34+
2435
it('isOpenTelemetryAgentName should guard if the passed agent is an OpenTelemetry one.', () => {
2536
expect(isOpenTelemetryAgentName('otlp')).toBe(true);
26-
expect(isOpenTelemetryAgentName('opentelemetry/java')).toBe(true);
27-
expect(isOpenTelemetryAgentName('opentelemetry/java/opentelemetry-java-instrumentation')).toBe(
28-
true
29-
);
37+
expect(isOpenTelemetryAgentName('otlp/nodejs')).toBe(true);
38+
expect(isOpenTelemetryAgentName('otlp/nodejs/elastic')).toBe(true);
39+
expect(isOpenTelemetryAgentName('opentelemetry')).toBe(true);
40+
expect(isOpenTelemetryAgentName('opentelemetry/nodejs')).toBe(true);
41+
expect(isOpenTelemetryAgentName('opentelemetry/nodejs/elastic')).toBe(true);
3042
expect(isOpenTelemetryAgentName('not-an-agent')).toBe(false);
3143
});
3244

3345
it('isJavaAgentName should guard if the passed agent is an Java one.', () => {
3446
expect(isJavaAgentName('java')).toBe(true);
47+
expect(isJavaAgentName('otlp/java')).toBe(true);
48+
expect(isJavaAgentName('otlp/java/opentelemetry-java-instrumentation')).toBe(true);
3549
expect(isJavaAgentName('opentelemetry/java')).toBe(true);
3650
expect(isJavaAgentName('opentelemetry/java/opentelemetry-java-instrumentation')).toBe(true);
3751
expect(isJavaAgentName('not-an-agent')).toBe(false);
3852
});
3953

4054
it('isRumAgentName should guard if the passed agent is an Rum one.', () => {
55+
expect(isRumAgentName('otlp/webjs')).toBe(true);
56+
expect(isRumAgentName('otlp/webjs/elastic')).toBe(true);
57+
expect(isRumAgentName('otlp/fail')).toBe(false);
58+
expect(isRumAgentName('opentelemetry/webjs')).toBe(true);
59+
expect(isRumAgentName('opentelemetry/webjs/elastic')).toBe(true);
60+
expect(isRumAgentName('opentelemetry/fail')).toBe(false);
4161
expect(isRumAgentName('rum-js')).toBe(true);
4262
expect(isRumAgentName('not-an-agent')).toBe(false);
4363
});
@@ -56,11 +76,23 @@ describe('Agents guards', () => {
5676
});
5777

5878
it('isIosAgentName should guard if the passed agent is an Ios one.', () => {
79+
expect(isIosAgentName('otlp/swift/elastic')).toBe(true);
80+
expect(isIosAgentName('otlp/swift')).toBe(true);
81+
expect(isIosAgentName('otlp/fail')).toBe(false);
82+
expect(isIosAgentName('opentelemetry/swift/elastic')).toBe(true);
83+
expect(isIosAgentName('opentelemetry/swift')).toBe(true);
84+
expect(isIosAgentName('opentelemetry/fail')).toBe(false);
5985
expect(isIosAgentName('ios/swift')).toBe(true);
6086
expect(isIosAgentName('not-an-agent')).toBe(false);
6187
});
6288

6389
it('isAndroidAgentName should guard if the passed agent is an Android one.', () => {
90+
expect(isAndroidAgentName('otlp/android/elastic')).toBe(true);
91+
expect(isAndroidAgentName('otlp/android')).toBe(true);
92+
expect(isAndroidAgentName('otlp/fail')).toBe(false);
93+
expect(isAndroidAgentName('opentelemetry/android/elastic')).toBe(true);
94+
expect(isAndroidAgentName('opentelemetry/android')).toBe(true);
95+
expect(isAndroidAgentName('opentelemetry/fail')).toBe(false);
6496
expect(isAndroidAgentName('android/java')).toBe(true);
6597
expect(isAndroidAgentName('not-an-agent')).toBe(false);
6698
});

packages/kbn-elastic-agent-utils/src/agent_guards.ts

+37-7
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,52 @@
66
* Side Public License, v 1.
77
*/
88

9-
import { JAVA_AGENT_NAMES, OPEN_TELEMETRY_AGENT_NAMES, RUM_AGENT_NAMES } from './agent_names';
9+
import {
10+
ANDROID_AGENT_NAMES,
11+
IOS_AGENT_NAMES,
12+
JAVA_AGENT_NAMES,
13+
OPEN_TELEMETRY_AGENT_NAMES,
14+
RUM_AGENT_NAMES,
15+
} from './agent_names';
1016

1117
import type {
18+
AndroidAgentName,
19+
IOSAgentName,
1220
JavaAgentName,
1321
OpenTelemetryAgentName,
1422
RumAgentName,
1523
ServerlessType,
1624
} from './agent_names';
1725

26+
export function hasOpenTelemetryPrefix(agentName?: string, language: string = '') {
27+
if (!agentName) {
28+
return false;
29+
}
30+
31+
return (
32+
agentName.startsWith(`opentelemetry/${language}`) || agentName.startsWith(`otlp/${language}`)
33+
);
34+
}
35+
1836
export function isOpenTelemetryAgentName(agentName: string): agentName is OpenTelemetryAgentName {
1937
return (
20-
agentName?.startsWith('opentelemetry/') ||
38+
hasOpenTelemetryPrefix(agentName) ||
2139
OPEN_TELEMETRY_AGENT_NAMES.includes(agentName as OpenTelemetryAgentName)
2240
);
2341
}
2442

2543
export function isJavaAgentName(agentName?: string): agentName is JavaAgentName {
2644
return (
27-
agentName?.startsWith('opentelemetry/java') ||
45+
hasOpenTelemetryPrefix(agentName, 'java') ||
2846
JAVA_AGENT_NAMES.includes(agentName! as JavaAgentName)
2947
);
3048
}
3149

3250
export function isRumAgentName(agentName?: string): agentName is RumAgentName {
33-
return RUM_AGENT_NAMES.includes(agentName! as RumAgentName);
51+
return (
52+
hasOpenTelemetryPrefix(agentName, 'webjs') ||
53+
RUM_AGENT_NAMES.includes(agentName! as RumAgentName)
54+
);
3455
}
3556

3657
export function isMobileAgentName(agentName?: string) {
@@ -42,12 +63,21 @@ export function isRumOrMobileAgentName(agentName?: string) {
4263
}
4364

4465
export function isIosAgentName(agentName?: string) {
45-
return agentName?.toLowerCase() === 'ios/swift';
66+
const lowercasedAgentName = agentName && agentName.toLowerCase();
67+
68+
return (
69+
hasOpenTelemetryPrefix(lowercasedAgentName, 'swift') ||
70+
IOS_AGENT_NAMES.includes(lowercasedAgentName! as IOSAgentName)
71+
);
4672
}
4773

4874
export function isAndroidAgentName(agentName?: string) {
49-
const lowercased = agentName && agentName.toLowerCase();
50-
return lowercased === 'android/java';
75+
const lowercasedAgentName = agentName && agentName.toLowerCase();
76+
77+
return (
78+
hasOpenTelemetryPrefix(lowercasedAgentName, 'android') ||
79+
ANDROID_AGENT_NAMES.includes(lowercasedAgentName! as AndroidAgentName)
80+
);
5181
}
5282

5383
export function isJRubyAgentName(agentName?: string, runtimeName?: string) {

0 commit comments

Comments
 (0)