[APM] Add AWS Lambda metrics to "Metrics" tab UI (#140550)

* [APM] AWS lambda metrics api

* fixing typo

* fixing duration

* fixing

* tests

* addressing pr comments

* yformater time

* tests

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* addressing pr comments

* addressing PR comments

* fixing compute usage

* fixing ci

* fixing ci

* addressing PR comments

* fixing synthtrace

* refactoring

* showing metrics tab

* synthtrace

* addressing pr comments

* adding beta badge

* fixing tests

* fixing active instaces query

* addressing PR comments

* fixing calc

* fixing

* removing merge conflic

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

* addressing PR changes

* remiving unused import

* adding feature flag

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Cauê Marcondes 2022-09-20 20:17:23 -04:00 committed by GitHub
parent 9fa1f04725
commit 99e367446b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 172 additions and 64 deletions

View file

@ -522,6 +522,10 @@ export const stackManagementSchema: MakeSchemaFrom<UsageStats> = {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
},
'observability:enableAwsLambdaMetrics': {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
},
'observability:apmProgressiveLoading': {
type: 'keyword',
_meta: { description: 'Non-default value of setting.' },

View file

@ -141,6 +141,7 @@ export interface UsageStats {
'metrics:allowCheckingForFailedShards': boolean;
'observability:apmOperationsTab': boolean;
'observability:apmLabsButton': boolean;
'observability:enableAwsLambdaMetrics': boolean;
'observability:apmProgressiveLoading': string;
'observability:apmServiceGroupMaxNumberOfServices': number;
'observability:apmServiceInventoryOptimizedSorting': boolean;

View file

@ -8828,6 +8828,12 @@
"description": "Non-default value of setting."
}
},
"observability:enableAwsLambdaMetrics": {
"type": "boolean",
"_meta": {
"description": "Non-default value of setting."
}
},
"observability:apmProgressiveLoading": {
"type": "keyword",
"_meta": {
@ -10317,4 +10323,4 @@
}
}
}
}
}

View file

@ -14,10 +14,11 @@ import {
import { i18n } from '@kbn/i18n';
import { omit } from 'lodash';
import React from 'react';
import { enableAwsLambdaMetrics } from '@kbn/observability-plugin/common';
import {
isMobileAgentName,
isJavaAgentName,
isJRubyAgent,
isMobileAgentName,
isRumAgentName,
isServerlessAgent,
} from '../../../../../common/agent_name';
@ -29,13 +30,13 @@ import { ServiceAnomalyTimeseriesContextProvider } from '../../../../context/ser
import { useApmParams } from '../../../../hooks/use_apm_params';
import { useApmRouter } from '../../../../hooks/use_apm_router';
import { useTimeRange } from '../../../../hooks/use_time_range';
import { SearchBar } from '../../../shared/search_bar';
import { ServiceIcons } from '../../../shared/service_icons';
import { ApmMainTemplate } from '../apm_main_template';
import { AnalyzeDataButton } from './analyze_data_button';
import { getAlertingCapabilities } from '../../../alerting/get_alerting_capabilities';
import { BetaBadge } from '../../../shared/beta_badge';
import { SearchBar } from '../../../shared/search_bar';
import { ServiceIcons } from '../../../shared/service_icons';
import { TechnicalPreviewBadge } from '../../../shared/technical_preview_badge';
import { ApmMainTemplate } from '../apm_main_template';
import { AnalyzeDataButton } from './analyze_data_button';
type Tab = NonNullable<EuiPageHeaderProps['tabs']>[0] & {
key:
@ -139,17 +140,21 @@ function TemplateWithContext({
export function isMetricsTabHidden({
agentName,
runtimeName,
isAwsLambdaEnabled,
}: {
agentName?: string;
runtimeName?: string;
isAwsLambdaEnabled?: boolean;
}) {
if (isServerlessAgent(runtimeName)) {
return !isAwsLambdaEnabled;
}
return (
!agentName ||
isRumAgentName(agentName) ||
isJavaAgentName(agentName) ||
isMobileAgentName(agentName) ||
isJRubyAgent(agentName, runtimeName) ||
isServerlessAgent(runtimeName)
isJRubyAgent(agentName, runtimeName)
);
}
@ -192,6 +197,11 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) {
const router = useApmRouter();
const isAwsLambdaEnabled = core.uiSettings.get<boolean>(
enableAwsLambdaMetrics,
true
);
const {
path: { serviceName },
query: queryFromUrl,
@ -257,7 +267,14 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) {
label: i18n.translate('xpack.apm.serviceDetails.metricsTabLabel', {
defaultMessage: 'Metrics',
}),
hidden: isMetricsTabHidden({ agentName, runtimeName }),
append: isServerlessAgent(runtimeName) ? (
<TechnicalPreviewBadge icon="beaker" />
) : undefined,
hidden: isMetricsTabHidden({
agentName,
runtimeName,
isAwsLambdaEnabled,
}),
},
{
key: 'nodes',

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { EuiTitle } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiIconTip, EuiTitle } from '@elastic/eui';
import React from 'react';
import { APIReturnType } from '../../../../services/rest/create_call_apm_api';
import {
@ -60,9 +60,22 @@ interface Props {
export function MetricsChart({ chart, fetchStatus }: Props) {
return (
<>
<EuiTitle size="xs">
<span>{chart.title}</span>
</EuiTitle>
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<span>{chart.title}</span>
</EuiTitle>
</EuiFlexItem>
{chart.description && (
<EuiFlexItem grow={false}>
<EuiIconTip
content={chart.description}
position="top"
type="questionInCircle"
/>
</EuiFlexItem>
)}
</EuiFlexGroup>
<TimeseriesChart
fetchStatus={fetchStatus}
id={chart.key}

View file

@ -9,6 +9,7 @@ import {
AnnotationDomainType,
AreaSeries,
Axis,
BarSeries,
Chart,
CurveType,
LegendItemListener,
@ -171,6 +172,17 @@ export function TimeseriesChart({
opacity: theme.darkMode ? 0.6 : 0.2,
};
function getChartType(type: string) {
switch (type) {
case 'area':
return AreaSeries;
case 'bar':
return BarSeries;
default:
return LineSeries;
}
}
return (
<ChartContainer
hasData={!isEmpty}
@ -281,7 +293,7 @@ export function TimeseriesChart({
/>
{allSeries.map((serie) => {
const Series = serie.type === 'area' ? AreaSeries : LineSeries;
const Series = getChartType(serie.type);
return (
<Series

View file

@ -32,7 +32,7 @@ export function useServiceMetricChartsFetcher({
} = useApmParams('/services/{serviceName}');
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
const { agentName, serviceName } = useApmServiceContext();
const { agentName, serviceName, runtimeName } = useApmServiceContext();
const {
data = INITIAL_DATA,
@ -53,13 +53,23 @@ export function useServiceMetricChartsFetcher({
start,
end,
agentName,
serviceRuntimeName: runtimeName,
},
},
}
);
}
},
[environment, kuery, serviceName, start, end, agentName, serviceNodeName]
[
environment,
kuery,
serviceName,
start,
end,
agentName,
serviceNodeName,
runtimeName,
]
);
return {

View file

@ -13,7 +13,6 @@ import {
SERVICE_NODE_NAME,
} from '../../../../../common/elasticsearch_fieldnames';
import { environmentQuery } from '../../../../../common/utils/environment_query';
import { getVizColorForIndex } from '../../../../../common/viz_colors';
import { getMetricsDateHistogramParams } from '../../../../lib/helpers/metrics';
import { Setup } from '../../../../lib/helpers/setup_request';
import {
@ -92,7 +91,14 @@ export async function getActiveInstances({
defaultMessage: 'Active instances',
}),
key: 'active_instances',
yUnit: 'number',
yUnit: 'integer',
description: i18n.translate(
'xpack.apm.agentMetrics.serverless.activeInstances.description',
{
defaultMessage:
'This chart shows the number of active instances of your serverless function over time. Multiple active instances may be a result of provisioned concurrency for your function or an increase in concurrent load that scales your function on-demand. An increase in active instance can be an indicator for an increase in concurrent invocations.',
}
),
series: [
{
title: i18n.translate(
@ -100,8 +106,8 @@ export async function getActiveInstances({
{ defaultMessage: 'Active instances' }
),
key: 'active_instances',
type: 'linemark',
color: getVizColorForIndex(0, theme),
type: 'bar',
color: theme.euiColorVis1,
overallValue: aggregations?.activeInstances.value ?? 0,
data:
aggregations?.timeseriesData.buckets.map((timeseriesBucket) => ({

View file

@ -7,6 +7,7 @@
import { i18n } from '@kbn/i18n';
import { termQuery } from '@kbn/observability-plugin/server';
import { euiLightVars as theme } from '@kbn/ui-theme';
import {
FAAS_COLDSTART,
METRICSET_NAME,
@ -20,13 +21,14 @@ const chartBase: ChartBase = {
defaultMessage: 'Cold start',
}),
key: 'cold_start_count',
type: 'linemark',
yUnit: 'number',
type: 'bar',
yUnit: 'integer',
series: {
coldStart: {
title: i18n.translate('xpack.apm.agentMetrics.serverless.coldStart', {
defaultMessage: 'Cold start',
}),
color: theme.euiColorVis5,
},
},
};

View file

@ -6,6 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
import { euiLightVars as theme } from '@kbn/ui-theme';
import { FAAS_COLDSTART_DURATION } from '../../../../../common/elasticsearch_fieldnames';
import { Setup } from '../../../../lib/helpers/setup_request';
import { fetchAndTransformMetrics } from '../../fetch_and_transform_metrics';
@ -24,8 +25,16 @@ const chartBase: ChartBase = {
'xpack.apm.agentMetrics.serverless.coldStartDuration',
{ defaultMessage: 'Cold start duration' }
),
color: theme.euiColorVis5,
},
},
description: i18n.translate(
'xpack.apm.agentMetrics.serverless.coldStartDuration.description',
{
defaultMessage:
'Cold start duration shows the execution duration of the serverless runtime for requests that experience cold starts.',
}
),
};
export function getColdStartDuration({

View file

@ -21,27 +21,9 @@ import {
} from '../../../../../common/elasticsearch_fieldnames';
import { environmentQuery } from '../../../../../common/utils/environment_query';
import { isFiniteNumber } from '../../../../../common/utils/is_finite_number';
import { getVizColorForIndex } from '../../../../../common/viz_colors';
import { getMetricsDateHistogramParams } from '../../../../lib/helpers/metrics';
import { Setup } from '../../../../lib/helpers/setup_request';
import { GenericMetricsChart } from '../../fetch_and_transform_metrics';
import { ChartBase } from '../../types';
const chartBase: ChartBase = {
title: i18n.translate('xpack.apm.agentMetrics.serverless.computeUsage', {
defaultMessage: 'Compute usage',
}),
key: 'compute_usage',
type: 'linemark',
yUnit: 'number',
series: {
computeUsage: {
title: i18n.translate('xpack.apm.agentMetrics.serverless.computeUsage', {
defaultMessage: 'Compute usage',
}),
},
},
};
/**
* To calculate the compute usage we need to multiply the "system.memory.total" by "faas.billed_duration".
@ -126,9 +108,18 @@ export async function getComputeUsage({
const timeseriesData = aggregations?.timeseriesData;
return {
title: chartBase.title,
key: chartBase.key,
yUnit: chartBase.yUnit,
title: i18n.translate('xpack.apm.agentMetrics.serverless.computeUsage', {
defaultMessage: 'Compute usage',
}),
key: 'compute_usage',
yUnit: 'number',
description: i18n.translate(
'xpack.apm.agentMetrics.serverless.computeUsage.description',
{
defaultMessage:
"Compute usage (in GB-seconds) is the execution time multiplied by the available memory size of your function's instances. The compute usage is a direct indicator for the costs of your serverless function.",
}
),
series:
!timeseriesData || timeseriesData.buckets.length === 0
? []
@ -139,12 +130,12 @@ export async function getComputeUsage({
{ defaultMessage: 'Compute usage' }
),
key: 'compute_usage',
type: 'linemark',
type: 'bar',
overallValue: calculateComputeUsageGBSeconds({
faasBilledDuration: aggregations?.avgFaasBilledDuration.value,
totalMemory: aggregations?.avgTotalMemory.value,
}),
color: getVizColorForIndex(0, theme),
color: theme.euiColorVis0,
data: timeseriesData.buckets.map((bucket) => {
const computeUsage = calculateComputeUsageGBSeconds({
faasBilledDuration: bucket.avgFaasBilledDuration.value,

View file

@ -51,9 +51,9 @@ export function getServerlessAgentMetricCharts({
...options,
searchAggregatedTransactions,
}),
getMemoryChartData(options),
getColdStartDuration(options),
getColdStartCount(options),
getMemoryChartData(options),
getComputeUsage(options),
getActiveInstances({ ...options, searchAggregatedTransactions }),
]);

View file

@ -32,6 +32,13 @@ const chartBase: ChartBase = {
type: 'linemark',
yUnit: 'time',
series: {},
description: i18n.translate(
'xpack.apm.agentMetrics.serverless.avgDuration.description',
{
defaultMessage:
'Transaction duration is the time spent processing and responding to a request. If the request is queued it will not be contribute to the transaction duration but will contribute the overall billed duration',
}
),
};
async function getServerlessLantecySeries({

View file

@ -21,6 +21,7 @@ import {
serviceNodeNameQuery,
} from '../../../common/utils/environment_query';
import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames';
import { ChartType, Coordinate, YUnit } from '../../../typings/timeseries';
type MetricsAggregationMap = Unionize<{
min: AggregationOptionsByType['min'];
@ -42,9 +43,22 @@ export type GenericMetricsRequest = APMEventESSearchRequest & {
};
};
export type GenericMetricsChart = Awaited<
ReturnType<typeof fetchAndTransformMetrics>
>;
export type GenericMetricsChart = Awaited<FetchAndTransformMetrics>;
export interface FetchAndTransformMetrics {
title: string;
key: string;
yUnit: YUnit;
series: Array<{
title: string;
key: string;
type: ChartType;
color: string;
overallValue: number;
data: Coordinate[];
}>;
description?: string;
}
export async function fetchAndTransformMetrics<T extends MetricAggs>({
environment,
@ -70,7 +84,7 @@ export async function fetchAndTransformMetrics<T extends MetricAggs>({
aggs: T;
additionalFilters?: QueryDslQueryContainer[];
operationName: string;
}) {
}): Promise<FetchAndTransformMetrics> {
const { apmEventClient, config } = setup;
const params: GenericMetricsRequest = {
@ -115,6 +129,7 @@ export async function fetchAndTransformMetrics<T extends MetricAggs>({
title: chartBase.title,
key: chartBase.key,
yUnit: chartBase.yUnit,
description: chartBase.description,
series:
hits.total.value === 0
? []

View file

@ -9,6 +9,7 @@ import * as t from 'io-ts';
import { setupRequest } from '../../lib/helpers/setup_request';
import { createApmServerRoute } from '../apm_routes/create_apm_server_route';
import { environmentRt, kueryRt, rangeRt } from '../default_api_types';
import { FetchAndTransformMetrics } from './fetch_and_transform_metrics';
import { getMetricsChartDataByAgent } from './get_metrics_chart_data_by_agent';
const metricsChartsRoute = createApmServerRoute({
@ -34,19 +35,7 @@ const metricsChartsRoute = createApmServerRoute({
handler: async (
resources
): Promise<{
charts: Array<{
title: string;
key: string;
yUnit: import('./../../../typings/timeseries').YUnit;
series: Array<{
title: string;
key: string;
type: import('./../../../typings/timeseries').ChartType;
color: string;
overallValue: number;
data: Array<{ x: number; y: number | null }>;
}>;
}>;
charts: FetchAndTransformMetrics[];
}> => {
const { params } = resources;
const setup = await setupRequest(resources);

View file

@ -18,4 +18,5 @@ export interface ChartBase {
color?: string;
};
};
description?: string;
}

View file

@ -69,5 +69,5 @@ export interface APMChartSpec<
groupId?: string;
}
export type ChartType = 'area' | 'linemark';
export type ChartType = 'area' | 'linemark' | 'bar';
export type YUnit = 'percent' | 'bytes' | 'number' | 'time' | 'integer';

View file

@ -25,6 +25,7 @@ export {
apmOperationsTab,
apmLabsButton,
enableInfrastructureHostsView,
enableAwsLambdaMetrics,
} from './ui_settings_keys';
export {

View file

@ -20,3 +20,4 @@ export const apmTraceExplorerTab = 'observability:apmTraceExplorerTab';
export const apmOperationsTab = 'observability:apmOperationsTab';
export const apmLabsButton = 'observability:apmLabsButton';
export const enableInfrastructureHostsView = 'observability:enableInfrastructureHostsView';
export const enableAwsLambdaMetrics = 'observability:enableAwsLambdaMetrics';

View file

@ -23,6 +23,7 @@ import {
apmOperationsTab,
apmLabsButton,
enableInfrastructureHostsView,
enableAwsLambdaMetrics,
} from '../common/ui_settings_keys';
const technicalPreviewLabel = i18n.translate(
@ -254,4 +255,26 @@ export const uiSettings: Record<string, UiSettings> = {
}),
schema: schema.boolean(),
},
[enableAwsLambdaMetrics]: {
category: [observabilityFeatureId],
name: i18n.translate('xpack.observability.enableAwsLambdaMetrics', {
defaultMessage: 'AWS Lambda Metrics',
}),
description: i18n.translate('xpack.observability.enableAwsLambdaMetricsDescription', {
defaultMessage: 'Display Amazon Lambda metrics in the service metrics tab. {feedbackLink}',
values: {
feedbackLink:
'<a href="https://ela.st/feedback-aws-lambda" target="_blank" rel="noopener noreferrer">' +
i18n.translate('xpack.observability.awsLambdaDescription', {
defaultMessage: 'Send feedback',
}) +
'</a>',
},
}),
schema: schema.boolean(),
value: true,
requiresPageReload: true,
type: 'boolean',
showInLabs: true,
},
};