Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[ProfilingxAPM] Link APM from Profiling UI #180677

Merged
merged 23 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/management/advanced-options.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,9 @@ If you're enrolled in the AWS Enterprise Discount Program (EDP), enter your disc
[[observability-profiling-azure-cost-discount-rate]]`observability:profilingAzureCostDiscountRate`::
If you have an Azure Enterprise Agreement with Microsoft, enter your discount rate to update the profiling cost calculation.

[[observability-profiling-use-topNFunctions-from-stacktraces]]`observability:profilingFetchTopNFunctionsFromStacktraces`::
Switch to fetch the TopN Functions from the Stacktraces API.

[[observability-profiling-cost-per-vcpu-per-hour]]`observability:profilingCostPervCPUPerHour`::
Default Hourly Cost per CPU Core for machines not on AWS or Azure.

Expand Down
2 changes: 2 additions & 0 deletions packages/kbn-profiling-utils/common/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type TopNFunction = Pick<
> & {
Id: string;
Rank: number;
subGroups: Record<string, number>;
};

export interface TopNFunctions {
Expand Down Expand Up @@ -207,6 +208,7 @@ export function createTopNFunctions({
selfAnnualCostUSD: frameAndCount.selfAnnualCostUSD,
totalAnnualCO2kgs: frameAndCount.totalAnnualCO2kgs,
totalAnnualCostUSD: frameAndCount.totalAnnualCostUSD,
subGroups: {},
};
});

Expand Down
40 changes: 40 additions & 0 deletions packages/kbn-profiling-utils/common/topN_functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export interface Frame {
frame_type: number;
inline: boolean;
address_or_line: number;
function_name: string;
file_name: string;
line_number: number;
executable_file_name: string;
}

interface TopNFunction {
id: string;
rank: number;
frame: Frame;
sub_groups: Record<string, number>;
self_count: number;
total_count: number;
self_annual_co2_tons: number;
total_annual_co2_tons: number;
self_annual_costs_usd: number;
total_annual_costs_usd: number;
}

export interface ESTopNFunctions {
self_count: number;
total_count: number;
self_annual_co2_tons: number;
self_annual_cost_usd: number;
topn: TopNFunction[];
}

export type AggregationField = 'service.name' | 'transaction.name';
1 change: 1 addition & 0 deletions packages/kbn-profiling-utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ export type {
} from './common/profiling';
export type { ProfilingStatus } from './common/profiling_status';
export type { TopNFunctions } from './common/functions';
export type { AggregationField, ESTopNFunctions } from './common/topN_functions';
Original file line number Diff line number Diff line change
Expand Up @@ -659,4 +659,8 @@ export const stackManagementSchema: MakeSchemaFrom<UsageStats> = {
type: 'keyword',
_meta: { description: 'Non-default value of setting.' },
},
'observability:profilingFetchTopNFunctionsFromStacktraces': {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,5 @@ export interface UsageStats {
'observability:apmEnableTransactionProfiling': boolean;
'devTools:enablePersistentConsole': boolean;
'aiAssistant:preferredAIAssistantType': string;
'observability:profilingFetchTopNFunctionsFromStacktraces': boolean;
}
10 changes: 8 additions & 2 deletions src/plugins/telemetry/schema/oss_plugins.json
Original file line number Diff line number Diff line change
Expand Up @@ -10433,7 +10433,13 @@
"_meta": {
"description": "Non-default value of setting."
}
}
},
"observability:profilingFetchTopNFunctionsFromStacktraces": {
"type": "boolean",
"_meta": {
"description": "Non-default value of setting."
}
},
}
},
"static_telemetry": {
Expand Down Expand Up @@ -11869,4 +11875,4 @@
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* 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 { EuiEmptyPrompt } from '@elastic/eui';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { Redirect } from 'react-router-dom';
import { css } from '@emotion/react';
import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
import { getRedirectToTransactionDetailPageUrl } from '../trace_link/get_redirect_to_transaction_detail_page_url';
import { useApmParams } from '../../../hooks/use_apm_params';
import { useTimeRange } from '../../../hooks/use_time_range';

export function TransactionDetailsByNameLink() {
const {
query: { rangeFrom, rangeTo, transactionName, serviceName },
} = useApmParams('/link-to/transaction');

const { start, end } = useTimeRange({
rangeFrom: rangeFrom || new Date(0).toISOString(),
cauemarcondes marked this conversation as resolved.
Show resolved Hide resolved
rangeTo: rangeTo || new Date().toISOString(),
});

const { data = { transaction: null }, status } = useFetcher(
(callApmApi) => {
return callApmApi('GET /internal/apm/transactions', {
params: {
query: {
start,
end,
transactionName,
serviceName,
},
},
});
},
[start, end, transactionName, serviceName]
);

if (status === FETCH_STATUS.SUCCESS) {
if (data.transaction) {
return (
<Redirect
to={getRedirectToTransactionDetailPageUrl({
transaction: data.transaction,
rangeFrom,
rangeTo,
})}
/>
);
}

return null;
}

return (
<div
css={css`
height: 100%;
display: flex;
`}
>
<EuiEmptyPrompt
iconType="apmTrace"
title={
<h2>
{i18n.translate(
'xpack.apm.transactionDetailsLink.h2.fetchingTransactionLabel',
{ defaultMessage: 'Fetching transaction...' }
)}
</h2>
}
/>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { ApmMainTemplate } from './templates/apm_main_template';
import { ServiceGroupsList } from '../app/service_groups';
import { offsetRt } from '../../../common/comparison_rt';
import { diagnosticsRoute } from '../app/diagnostics';
import { TransactionDetailsByNameLink } from '../app/transaction_details_link';

const ServiceGroupsTitle = i18n.translate(
'xpack.apm.views.serviceGroups.title',
Expand All @@ -34,6 +35,18 @@ const ServiceGroupsTitle = i18n.translate(
* creates the routes.
*/
const apmRoutes = {
'/link-to/transaction': {
element: <TransactionDetailsByNameLink />,
params: t.type({
query: t.intersection([
t.type({ transactionName: t.string, serviceName: t.string }),
t.partial({
rangeFrom: t.string,
rangeTo: t.string,
}),
]),
}),
},
'/link-to/transaction/{transactionId}': {
element: <TransactionLink />,
params: t.intersection([
Expand Down Expand Up @@ -69,7 +82,12 @@ const apmRoutes = {
},
'/': {
element: (
<Breadcrumb title="APM" href="/">
<Breadcrumb
title={i18n.translate('xpack.apm..breadcrumb.apmLabel', {
defaultMessage: 'APM',
})}
href="/"
>
<Outlet />
</Breadcrumb>
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
import { getSpan } from '../transactions/get_span';
import { Transaction } from '../../../typings/es_schemas/ui/transaction';
import { Span } from '../../../typings/es_schemas/ui/span';
import { getTransactionByName } from '../transactions/get_transaction_by_name';

const tracesRoute = createApmServerRoute({
endpoint: 'GET /internal/apm/traces',
Expand Down Expand Up @@ -186,6 +187,42 @@ const transactionByIdRoute = createApmServerRoute({
},
});

const transactionByNameRoute = createApmServerRoute({
endpoint: 'GET /internal/apm/transactions',
params: t.type({
query: t.intersection([
rangeRt,
t.type({
transactionName: t.string,
serviceName: t.string,
}),
]),
}),
options: { tags: ['access:apm'] },
handler: async (
resources
): Promise<{
transaction: Transaction;
}> => {
const {
params: {
query: { start, end, transactionName, serviceName },
},
} = resources;

const apmEventClient = await getApmEventClient(resources);
return {
transaction: await getTransactionByName({
transactionName,
apmEventClient,
start,
end,
serviceName,
}),
};
},
});

const findTracesRoute = createApmServerRoute({
endpoint: 'GET /internal/apm/traces/find',
params: t.type({
Expand Down Expand Up @@ -338,4 +375,5 @@ export const traceRouteRepository = {
...aggregatedCriticalPathRoute,
...transactionFromTraceByIdRoute,
...spanFromTraceByIdRoute,
...transactionByNameRoute,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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 { rangeQuery } from '@kbn/observability-plugin/server';
import { ApmDocumentType } from '../../../../common/document_type';
import {
SERVICE_NAME,
TRANSACTION_NAME,
} from '../../../../common/es_fields/apm';
import { RollupInterval } from '../../../../common/rollup';
import { asMutableArray } from '../../../../common/utils/as_mutable_array';
import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client';

export async function getTransactionByName({
transactionName,
serviceName,
apmEventClient,
start,
end,
}: {
transactionName: string;
serviceName: string;
apmEventClient: APMEventClient;
start: number;
end: number;
}) {
const resp = await apmEventClient.search('get_transaction', {
apm: {
sources: [
{
documentType: ApmDocumentType.TransactionEvent,
rollupInterval: RollupInterval.None,
},
],
},
body: {
track_total_hits: false,
size: 1,
terminate_after: 1,
query: {
bool: {
filter: asMutableArray([
{ term: { [TRANSACTION_NAME]: transactionName } },
{ term: { [SERVICE_NAME]: serviceName } },
...rangeQuery(start, end),
]),
},
},
},
});

return resp.hits.hits[0]?._source;
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export {
profilingAzureCostDiscountRate,
apmEnableTransactionProfiling,
apmEnableServiceInventoryTableSearchBar,
profilingFetchTopNFunctionsFromStacktraces,
} from './ui_settings_keys';

export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@ export const profilingAWSCostDiscountRate = 'observability:profilingAWSCostDisco
export const profilingCostPervCPUPerHour = 'observability:profilingCostPervCPUPerHour';
export const profilingAzureCostDiscountRate = 'observability:profilingAzureCostDiscountRate';
export const apmEnableTransactionProfiling = 'observability:apmEnableTransactionProfiling';
export const profilingFetchTopNFunctionsFromStacktraces =
'observability:profilingFetchTopNFunctionsFromStacktraces';
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
apmEnableTransactionProfiling,
enableInfrastructureAssetCustomDashboards,
apmEnableServiceInventoryTableSearchBar,
profilingFetchTopNFunctionsFromStacktraces,
} from '../common/ui_settings_keys';

const betaLabel = i18n.translate('xpack.observability.uiSettings.betaLabel', {
Expand Down Expand Up @@ -600,6 +601,21 @@ export const uiSettings: Record<string, UiSettings> = {
schema: schema.boolean(),
requiresPageReload: true,
},
[profilingFetchTopNFunctionsFromStacktraces]: {
category: [observabilityFeatureId],
name: i18n.translate('xpack.observability.profilingFetchTopNFunctionsFromStacktraces', {
defaultMessage: 'Switch to fetch the TopN Functions from the Stacktraces API.',
}),
description: i18n.translate(
'xpack.observability.profilingFetchTopNFunctionsFromStacktracesDescription',
{
defaultMessage: `The topN functions pages use the topN/functions API, turn it on to switch to the stacktraces api`,
}
),
value: false,
schema: schema.boolean(),
requiresPageReload: false,
},
};

function throttlingDocsLink({ href }: { href: string }) {
Expand Down
Loading