From 870f4e3728084ce8a066d2d5410bcccdc957e77a Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 7 Mar 2025 01:53:17 +1100 Subject: [PATCH] [8.x] [ObsUX] [APM] [OTel] Runtime metrics show dashboards with different ingest path (#211822) (#213373) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Backport This will backport the following commits from `main` to `8.x`: - [[ObsUX] [APM] [OTel] Runtime metrics show dashboards with different ingest path (#211822)](https://github.com/elastic/kibana/pull/211822) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) APM Server --> ES\nVanilla OTel SDKs --> APM Server --> ES\nEDOT OTel SDKs --> APM Server --> ES\nClassic APM Agent --> EDOT Collector --> ES\nVanilla OTel SDKs. --> EDOT Collector --> ES\nEDOT OTel SDKs --> EDOT Collector --> ES\nWe agreed on having a dashboard filename pattern to make showing the\ncorrect dashboard easier described\n[here](https://github.com/elastic/kibana/issues/195857#issue-2580733648)\n\nFirst, we determine if the ingest path is through APM Server or EDOT\nCollector by checking the `telemtry.sdk` fields.\n\n## TODOs / Reviewer notes\n- [ ] Currently, we have a fallback to metrics charts which is valid\nonly if we have APM agent so this PR adds an empty state message:\n\"Runtime metrics are not available for this Agent / SDK type.\" in case\nthere is no dashboard for the service language. To be improved in\nhttps://github.com/elastic/kibana/issues/211774 and will be updated in\nthis PR when ready - I will still open it for review as the other logic\ncan be reviewed\n- The dashboards are to be updated (by the agent team so not part of the\nchanges here)\n\n## Testing: \n- Using e2e PoC \n- The available dashboard cases can be found in\n[loadDashboardFile](https://github.com/jennypavlova/kibana/blob/91f169e19a3fa3f83ca60eb078159c026f9617af/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/dashboards/dashboard_catalog.ts#L40)\n- Cases to be checked:\n- OTel native with Vanilla OTel SDKs with available dashboard (example\ncase file: `otel_native-otel_other-nodejs`, `...-java`, `...-dotnet`)\n \n\"image\"\nsrc=\"https://github.com/user-attachments/assets/44d37b05-a8e7-4f14-a1de-2c631f1843bb\"\n/\n\n- APM server with Vanilla OTel SDKs service with available dashboard\n(example case file: `classic_apm-otel_other-nodejs`, `...-java`,\n`...-dotnet`)\n \n\n![image](https://github.com/user-attachments/assets/caef88ea-2603-41ad-b815-f4c0c3647809)\n\n- APM server with Classic APM Agent (example case file:\n`classic_apm-apm-nodejs`, `...-java`)\n \n\"image\"\nsrc=\"https://github.com/user-attachments/assets/f9e96dce-55c8-467a-93f0-a09fa219597e\"\n/\n\n- OTel native with Vanilla OTel SDKs without available dashboard (empty\nstate case example: python service)\n \n\n![image](https://github.com/user-attachments/assets/4cb6cca3-240e-422b-9288-701ef080f9cc)\n\n- APM server with Vanilla OTel SDKs service without available dashboard\n(empty state)\n \n\"image\"\nsrc=\"https://github.com/user-attachments/assets/5219cf94-5013-4874-aaea-e558cca69281\"\n/\n\n- APM server with Classic APM Agent without available dashboard (Current\nmetrics fallback)\n \n\"image\"\nsrc=\"https://github.com/user-attachments/assets/66342f49-876c-4ad5-a4d1-1414c3abac75\"\n/\n\n- ⚠️ OTel native Dashboards are still not available (at the time of\nadding the description)\n\n---------\n\nCo-authored-by: Sergi Romeu \nCo-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com>\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"f19557004904b490fcb69d18c1ea5f1ef9634ee0","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement","backport:prev-minor","backport:prev-major","Team:obs-ux-infra_services","v9.1.0"],"title":"[ObsUX] [APM] [OTel] Runtime metrics show dashboards with different ingest path","number":211822,"url":"https://github.com/elastic/kibana/pull/211822","mergeCommit":{"message":"[ObsUX] [APM] [OTel] Runtime metrics show dashboards with different ingest path (#211822)\n\nCloses #211783\nPart of https://github.com/elastic/kibana/issues/195857\n\n## Summary\n\nThis PR expands the logic to get the dashboard files based on the agent.\nWe have many different ways to ingest data so we want to add more\nmetrics dashboards to the APM metrics tab. The different ingest paths we\nhave:\nClassic APM Agent --> APM Server --> ES\nVanilla OTel SDKs --> APM Server --> ES\nEDOT OTel SDKs --> APM Server --> ES\nClassic APM Agent --> EDOT Collector --> ES\nVanilla OTel SDKs. --> EDOT Collector --> ES\nEDOT OTel SDKs --> EDOT Collector --> ES\nWe agreed on having a dashboard filename pattern to make showing the\ncorrect dashboard easier described\n[here](https://github.com/elastic/kibana/issues/195857#issue-2580733648)\n\nFirst, we determine if the ingest path is through APM Server or EDOT\nCollector by checking the `telemtry.sdk` fields.\n\n## TODOs / Reviewer notes\n- [ ] Currently, we have a fallback to metrics charts which is valid\nonly if we have APM agent so this PR adds an empty state message:\n\"Runtime metrics are not available for this Agent / SDK type.\" in case\nthere is no dashboard for the service language. To be improved in\nhttps://github.com/elastic/kibana/issues/211774 and will be updated in\nthis PR when ready - I will still open it for review as the other logic\ncan be reviewed\n- The dashboards are to be updated (by the agent team so not part of the\nchanges here)\n\n## Testing: \n- Using e2e PoC \n- The available dashboard cases can be found in\n[loadDashboardFile](https://github.com/jennypavlova/kibana/blob/91f169e19a3fa3f83ca60eb078159c026f9617af/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/dashboards/dashboard_catalog.ts#L40)\n- Cases to be checked:\n- OTel native with Vanilla OTel SDKs with available dashboard (example\ncase file: `otel_native-otel_other-nodejs`, `...-java`, `...-dotnet`)\n \n\"image\"\nsrc=\"https://github.com/user-attachments/assets/44d37b05-a8e7-4f14-a1de-2c631f1843bb\"\n/\n\n- APM server with Vanilla OTel SDKs service with available dashboard\n(example case file: `classic_apm-otel_other-nodejs`, `...-java`,\n`...-dotnet`)\n \n\n![image](https://github.com/user-attachments/assets/caef88ea-2603-41ad-b815-f4c0c3647809)\n\n- APM server with Classic APM Agent (example case file:\n`classic_apm-apm-nodejs`, `...-java`)\n \n\"image\"\nsrc=\"https://github.com/user-attachments/assets/f9e96dce-55c8-467a-93f0-a09fa219597e\"\n/\n\n- OTel native with Vanilla OTel SDKs without available dashboard (empty\nstate case example: python service)\n \n\n![image](https://github.com/user-attachments/assets/4cb6cca3-240e-422b-9288-701ef080f9cc)\n\n- APM server with Vanilla OTel SDKs service without available dashboard\n(empty state)\n \n\"image\"\nsrc=\"https://github.com/user-attachments/assets/5219cf94-5013-4874-aaea-e558cca69281\"\n/\n\n- APM server with Classic APM Agent without available dashboard (Current\nmetrics fallback)\n \n\"image\"\nsrc=\"https://github.com/user-attachments/assets/66342f49-876c-4ad5-a4d1-1414c3abac75\"\n/\n\n- ⚠️ OTel native Dashboards are still not available (at the time of\nadding the description)\n\n---------\n\nCo-authored-by: Sergi Romeu \nCo-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com>\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"f19557004904b490fcb69d18c1ea5f1ef9634ee0"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/211822","number":211822,"mergeCommit":{"message":"[ObsUX] [APM] [OTel] Runtime metrics show dashboards with different ingest path (#211822)\n\nCloses #211783\nPart of https://github.com/elastic/kibana/issues/195857\n\n## Summary\n\nThis PR expands the logic to get the dashboard files based on the agent.\nWe have many different ways to ingest data so we want to add more\nmetrics dashboards to the APM metrics tab. The different ingest paths we\nhave:\nClassic APM Agent --> APM Server --> ES\nVanilla OTel SDKs --> APM Server --> ES\nEDOT OTel SDKs --> APM Server --> ES\nClassic APM Agent --> EDOT Collector --> ES\nVanilla OTel SDKs. --> EDOT Collector --> ES\nEDOT OTel SDKs --> EDOT Collector --> ES\nWe agreed on having a dashboard filename pattern to make showing the\ncorrect dashboard easier described\n[here](https://github.com/elastic/kibana/issues/195857#issue-2580733648)\n\nFirst, we determine if the ingest path is through APM Server or EDOT\nCollector by checking the `telemtry.sdk` fields.\n\n## TODOs / Reviewer notes\n- [ ] Currently, we have a fallback to metrics charts which is valid\nonly if we have APM agent so this PR adds an empty state message:\n\"Runtime metrics are not available for this Agent / SDK type.\" in case\nthere is no dashboard for the service language. To be improved in\nhttps://github.com/elastic/kibana/issues/211774 and will be updated in\nthis PR when ready - I will still open it for review as the other logic\ncan be reviewed\n- The dashboards are to be updated (by the agent team so not part of the\nchanges here)\n\n## Testing: \n- Using e2e PoC \n- The available dashboard cases can be found in\n[loadDashboardFile](https://github.com/jennypavlova/kibana/blob/91f169e19a3fa3f83ca60eb078159c026f9617af/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/dashboards/dashboard_catalog.ts#L40)\n- Cases to be checked:\n- OTel native with Vanilla OTel SDKs with available dashboard (example\ncase file: `otel_native-otel_other-nodejs`, `...-java`, `...-dotnet`)\n \n\"image\"\nsrc=\"https://github.com/user-attachments/assets/44d37b05-a8e7-4f14-a1de-2c631f1843bb\"\n/\n\n- APM server with Vanilla OTel SDKs service with available dashboard\n(example case file: `classic_apm-otel_other-nodejs`, `...-java`,\n`...-dotnet`)\n \n\n![image](https://github.com/user-attachments/assets/caef88ea-2603-41ad-b815-f4c0c3647809)\n\n- APM server with Classic APM Agent (example case file:\n`classic_apm-apm-nodejs`, `...-java`)\n \n\"image\"\nsrc=\"https://github.com/user-attachments/assets/f9e96dce-55c8-467a-93f0-a09fa219597e\"\n/\n\n- OTel native with Vanilla OTel SDKs without available dashboard (empty\nstate case example: python service)\n \n\n![image](https://github.com/user-attachments/assets/4cb6cca3-240e-422b-9288-701ef080f9cc)\n\n- APM server with Vanilla OTel SDKs service without available dashboard\n(empty state)\n \n\"image\"\nsrc=\"https://github.com/user-attachments/assets/5219cf94-5013-4874-aaea-e558cca69281\"\n/\n\n- APM server with Classic APM Agent without available dashboard (Current\nmetrics fallback)\n \n\"image\"\nsrc=\"https://github.com/user-attachments/assets/66342f49-876c-4ad5-a4d1-1414c3abac75\"\n/\n\n- ⚠️ OTel native Dashboards are still not available (at the time of\nadding the description)\n\n---------\n\nCo-authored-by: Sergi Romeu \nCo-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com>\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"f19557004904b490fcb69d18c1ea5f1ef9634ee0"}}]}] BACKPORT--> Co-authored-by: jennypavlova --- .../src/lib/otel/index.ts | 4 +- .../shared/kbn-elastic-agent-utils/index.ts | 4 + .../src/agent_guards.test.ts | 12 ++ .../src/agent_guards.ts | 7 + .../src/agent_ingestion_path.ts | 11 ++ .../src/agent_sdk_name_and_language.test.ts | 61 +++++++ .../src/agent_sdk_name_and_language.ts | 36 ++++ .../components/app/metrics/index.test.tsx | 155 ++++++++++++++++++ .../public/components/app/metrics/index.tsx | 44 +++-- .../dashboards/dashboard_catalog.ts | 71 +++++--- .../get_dashboard_file_name.test.ts | 152 +++++++++++++++++ .../dashboards/get_dashboard_file_name.ts | 33 ++++ .../app/metrics/static_dashboard/helper.ts | 31 ++-- .../apm_service/apm_service_context.tsx | 6 + .../apm_service/use_service_agent_fetcher.ts | 2 + .../routes/services/get_service_agent.ts | 17 +- .../observability/apm/services/agent.spec.ts | 5 +- 17 files changed, 590 insertions(+), 61 deletions(-) create mode 100644 src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_ingestion_path.ts create mode 100644 src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_sdk_name_and_language.test.ts create mode 100644 src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_sdk_name_and_language.ts create mode 100644 x-pack/solutions/observability/plugins/apm/public/components/app/metrics/index.test.tsx create mode 100644 x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/dashboards/get_dashboard_file_name.test.ts create mode 100644 x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/dashboards/get_dashboard_file_name.ts diff --git a/src/platform/packages/shared/kbn-apm-synthtrace-client/src/lib/otel/index.ts b/src/platform/packages/shared/kbn-apm-synthtrace-client/src/lib/otel/index.ts index dfa3f10d8fa6f..1ff60574161d3 100644 --- a/src/platform/packages/shared/kbn-apm-synthtrace-client/src/lib/otel/index.ts +++ b/src/platform/packages/shared/kbn-apm-synthtrace-client/src/lib/otel/index.ts @@ -132,11 +132,13 @@ class Otel extends Serializable { }, resource: { attributes: { - 'agent.name': 'otlp', + 'agent.name': 'opentelemetry/nodejs', 'agent.version': '1.28.0', 'service.instance.id': '89117ac1-0dbf-4488-9e17-4c2c3b76943a', 'service.name': 'sendotlp-synth', 'metricset.interval': '10m', + 'telemetry.sdk.name': 'opentelemetry', + 'telemetry.sdk.language': 'nodejs', }, dropped_attributes_count: 0, }, diff --git a/src/platform/packages/shared/kbn-elastic-agent-utils/index.ts b/src/platform/packages/shared/kbn-elastic-agent-utils/index.ts index d92db7cd9489c..1b9db813d5891 100644 --- a/src/platform/packages/shared/kbn-elastic-agent-utils/index.ts +++ b/src/platform/packages/shared/kbn-elastic-agent-utils/index.ts @@ -32,6 +32,10 @@ export { AGENT_NAMES, } from './src/agent_names'; +export { getIngestionPath } from './src/agent_ingestion_path'; + +export { getSdkNameAndLanguage } from './src/agent_sdk_name_and_language'; + export type { ElasticAgentName, OpenTelemetryAgentName, diff --git a/src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_guards.test.ts b/src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_guards.test.ts index 1c9360649439b..388c2eebf843d 100644 --- a/src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_guards.test.ts +++ b/src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_guards.test.ts @@ -13,6 +13,7 @@ import { isAndroidAgentName, isAWSLambdaAgentName, isAzureFunctionsAgentName, + isElasticAgentName, isIosAgentName, isJavaAgentName, isJRubyAgentName, @@ -44,6 +45,17 @@ describe('Agents guards', () => { expect(isOpenTelemetryAgentName('not-an-agent')).toBe(false); }); + it('isElasticAgentName should guard if the passed agent is an APM agent one.', () => { + expect(isElasticAgentName('nodejs')).toBe(true); + expect(isElasticAgentName('iOS/swift')).toBe(true); + expect(isElasticAgentName('java')).toBe(true); + expect(isElasticAgentName('rum-js')).toBe(true); + expect(isElasticAgentName('android/java')).toBe(true); + expect(isElasticAgentName('node-js')).toBe(false); + expect(isElasticAgentName('opentelemetry/nodejs/elastic')).toBe(false); + expect(isElasticAgentName('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); diff --git a/src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_guards.ts b/src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_guards.ts index ea9d112d9630b..eda96c9853c5d 100644 --- a/src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_guards.ts +++ b/src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_guards.ts @@ -9,6 +9,7 @@ import { ANDROID_AGENT_NAMES, + ELASTIC_AGENT_NAMES, IOS_AGENT_NAMES, JAVA_AGENT_NAMES, OPEN_TELEMETRY_AGENT_NAMES, @@ -17,6 +18,7 @@ import { import type { AndroidAgentName, + ElasticAgentName, IOSAgentName, JavaAgentName, OpenTelemetryAgentName, @@ -24,6 +26,8 @@ import type { ServerlessType, } from './agent_names'; +const ElasticAgentNamesSet = new Set(ELASTIC_AGENT_NAMES); + export function getAgentName( agentName: string | null, telemetryAgentName: string | null, @@ -57,6 +61,9 @@ export function isOpenTelemetryAgentName(agentName: string): agentName is OpenTe ); } +export const isElasticAgentName = (agentName: string): agentName is ElasticAgentName => + ElasticAgentNamesSet.has(agentName as ElasticAgentName); + export function isJavaAgentName(agentName?: string): agentName is JavaAgentName { return ( hasOpenTelemetryPrefix(agentName, 'java') || diff --git a/src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_ingestion_path.ts b/src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_ingestion_path.ts new file mode 100644 index 0000000000000..a41a84792ef67 --- /dev/null +++ b/src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_ingestion_path.ts @@ -0,0 +1,11 @@ +/* + * 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". + */ + +export const getIngestionPath = (hasOpenTelemetryFields: boolean) => + hasOpenTelemetryFields ? 'otel_native' : 'classic_apm'; diff --git a/src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_sdk_name_and_language.test.ts b/src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_sdk_name_and_language.test.ts new file mode 100644 index 0000000000000..9ec09740dcd87 --- /dev/null +++ b/src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_sdk_name_and_language.test.ts @@ -0,0 +1,61 @@ +/* + * 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 { getSdkNameAndLanguage } from './agent_sdk_name_and_language'; + +describe('getSdkNameAndLanguage', () => { + it.each([ + { + agentName: 'java', + result: { sdkName: 'apm', language: 'java' }, + }, + { + agentName: 'iOS/swift', + result: { sdkName: 'apm', language: 'iOS/swift' }, + }, + { + agentName: 'android/java', + result: { sdkName: 'apm', language: 'android/java' }, + }, + { + agentName: 'opentelemetry/java/test/elastic', + result: { sdkName: 'edot', language: 'java' }, + }, + { + agentName: 'opentelemetry/java/elastic', + result: { sdkName: 'edot', language: 'java' }, + }, + { + agentName: 'otlp/nodejs', + result: { sdkName: 'otel_other', language: 'nodejs' }, + }, + { + agentName: 'otlp', + result: { sdkName: 'otel_other', language: undefined }, + }, + { + agentName: 'test/test/test/something-else/elastic', + result: { sdkName: undefined, language: undefined }, + }, + { + agentName: 'test/java/test/something-else/', + result: { sdkName: undefined, language: undefined }, + }, + { + agentName: 'elastic', + result: { sdkName: undefined, language: undefined }, + }, + { + agentName: 'my-awesome-agent/otel', + result: { sdkName: undefined, language: undefined }, + }, + ])('for the agent name $agentName returns $result', ({ agentName, result }) => { + expect(getSdkNameAndLanguage(agentName)).toStrictEqual(result); + }); +}); diff --git a/src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_sdk_name_and_language.ts b/src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_sdk_name_and_language.ts new file mode 100644 index 0000000000000..1cffd32bcab56 --- /dev/null +++ b/src/platform/packages/shared/kbn-elastic-agent-utils/src/agent_sdk_name_and_language.ts @@ -0,0 +1,36 @@ +/* + * 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 { isElasticAgentName, isOpenTelemetryAgentName } from './agent_guards'; + +interface SdkNameAndLanguage { + sdkName?: 'apm' | 'edot' | 'otel_other'; + language?: string; +} + +const LANGUAGE_INDEX = 1; + +export const getSdkNameAndLanguage = (agentName: string): SdkNameAndLanguage => { + if (isElasticAgentName(agentName)) { + return { sdkName: 'apm', language: agentName }; + } + const agentNameParts = agentName.split('/'); + + if (isOpenTelemetryAgentName(agentName)) { + if (agentNameParts[agentNameParts.length - 1] === 'elastic') { + return { sdkName: 'edot', language: agentNameParts[LANGUAGE_INDEX] }; + } + return { + sdkName: 'otel_other', + language: agentNameParts[LANGUAGE_INDEX], + }; + } + + return { sdkName: undefined, language: undefined }; +}; diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/index.test.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/index.test.tsx new file mode 100644 index 0000000000000..d89409c021f55 --- /dev/null +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/index.test.tsx @@ -0,0 +1,155 @@ +/* + * 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 type { CoreStart } from '@kbn/core/public'; +import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; +import { render } from '@testing-library/react'; +import { createMemoryHistory } from 'history'; +import React from 'react'; +import type { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; +import { + MockApmPluginContextWrapper, + mockApmPluginContextValue, +} from '../../../context/apm_plugin/mock_apm_plugin_context'; +import * as useApmServiceContext from '../../../context/apm_service/use_apm_service_context'; +import type { ServiceEntitySummary } from '../../../context/apm_service/use_service_entity_summary_fetcher'; +import * as useApmDataViewHook from '../../../hooks/use_adhoc_apm_data_view'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { fromQuery } from '../../shared/links/url_helpers'; +import { Metrics } from '.'; +import type { DataView } from '@kbn/data-views-plugin/common'; + +const KibanaReactContext = createKibanaReactContext({ + settings: { client: { get: () => {} } }, +} as unknown as Partial); + +function MetricsWithWrapper() { + jest + .spyOn(useApmDataViewHook, 'useAdHocApmDataView') + .mockReturnValue({ dataView: { id: 'id-1', name: 'apm-data-view' } as DataView }); + + const history = createMemoryHistory(); + history.replace({ + pathname: '/services/testServiceName/metrics', + search: fromQuery({ + rangeFrom: 'now-15m', + rangeTo: 'now', + }), + }); + + return ( + + + + + + ); +} + +describe('Metrics', () => { + describe('render the correct metrics content for', () => { + describe('APM agent / server service', () => { + beforeEach(() => { + jest.spyOn(useApmServiceContext, 'useApmServiceContext').mockReturnValue({ + agentName: 'java', + serviceName: 'testServiceName', + transactionTypeStatus: FETCH_STATUS.SUCCESS, + transactionTypes: [], + fallbackToTransactions: true, + serviceAgentStatus: FETCH_STATUS.SUCCESS, + serviceEntitySummaryStatus: FETCH_STATUS.SUCCESS, + serviceEntitySummary: { + dataStreamTypes: ['metrics'], + } as unknown as ServiceEntitySummary, + }); + }); + + it('shows java dashboard content', () => { + const result = render(); + // Check that the other content is not rendered as we don't have test id in the dashboard rendering component + const loadingBar = result.queryByRole('progressbar'); + expect(loadingBar).toBeNull(); + expect(result.queryByTestId('apmMetricsNoDashboardFound')).toBeNull(); + expect(result.queryByTestId('apmAddApmCallout')).toBeNull(); + }); + }); + + describe('APM agent / EDOT sdk with dashboard', () => { + beforeEach(() => { + jest.spyOn(useApmServiceContext, 'useApmServiceContext').mockReturnValue({ + agentName: 'opentelemetry/nodejs/elastic', + serviceName: 'testServiceName', + transactionTypeStatus: FETCH_STATUS.SUCCESS, + transactionTypes: [], + fallbackToTransactions: true, + serviceAgentStatus: FETCH_STATUS.SUCCESS, + serviceEntitySummaryStatus: FETCH_STATUS.SUCCESS, + serviceEntitySummary: { + dataStreamTypes: ['metrics'], + } as unknown as ServiceEntitySummary, + }); + }); + + it('shows nodejs dashboard content', () => { + const result = render(); + // Check that the other content is not rendered as we don't have test id in the dashboard rendering component + const loadingBar = result.queryByRole('progressbar'); + expect(loadingBar).toBeNull(); + expect(result.queryByTestId('apmMetricsNoDashboardFound')).toBeNull(); + expect(result.queryByTestId('apmAddApmCallout')).toBeNull(); + }); + }); + + describe('APM agent / otel sdk with no dashboard', () => { + beforeEach(() => { + jest.spyOn(useApmServiceContext, 'useApmServiceContext').mockReturnValue({ + agentName: 'opentelemetry/go', + serviceName: 'testServiceName', + transactionTypeStatus: FETCH_STATUS.SUCCESS, + transactionTypes: [], + fallbackToTransactions: true, + serviceAgentStatus: FETCH_STATUS.SUCCESS, + serviceEntitySummaryStatus: FETCH_STATUS.SUCCESS, + serviceEntitySummary: { + dataStreamTypes: ['metrics'], + } as unknown as ServiceEntitySummary, + }); + }); + + it('shows "no dashboard found" message', () => { + const result = render(); + const apmMetricsNoDashboardFound = result.getByTestId('apmMetricsNoDashboardFound'); + expect(apmMetricsNoDashboardFound).toBeInTheDocument(); + }); + }); + + describe('Logs signals', () => { + beforeEach(() => { + jest.spyOn(useApmServiceContext, 'useApmServiceContext').mockReturnValue({ + agentName: 'java', + serviceName: 'testServiceName', + transactionTypeStatus: FETCH_STATUS.SUCCESS, + transactionTypes: [], + fallbackToTransactions: true, + serviceAgentStatus: FETCH_STATUS.SUCCESS, + serviceEntitySummaryStatus: FETCH_STATUS.SUCCESS, + serviceEntitySummary: { + dataStreamTypes: ['logs'], + } as unknown as ServiceEntitySummary, + }); + }); + + it('shows service from logs metrics content', () => { + const result = render(); + const apmAddApmCallout = result.getByTestId('apmAddApmCallout'); + expect(apmAddApmCallout).toBeInTheDocument(); + }); + }); + }); +}); diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/index.tsx index e89bffeaf5b5d..06fdee55d4cfe 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/index.tsx @@ -6,27 +6,33 @@ */ import React from 'react'; -import { - isJavaAgentName, - isJRubyAgentName, - isAWSLambdaAgentName, -} from '../../../../common/agent_name'; +import { isElasticAgentName, isJRubyAgentName } from '@kbn/elastic-agent-utils/src/agent_guards'; +import { EuiCallOut } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { isAWSLambdaAgentName } from '../../../../common/agent_name'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { ServerlessMetrics } from './serverless_metrics'; import { ServiceMetrics } from './service_metrics'; -import { JvmMetricsOverview } from './jvm_metrics_overview'; import { JsonMetricsDashboard } from './static_dashboard'; -import { hasDashboardFile } from './static_dashboard/helper'; +import { hasDashboard } from './static_dashboard/helper'; import { useAdHocApmDataView } from '../../../hooks/use_adhoc_apm_data_view'; import { isLogsOnlySignal } from '../../../utils/get_signal_type'; import { ServiceTabEmptyState } from '../service_tab_empty_state'; +import { JvmMetricsOverview } from './jvm_metrics_overview'; export function Metrics() { - const { agentName, runtimeName, serverlessType } = useApmServiceContext(); + const { + agentName, + runtimeName, + serverlessType, + serviceEntitySummary, + telemetrySdkName, + telemetrySdkLanguage, + } = useApmServiceContext(); const isAWSLambda = isAWSLambdaAgentName(serverlessType); const { dataView } = useAdHocApmDataView(); - const { serviceEntitySummary } = useApmServiceContext(); + const hasDashboardFile = hasDashboard({ agentName, telemetrySdkName, telemetrySdkLanguage }); const hasLogsOnlySignal = serviceEntitySummary?.dataStreamTypes && isLogsOnlySignal(serviceEntitySummary.dataStreamTypes); @@ -38,13 +44,19 @@ export function Metrics() { return ; } - const hasStaticDashboard = hasDashboardFile({ - agentName, - runtimeName, - serverlessType, - }); + if (!hasDashboardFile && !isElasticAgentName(agentName ?? '')) { + return ( + + ); + } - if (hasStaticDashboard && dataView) { + if (hasDashboardFile && dataView) { return ( ; } diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/dashboards/dashboard_catalog.ts b/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/dashboards/dashboard_catalog.ts index ea3c468a6c829..59ba771dea86c 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/dashboards/dashboard_catalog.ts +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/dashboards/dashboard_catalog.ts @@ -5,52 +5,69 @@ * 2.0. */ -export const AGENT_NAME_DASHBOARD_FILE_MAPPING: Record = { - nodejs: 'nodejs', - 'opentelemetry/nodejs': 'opentelemetry_nodejs', - 'opentelemetry/nodejs/elastic': 'opentelemetry_nodejs', - java: 'java', - 'opentelemetry/java': 'opentelemetry_java', - 'opentelemetry/java/opentelemetry-java-instrumentation': 'opentelemetry_java', - 'opentelemetry/java/elastic': 'opentelemetry_java', - 'opentelemetry/dotnet': 'opentelemetry_dotnet', - 'opentelemetry/dotnet/opentelemetry-dotnet-instrumentation': 'opentelemetry_dotnet', - 'opentelemetry/dotnet/elastic': 'opentelemetry_dotnet', -}; +// The new dashboard file names should be added here +export const existingDashboardFileNames = new Set([ + 'classic_apm-apm-nodejs', + 'classic_apm-apm-java', + 'classic_apm-otel_other-nodejs', + 'classic_apm-otel_other-java', + 'classic_apm-otel_other-dotnet', + 'classic_apm-edot-nodejs', + 'classic_apm-edot-java', + 'classic_apm-edot-dotnet', +]); -/** - * The specially formatted comment in the `import` expression causes the corresponding webpack chunk to be named. This aids us in debugging chunk size issues. - * See https://webpack.js.org/api/module-methods/#magic-comments - */ -export async function loadDashboardFile(filename: string): Promise { +// The new dashboard files should be mapped here +// + changed with the new ones (following the naming convention) +// + similar mapping for edot needed +// - example: otel_native-edot-nodejs +export async function loadDashboardFile(filename: string) { switch (filename) { - case 'nodejs': { + case 'classic_apm-apm-nodejs': { return import( - /* webpackChunkName: "lazyNodeJsDashboard" */ + /* webpackChunkName: "lazyNodeJsClassicApmDashboard" */ './nodejs.json' ); } - case 'opentelemetry_nodejs': { + case 'classic_apm-otel_other-nodejs': { return import( - /* webpackChunkName: "lazyNodeJsDashboard" */ + /* webpackChunkName: "lazyNodeJsApmOtelDashboard" */ './opentelemetry_nodejs.json' ); } - case 'java': { + case 'classic_apm-edot-nodejs': { return import( - /* webpackChunkName: "lazyJavaDashboard" */ + /* webpackChunkName: "lazyNodeJsOtelNativeDashboard" */ + './opentelemetry_nodejs.json' + ); + } + case 'classic_apm-apm-java': { + return import( + /* webpackChunkName: "lazyJavaClassicApmDashboard" */ './java.json' ); } - case 'opentelemetry_java': { + case 'classic_apm-otel_other-java': { return import( - /* webpackChunkName: "lazyJavaDashboard" */ + /* webpackChunkName: "lazyJavaApmOtelDashboard" */ './opentelemetry_java.json' ); } - case 'opentelemetry_dotnet': { + case 'classic_apm-edot-java': { + return import( + /* webpackChunkName: "lazyJavaOtelNativeDashboard" */ + './opentelemetry_java.json' + ); + } + case 'classic_apm-edot-dotnet': { + return import( + /* webpackChunkName: "lazyDotnetOtelNativeDashboard" */ + './opentelemetry_dotnet.json' + ); + } + case 'classic_apm-otel_other-dotnet': { return import( - /* webpackChunkName: "lazyOtelDotnetDashboard" */ + /* webpackChunkName: "lazyDotnetApmOtelDashboard" */ './opentelemetry_dotnet.json' ); } diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/dashboards/get_dashboard_file_name.test.ts b/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/dashboards/get_dashboard_file_name.test.ts new file mode 100644 index 0000000000000..5043ff98ac855 --- /dev/null +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/dashboards/get_dashboard_file_name.test.ts @@ -0,0 +1,152 @@ +/* + * 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 { getDashboardFileName } from './get_dashboard_file_name'; + +const apmAgent = [ + { + agentName: 'java', + telemetrySdkName: undefined, + telemetrySdkLanguage: undefined, + filename: 'classic_apm-apm-java', + }, + { + agentName: 'iOS/swift', + telemetrySdkName: undefined, + telemetrySdkLanguage: undefined, + filename: 'classic_apm-apm-ios_swift', + }, + { + agentName: 'java', + telemetrySdkName: 'opentelemetry', + filename: 'otel_native-apm-java', + }, +]; +const edotSdk = [ + { + agentName: 'opentelemetry/java/test/elastic', + filename: 'classic_apm-edot-java', + }, + { + agentName: 'opentelemetry/java/elastic', + filename: 'classic_apm-edot-java', + }, + { + agentName: 'opentelemetry/java/test/elastic', + filename: 'classic_apm-edot-java', + }, + { + agentName: 'opentelemetry/java/elastic', + filename: 'classic_apm-edot-java', + }, + { + agentName: 'opentelemetry/java/elastic', + telemetrySdkName: 'opentelemetry', + telemetrySdkLanguage: 'java', + filename: 'otel_native-edot-java', + }, + { + agentName: 'opentelemetry/nodejs/nodejs-agent/elastic', + telemetrySdkName: 'opentelemetry', + telemetrySdkLanguage: 'nodejs', + filename: 'otel_native-edot-nodejs', + }, +]; +const vanillaOtelSdk = [ + { + agentName: 'opentelemetry/java', + filename: 'classic_apm-otel_other-java', + }, + { + agentName: 'opentelemetry/nodejs/test/nodejs-agent', + telemetrySdkName: 'opentelemetry', + telemetrySdkLanguage: 'nodejs', + filename: 'otel_native-otel_other-nodejs', + }, + { + agentName: 'opentelemetry/java/test/something-else/', + telemetrySdkName: 'opentelemetry', + telemetrySdkLanguage: 'java', + filename: 'otel_native-otel_other-java', + }, + { + agentName: 'otlp/nodejs', + telemetrySdkName: 'opentelemetry', + telemetrySdkLanguage: 'nodejs', + filename: 'otel_native-otel_other-nodejs', + }, + { + agentName: 'otlp/Android', + telemetrySdkName: 'opentelemetry', + telemetrySdkLanguage: 'android', + filename: 'otel_native-otel_other-android', + }, +]; +const noFilenameCases = [ + { + agentName: 'test/java/test/something-else/', + telemetrySdkName: undefined, + telemetrySdkLanguage: undefined, + filename: undefined, + }, + { + agentName: 'otlp', + filename: undefined, + }, + { + agentName: 'elastic', + filename: undefined, + }, + { + agentName: 'my-awesome-agent/otel', + telemetrySdkName: 'opentelemetry', + filename: undefined, + }, +]; + +describe('getDashboardFileName', () => { + describe('apmAgent', () => { + it.each(apmAgent)( + 'for the agent name $agentName and open telemetry sdk name: $telemetrySdkName returns $filename', + ({ agentName, telemetrySdkName, telemetrySdkLanguage, filename }) => { + expect( + getDashboardFileName({ agentName, telemetrySdkName, telemetrySdkLanguage }) + ).toStrictEqual(filename); + } + ); + }); + describe('vanillaOtelSdk', () => { + it.each(vanillaOtelSdk)( + 'for the agent name $agentName and open telemetry sdk name: $telemetrySdkName and language $telemetrySdkLanguage returns $filename', + ({ agentName, telemetrySdkName, telemetrySdkLanguage, filename }) => { + expect( + getDashboardFileName({ agentName, telemetrySdkName, telemetrySdkLanguage }) + ).toStrictEqual(filename); + } + ); + }); + describe('edotSdk', () => { + it.each(edotSdk)( + 'for the agent name $agentName and open telemetry sdk name: $telemetrySdkName and language $telemetrySdkLanguage returns $filename', + ({ agentName, telemetrySdkName, telemetrySdkLanguage, filename }) => { + expect( + getDashboardFileName({ agentName, telemetrySdkName, telemetrySdkLanguage }) + ).toStrictEqual(filename); + } + ); + }); + describe('noFilenameCases', () => { + it.each(noFilenameCases)( + 'for the agent name $agentName and open telemetry sdk name: $telemetrySdkName and language $telemetrySdkLanguage returns $filename', + ({ agentName, telemetrySdkName, telemetrySdkLanguage, filename }) => { + expect( + getDashboardFileName({ agentName, telemetrySdkName, telemetrySdkLanguage }) + ).toStrictEqual(filename); + } + ); + }); +}); diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/dashboards/get_dashboard_file_name.ts b/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/dashboards/get_dashboard_file_name.ts new file mode 100644 index 0000000000000..30debf725fe88 --- /dev/null +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/dashboards/get_dashboard_file_name.ts @@ -0,0 +1,33 @@ +/* + * 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 { getSdkNameAndLanguage, getIngestionPath } from '@kbn/elastic-agent-utils'; + +interface DashboardFileNamePartsProps { + agentName: string; + telemetrySdkName?: string; + telemetrySdkLanguage?: string; +} + +// We use the language name in the filename so we want to have a valid filename +// Example swift/iOS -> swift_ios : lowercased and '/' is replaces by '_' +const standardizeLanguageName = (languageName?: string) => + languageName ? languageName.toLowerCase().replace('/', '_') : undefined; + +export const getDashboardFileName = ({ + agentName, + telemetrySdkName, + telemetrySdkLanguage, +}: DashboardFileNamePartsProps): string | undefined => { + const dataFormat = getIngestionPath(!!(telemetrySdkName ?? telemetrySdkLanguage)); + const { sdkName, language } = getSdkNameAndLanguage(agentName); + const sdkLanguage = standardizeLanguageName(language); + if (!sdkName || !sdkLanguage) { + return undefined; + } + return `${dataFormat}-${sdkName}-${sdkLanguage}`; +}; diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/helper.ts b/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/helper.ts index 2674034cd3d7e..a2b92f2b6a6ff 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/helper.ts +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/metrics/static_dashboard/helper.ts @@ -7,28 +7,33 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import type { DashboardPanelMap } from '@kbn/dashboard-plugin/common'; -import { - AGENT_NAME_DASHBOARD_FILE_MAPPING, - loadDashboardFile, -} from './dashboards/dashboard_catalog'; - +import { existingDashboardFileNames, loadDashboardFile } from './dashboards/dashboard_catalog'; +import { getDashboardFileName } from './dashboards/get_dashboard_file_name'; interface DashboardFileProps { agentName?: string; runtimeName?: string; serverlessType?: string; + telemetrySdkName?: string; + telemetrySdkLanguage?: string; } export interface MetricsDashboardProps extends DashboardFileProps { dataView: DataView; } -export function hasDashboardFile(props: DashboardFileProps) { - return !!getDashboardFileName(props); +function getDashboardFileNameFromProps({ + agentName, + telemetrySdkName, + telemetrySdkLanguage, +}: DashboardFileProps) { + const dashboardFile = + agentName && getDashboardFileName({ agentName, telemetrySdkName, telemetrySdkLanguage }); + return dashboardFile; } -function getDashboardFileName({ agentName }: DashboardFileProps) { - const dashboardFile = agentName && AGENT_NAME_DASHBOARD_FILE_MAPPING[agentName]; - return dashboardFile; +export function hasDashboard(props: DashboardFileProps) { + const dashboardFilename = getDashboardFileNameFromProps(props); + return !!dashboardFilename && existingDashboardFileNames.has(dashboardFilename); } const getAdhocDataView = (dataView: DataView) => { @@ -43,10 +48,8 @@ export async function convertSavedDashboardToPanels( props: MetricsDashboardProps, dataView: DataView ): Promise { - const dashboardFilename = getDashboardFileName(props); - const dashboardJSON = !!dashboardFilename - ? await loadDashboardFile(dashboardFilename) - : undefined; + const dashboardFilename = getDashboardFileNameFromProps(props); + const dashboardJSON = !!dashboardFilename ? await loadDashboardFile(dashboardFilename) : false; if (!dashboardFilename || !dashboardJSON) { return undefined; diff --git a/x-pack/solutions/observability/plugins/apm/public/context/apm_service/apm_service_context.tsx b/x-pack/solutions/observability/plugins/apm/public/context/apm_service/apm_service_context.tsx index e0c09c5756a4e..3f4ae1a2fa1d2 100644 --- a/x-pack/solutions/observability/plugins/apm/public/context/apm_service/apm_service_context.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/context/apm_service/apm_service_context.tsx @@ -28,6 +28,8 @@ import { export interface APMServiceContextValue { serviceName: string; agentName?: string; + telemetrySdkName?: string; + telemetrySdkLanguage?: string; serverlessType?: ServerlessType; transactionType?: string; transactionTypeStatus: FETCH_STATUS; @@ -63,6 +65,8 @@ export function ApmServiceContextProvider({ children }: { children: ReactNode }) agentName, runtimeName, serverlessType, + telemetrySdkName, + telemetrySdkLanguage, status: serviceAgentStatus, } = useServiceAgentFetcher({ serviceName, @@ -108,6 +112,8 @@ export function ApmServiceContextProvider({ children }: { children: ReactNode }) serviceName, agentName, serverlessType, + telemetrySdkName, + telemetrySdkLanguage, transactionType: currentTransactionType, transactionTypeStatus, transactionTypes, diff --git a/x-pack/solutions/observability/plugins/apm/public/context/apm_service/use_service_agent_fetcher.ts b/x-pack/solutions/observability/plugins/apm/public/context/apm_service/use_service_agent_fetcher.ts index a7d22e24e1a72..909ba6b36bdaa 100644 --- a/x-pack/solutions/observability/plugins/apm/public/context/apm_service/use_service_agent_fetcher.ts +++ b/x-pack/solutions/observability/plugins/apm/public/context/apm_service/use_service_agent_fetcher.ts @@ -11,6 +11,8 @@ const INITIAL_STATE = { agentName: undefined, runtimeName: undefined, serverlessType: undefined, + telemetrySdkName: undefined, + telemetrySdkLanguage: undefined, }; export function useServiceAgentFetcher({ diff --git a/x-pack/solutions/observability/plugins/apm/server/routes/services/get_service_agent.ts b/x-pack/solutions/observability/plugins/apm/server/routes/services/get_service_agent.ts index 72c15d2ca66ae..75503530f969f 100644 --- a/x-pack/solutions/observability/plugins/apm/server/routes/services/get_service_agent.ts +++ b/x-pack/solutions/observability/plugins/apm/server/routes/services/get_service_agent.ts @@ -15,6 +15,8 @@ import { SERVICE_RUNTIME_NAME, CLOUD_PROVIDER, CLOUD_SERVICE_NAME, + TELEMETRY_SDK_NAME, + TELEMETRY_SDK_LANGUAGE, } from '../../../common/es_fields/apm'; import type { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; import type { ServerlessType } from '../../../common/serverless'; @@ -24,6 +26,8 @@ import { maybe } from '../../../common/utils/maybe'; export interface ServiceAgentResponse { agentName?: string; runtimeName?: string; + telemetrySdkName?: string; + telemetrySdkLanguage?: string; serverlessType?: ServerlessType; } @@ -40,6 +44,8 @@ export async function getServiceAgent({ }): Promise { const fields = asMutableArray([ AGENT_NAME, + TELEMETRY_SDK_NAME, + TELEMETRY_SDK_LANGUAGE, SERVICE_RUNTIME_NAME, CLOUD_PROVIDER, CLOUD_SERVICE_NAME, @@ -48,7 +54,12 @@ export async function getServiceAgent({ const params = { terminate_after: 1, apm: { - events: [ProcessorEvent.error, ProcessorEvent.transaction, ProcessorEvent.metric], + events: [ + ProcessorEvent.span, + ProcessorEvent.error, + ProcessorEvent.transaction, + ProcessorEvent.metric, + ], }, body: { track_total_hits: 1, @@ -99,11 +110,13 @@ export async function getServiceAgent({ const event = unflattenKnownApmEventFields(hit.fields); - const { agent, service, cloud } = event; + const { agent, service, cloud, telemetry } = event; const serverlessType = getServerlessTypeFromCloudData(cloud?.provider, cloud?.service?.name); return { agentName: agent?.name, + telemetrySdkName: telemetry?.sdk?.name, + telemetrySdkLanguage: telemetry?.sdk?.language, runtimeName: service?.runtime?.name, serverlessType, }; diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/services/agent.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/services/agent.spec.ts index 8d96b00867e80..22953fced1fee 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/services/agent.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/services/agent.spec.ts @@ -58,7 +58,10 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon expect(response.status).to.be(200); - expect(response.body).to.eql({ agentName: 'nodejs', runtimeName: 'node' }); + expect(response.body).to.eql({ + agentName: 'nodejs', + runtimeName: 'node', + }); }); }); });