Skip to content

Commit

Permalink
[8.x] [APM UI] Fix OpenTelemetry agent names (#193134) (#193509)
Browse files Browse the repository at this point in the history
# Backport

This will backport the following commits from `main` to `8.x`:
- [[APM UI] Fix OpenTelemetry agent names
(#193134)](#193134)

<!--- Backport version: 9.4.3 -->

### 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"],"title":"[APM
UI] Fix OpenTelemetry agent
names","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","branchLabelMappingKey":"^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"}}]}]
BACKPORT-->

---------

Co-authored-by: Sergi Romeu <[email protected]>
  • Loading branch information
kibanamachine and rmyz authored Sep 20, 2024
1 parent 7cba7d9 commit b75df09
Show file tree
Hide file tree
Showing 15 changed files with 542 additions and 30 deletions.
102 changes: 102 additions & 0 deletions packages/kbn-apm-synthtrace/src/scenarios/many_otel_services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { ApmFields, apm, Instance } from '@kbn/apm-synthtrace-client';
import { flatten, random, times } from 'lodash';
import { Scenario } from '../cli/scenario';
import { getSynthtraceEnvironment } from '../lib/utils/get_synthtrace_environment';
import { withClient } from '../lib/utils/with_client';
import { getRandomNameForIndex } from './helpers/random_names';

const ENVIRONMENT = getSynthtraceEnvironment(__filename);

const scenario: Scenario<ApmFields> = async ({ logger, scenarioOpts = { services: 2000 } }) => {
const numServices = scenarioOpts.services;
const transactionName = 'GET /order/{id}';
const languages = [
'go',
'dotnet',
'java',
'python',
'nodejs',
'php',
'webjs',
'swift',
'android',
];
const agentVersions: Record<string, string[]> = {
go: ['2.1.0', '2.0.0', '1.15.0', '1.14.0', '1.13.1'],
dotnet: ['1.18.0', '1.17.0', '1.16.1', '1.16.0', '1.15.0'],
java: ['1.34.1', '1.34.0', '1.33.0', '1.32.0', '1.32.0'],
python: ['6.12.0', '6.11.0', '6.10.2', '6.10.1', '6.10.0'],
nodejs: ['1.34.1', '1.34.0', '1.33.0', '1.32.0', '1.32.0'],
php: ['1.34.1', '1.34.0', '1.33.0', '1.32.0', '1.32.0'],
webjs: ['6.12.0', '6.11.0', '6.10.2', '6.10.1', '6.10.0'],
swift: ['1.18.0', '1.17.0', '1.16.1', '1.16.0', '1.15.0'],
android: ['6.12.0', '6.11.0', '6.10.2', '6.10.1', '6.10.0'],
};

return {
generate: ({ range, clients: { apmEsClient } }) => {
const instances = flatten(
times(numServices).map((index) => {
const language = languages[index % languages.length];
const agentLanguageVersions = agentVersions[language];
const agentVersion = agentLanguageVersions[index % agentLanguageVersions.length];

const numOfInstances = (index % 3) + 1;
return times(numOfInstances).map((instanceIndex) =>
apm
.service({
name: `${getRandomNameForIndex(index)}-${language}-${index}`,
environment: ENVIRONMENT,
agentName:
index % 2 ? `opentelemetry/${language}/elastic` : `otlp/${language}/elastic`,
})
.instance(`instance-${index}-${instanceIndex}`)
.defaults({ 'agent.version': agentVersion, 'service.language.name': language })
);
})
);

const instanceSpans = (instance: Instance) => {
const hasHighDuration = Math.random() > 0.5;
const throughput = random(1, 10);

return range.ratePerMinute(throughput).generator((timestamp) => {
const parentDuration = hasHighDuration ? random(1000, 5000) : random(100, 1000);
const generateError = random(1, 4) % 3 === 0;
const span = instance
.transaction({ transactionName })
.timestamp(timestamp)
.duration(parentDuration);

return !generateError
? span.success()
: span.failure().errors(
instance
.error({
message: `No handler for ${transactionName}`,
type: 'No handler',
culprit: 'request',
})
.timestamp(timestamp + 50)
);
});
};

return withClient(
apmEsClient,
logger.perf('generating_apm_events', () => instances.map(instanceSpans))
);
},
};
};

export default scenario;
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ const examples = {
'opentelemetry/python': 'python',
'opentelemetry/ruby': 'ruby',
'opentelemetry/rust': 'rust',
'opentelemetry/cpp/elastic': 'cpp', // Tests for additional arguments on OpenTelemetry agents
'opentelemetry/dotnet/elastic': 'dotnet',
'opentelemetry/erlang/elastic': 'erlang',
'opentelemetry/go/elastic': 'go',
'opentelemetry/nodejs/elastic': 'nodejs',
'opentelemetry/php/elastic': 'php',
'opentelemetry/python/elastic': 'python',
'opentelemetry/ruby/elastic': 'ruby',
'opentelemetry/rust/elastic': 'rust',
opentelemetry: 'opentelemetry',
otlp: 'opentelemetry',
php: 'php',
python: 'python',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
isIosAgentName,
isJavaAgentName,
isRumAgentName,
hasOpenTelemetryPrefix,
OpenTelemetryAgentName,
OPEN_TELEMETRY_AGENT_NAMES,
} from '@kbn/elastic-agent-utils';
Expand Down Expand Up @@ -66,6 +67,15 @@ const darkAgentIcons: { [key: string]: string } = {
rust: darkRustIcon,
};

const sanitizeAgentName = (agentName: string) => {
if (hasOpenTelemetryPrefix(agentName)) {
// for OpenTelemetry only split the agent name by `/` and take the second part, format is `(opentelemetry|otlp)/{agentName}/{details}`
return agentName.split('/')[1];
}

return agentName;
};

// This only needs to be exported for testing purposes, since we stub the SVG
// import values in test.
export function getAgentIconKey(agentName: string) {
Expand All @@ -90,11 +100,10 @@ export function getAgentIconKey(agentName: string) {
return 'android';
}

// Remove "opentelemetry/" prefix
const agentNameWithoutPrefix = lowercasedAgentName.replace(/^opentelemetry\//, '');
const cleanAgentName = sanitizeAgentName(lowercasedAgentName);

if (Object.keys(agentIcons).includes(agentNameWithoutPrefix)) {
return agentNameWithoutPrefix;
if (Object.keys(agentIcons).includes(cleanAgentName)) {
return cleanAgentName;
}

// OpenTelemetry-only agents
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-elastic-agent-utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

export {
isOpenTelemetryAgentName,
hasOpenTelemetryPrefix,
isJavaAgentName,
isRumAgentName,
isMobileAgentName,
Expand Down
40 changes: 36 additions & 4 deletions packages/kbn-elastic-agent-utils/src/agent_guards.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import {
hasOpenTelemetryPrefix,
isAndroidAgentName,
isAWSLambdaAgentName,
isAzureFunctionsAgentName,
Expand All @@ -22,23 +23,42 @@ import {
} from './agent_guards';

describe('Agents guards', () => {
it('hasOpenTelemetryPrefix should guard if the passed agent has an OpenTelemetry prefix.', () => {
expect(hasOpenTelemetryPrefix('otlp')).toBe(false);
expect(hasOpenTelemetryPrefix('otlp/nodejs')).toBe(true);
expect(hasOpenTelemetryPrefix('otlp/nodejs/elastic')).toBe(true);
expect(hasOpenTelemetryPrefix('opentelemetry')).toBe(false);
expect(hasOpenTelemetryPrefix('opentelemetry/nodejs')).toBe(true);
expect(hasOpenTelemetryPrefix('opentelemetry/nodejs/elastic')).toBe(true);
expect(hasOpenTelemetryPrefix('not-an-agent')).toBe(false);
});

it('isOpenTelemetryAgentName should guard if the passed agent is an OpenTelemetry one.', () => {
expect(isOpenTelemetryAgentName('otlp')).toBe(true);
expect(isOpenTelemetryAgentName('opentelemetry/java')).toBe(true);
expect(isOpenTelemetryAgentName('opentelemetry/java/opentelemetry-java-instrumentation')).toBe(
true
);
expect(isOpenTelemetryAgentName('otlp/nodejs')).toBe(true);
expect(isOpenTelemetryAgentName('otlp/nodejs/elastic')).toBe(true);
expect(isOpenTelemetryAgentName('opentelemetry')).toBe(true);
expect(isOpenTelemetryAgentName('opentelemetry/nodejs')).toBe(true);
expect(isOpenTelemetryAgentName('opentelemetry/nodejs/elastic')).toBe(true);
expect(isOpenTelemetryAgentName('not-an-agent')).toBe(false);
});

it('isJavaAgentName should guard if the passed agent is an Java one.', () => {
expect(isJavaAgentName('java')).toBe(true);
expect(isJavaAgentName('otlp/java')).toBe(true);
expect(isJavaAgentName('otlp/java/opentelemetry-java-instrumentation')).toBe(true);
expect(isJavaAgentName('opentelemetry/java')).toBe(true);
expect(isJavaAgentName('opentelemetry/java/opentelemetry-java-instrumentation')).toBe(true);
expect(isJavaAgentName('not-an-agent')).toBe(false);
});

it('isRumAgentName should guard if the passed agent is an Rum one.', () => {
expect(isRumAgentName('otlp/webjs')).toBe(true);
expect(isRumAgentName('otlp/webjs/elastic')).toBe(true);
expect(isRumAgentName('otlp/fail')).toBe(false);
expect(isRumAgentName('opentelemetry/webjs')).toBe(true);
expect(isRumAgentName('opentelemetry/webjs/elastic')).toBe(true);
expect(isRumAgentName('opentelemetry/fail')).toBe(false);
expect(isRumAgentName('rum-js')).toBe(true);
expect(isRumAgentName('not-an-agent')).toBe(false);
});
Expand All @@ -57,11 +77,23 @@ describe('Agents guards', () => {
});

it('isIosAgentName should guard if the passed agent is an Ios one.', () => {
expect(isIosAgentName('otlp/swift/elastic')).toBe(true);
expect(isIosAgentName('otlp/swift')).toBe(true);
expect(isIosAgentName('otlp/fail')).toBe(false);
expect(isIosAgentName('opentelemetry/swift/elastic')).toBe(true);
expect(isIosAgentName('opentelemetry/swift')).toBe(true);
expect(isIosAgentName('opentelemetry/fail')).toBe(false);
expect(isIosAgentName('ios/swift')).toBe(true);
expect(isIosAgentName('not-an-agent')).toBe(false);
});

it('isAndroidAgentName should guard if the passed agent is an Android one.', () => {
expect(isAndroidAgentName('otlp/android/elastic')).toBe(true);
expect(isAndroidAgentName('otlp/android')).toBe(true);
expect(isAndroidAgentName('otlp/fail')).toBe(false);
expect(isAndroidAgentName('opentelemetry/android/elastic')).toBe(true);
expect(isAndroidAgentName('opentelemetry/android')).toBe(true);
expect(isAndroidAgentName('opentelemetry/fail')).toBe(false);
expect(isAndroidAgentName('android/java')).toBe(true);
expect(isAndroidAgentName('not-an-agent')).toBe(false);
});
Expand Down
44 changes: 37 additions & 7 deletions packages/kbn-elastic-agent-utils/src/agent_guards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,52 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { JAVA_AGENT_NAMES, OPEN_TELEMETRY_AGENT_NAMES, RUM_AGENT_NAMES } from './agent_names';
import {
ANDROID_AGENT_NAMES,
IOS_AGENT_NAMES,
JAVA_AGENT_NAMES,
OPEN_TELEMETRY_AGENT_NAMES,
RUM_AGENT_NAMES,
} from './agent_names';

import type {
AndroidAgentName,
IOSAgentName,
JavaAgentName,
OpenTelemetryAgentName,
RumAgentName,
ServerlessType,
} from './agent_names';

export function hasOpenTelemetryPrefix(agentName?: string, language: string = '') {
if (!agentName) {
return false;
}

return (
agentName.startsWith(`opentelemetry/${language}`) || agentName.startsWith(`otlp/${language}`)
);
}

export function isOpenTelemetryAgentName(agentName: string): agentName is OpenTelemetryAgentName {
return (
agentName?.startsWith('opentelemetry/') ||
hasOpenTelemetryPrefix(agentName) ||
OPEN_TELEMETRY_AGENT_NAMES.includes(agentName as OpenTelemetryAgentName)
);
}

export function isJavaAgentName(agentName?: string): agentName is JavaAgentName {
return (
agentName?.startsWith('opentelemetry/java') ||
hasOpenTelemetryPrefix(agentName, 'java') ||
JAVA_AGENT_NAMES.includes(agentName! as JavaAgentName)
);
}

export function isRumAgentName(agentName?: string): agentName is RumAgentName {
return RUM_AGENT_NAMES.includes(agentName! as RumAgentName);
return (
hasOpenTelemetryPrefix(agentName, 'webjs') ||
RUM_AGENT_NAMES.includes(agentName! as RumAgentName)
);
}

export function isMobileAgentName(agentName?: string) {
Expand All @@ -43,12 +64,21 @@ export function isRumOrMobileAgentName(agentName?: string) {
}

export function isIosAgentName(agentName?: string) {
return agentName?.toLowerCase() === 'ios/swift';
const lowercasedAgentName = agentName && agentName.toLowerCase();

return (
hasOpenTelemetryPrefix(lowercasedAgentName, 'swift') ||
IOS_AGENT_NAMES.includes(lowercasedAgentName! as IOSAgentName)
);
}

export function isAndroidAgentName(agentName?: string) {
const lowercased = agentName && agentName.toLowerCase();
return lowercased === 'android/java';
const lowercasedAgentName = agentName && agentName.toLowerCase();

return (
hasOpenTelemetryPrefix(lowercasedAgentName, 'android') ||
ANDROID_AGENT_NAMES.includes(lowercasedAgentName! as AndroidAgentName)
);
}

export function isJRubyAgentName(agentName?: string, runtimeName?: string) {
Expand Down
Loading

0 comments on commit b75df09

Please sign in to comment.