Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.15] [APM UI] Fix OpenTelemetry agent names (#193134) #193511

Merged
merged 2 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 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,101 @@
/*
* 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 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 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 @@ -24,6 +24,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 @@ -10,6 +10,7 @@ import {
isIosAgentName,
isJavaAgentName,
isRumAgentName,
hasOpenTelemetryPrefix,
OpenTelemetryAgentName,
OPEN_TELEMETRY_AGENT_NAMES,
} from '@kbn/elastic-agent-utils';
Expand Down Expand Up @@ -64,6 +65,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 @@ -88,11 +98,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 @@ -7,6 +7,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 @@ -7,6 +7,7 @@
*/

import {
hasOpenTelemetryPrefix,
isAndroidAgentName,
isAWSLambdaAgentName,
isAzureFunctionsAgentName,
Expand All @@ -21,23 +22,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 @@ -56,11 +76,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 @@ -6,31 +6,52 @@
* 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 @@ -42,12 +63,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