mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[8.18] [ObsUX] [APM] [OTel] Runtime metrics show dashboards with different ingest path (#211822) (#213534)
# Backport This will backport the following commits from `main` to `8.18`: - [[ObsUX] [APM] [OTel] Runtime metrics show dashboards with different ingest path (#211822)](https://github.com/elastic/kibana/pull/211822) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport)
This commit is contained in:
parent
a4f203f821
commit
836a2536c1
16 changed files with 587 additions and 60 deletions
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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') ||
|
||||
|
|
|
@ -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';
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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 };
|
||||
};
|
|
@ -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<CoreStart>);
|
||||
|
||||
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 (
|
||||
<KibanaReactContext.Provider>
|
||||
<MockApmPluginContextWrapper
|
||||
history={history}
|
||||
value={mockApmPluginContextValue as unknown as ApmPluginContextValue}
|
||||
>
|
||||
<Metrics />
|
||||
</MockApmPluginContextWrapper>
|
||||
</KibanaReactContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
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(<MetricsWithWrapper />);
|
||||
// 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(<MetricsWithWrapper />);
|
||||
// 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(<MetricsWithWrapper />);
|
||||
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(<MetricsWithWrapper />);
|
||||
const apmAddApmCallout = result.getByTestId('apmAddApmCallout');
|
||||
expect(apmAddApmCallout).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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 <ServerlessMetrics />;
|
||||
}
|
||||
|
||||
const hasStaticDashboard = hasDashboardFile({
|
||||
agentName,
|
||||
runtimeName,
|
||||
serverlessType,
|
||||
});
|
||||
if (!hasDashboardFile && !isElasticAgentName(agentName ?? '')) {
|
||||
return (
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.apm.metrics.emptyState.title', {
|
||||
defaultMessage: 'Runtime metrics are not available for this Agent / SDK type.',
|
||||
})}
|
||||
iconType="iInCircle"
|
||||
data-test-subj="apmMetricsNoDashboardFound"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (hasStaticDashboard && dataView) {
|
||||
if (hasDashboardFile && dataView) {
|
||||
return (
|
||||
<JsonMetricsDashboard
|
||||
agentName={agentName}
|
||||
|
@ -55,7 +67,7 @@ export function Metrics() {
|
|||
);
|
||||
}
|
||||
|
||||
if (!isAWSLambda && (isJavaAgentName(agentName) || isJRubyAgentName(agentName, runtimeName))) {
|
||||
if (!isAWSLambda && isJRubyAgentName(agentName, runtimeName)) {
|
||||
return <JvmMetricsOverview />;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,52 +5,69 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export const AGENT_NAME_DASHBOARD_FILE_MAPPING: Record<string, string> = {
|
||||
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<any> {
|
||||
// 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: "lazyOtelDotnetDashboard" */
|
||||
/* 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: "lazyDotnetApmOtelDashboard" */
|
||||
'./opentelemetry_dotnet.json'
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
|
@ -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}`;
|
||||
};
|
|
@ -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<DashboardPanelMap | undefined> {
|
||||
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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -11,6 +11,8 @@ const INITIAL_STATE = {
|
|||
agentName: undefined,
|
||||
runtimeName: undefined,
|
||||
serverlessType: undefined,
|
||||
telemetrySdkName: undefined,
|
||||
telemetrySdkLanguage: undefined,
|
||||
};
|
||||
|
||||
export function useServiceAgentFetcher({
|
||||
|
|
|
@ -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<ServiceAgentResponse> {
|
||||
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,
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue