Skip to content

Commit c9596b5

Browse files
committed
[APM UI] Fix OpenTelemetry agent names (elastic#193134)
## Summary Fixes elastic#180444 This PR fixes the agent names not being able to properly be retrieved by the APM UI, changing the way we map OpenTelemetry agent names. As the format changed from `(opentelemetry|otlp)/{agentName}` to `(opentelemetry|otlp)/{agentName}/{details}`, we now get the second part splitting by `/`. Added mappings for RUM, Android, and iOS OpenTelemetry client, also fixed `get_service_metadata_details` to get the correct OpenTelemetry details. |Before|After| |-|-| |![image](https://github.com/user-attachments/assets/28732018-511b-44e0-ac86-cdbe7ed0d1e0)|![image](https://github.com/user-attachments/assets/45a29cc6-f939-4c52-bcc7-54dc15b1a403)| ## How to test 1. Checkout to this branch 2. Run `node scripts/synthtrace many_otel_services.ts --live --clean` which will fill some APM Otel services. 3. Check that the icon is now rendering (cherry picked from commit 735e216) # Conflicts: # src/plugins/telemetry/schema/oss_plugins.json
1 parent 2f4316a commit c9596b5

File tree

16 files changed

+560
-30
lines changed

16 files changed

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