Skip to content

Commit 5d96f36

Browse files
[APM]Handle ELASTIC_PROFILER_STACK_TRACE_IDS for apm-profiler integration (#217020)
Depends on elastic/elasticsearch#125608 # Summary `ELASTIC_PROFILER_STACK_TRACE_IDS` is introduced for OTel based data streams. The same information is stored in `TRANSACTION_PROFILER_STACK_TRACE_IDS` in the classic APM data streams. Prior to this PR apm<->profiling integration did not work for OTel SDKs. This PR adds handling for the new field name. <img width="1159" alt="Screenshot 2025-04-03 at 10 05 28" src="https://github.com/user-attachments/assets/ce3ad092-d4f4-4a16-843e-923c72938fe1" /> <img width="1772" alt="Screenshot 2025-04-03 at 10 05 40" src="https://github.com/user-attachments/assets/8b2682fe-6f2e-49a4-9995-d83997a05f02" /> --------- Co-authored-by: Greg Kalapos <[email protected]>
1 parent 7092e79 commit 5d96f36

File tree

4 files changed

+117
-14
lines changed

4 files changed

+117
-14
lines changed

x-pack/platform/packages/shared/kbn-apm-types/src/es_fields/apm.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ export const TRANSACTION_OVERFLOW_COUNT = 'transaction.aggregation.overflow_coun
7474
export const TRANSACTION_ROOT = 'transaction.root';
7575
export const TRANSACTION_PROFILER_STACK_TRACE_IDS = 'transaction.profiler_stack_trace_ids';
7676

77+
// OTel field to link profiling and APM
78+
export const ELASTIC_PROFILER_STACK_TRACE_IDS = 'elastic.profiler_stack_trace_ids';
79+
7780
export const EVENT_OUTCOME = 'event.outcome';
7881

7982
export const TRACE_ID = 'trace.id';

x-pack/solutions/observability/plugins/apm/common/es_fields/__snapshots__/es_fields.test.ts.snap

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
import { ProcessorEvent } from '@kbn/observability-plugin/common';
8+
import { rangeQuery, termQuery, kqlQuery } from '@kbn/observability-plugin/server';
9+
import { isEmpty } from 'lodash';
10+
import { unflattenKnownApmEventFields } from '@kbn/apm-data-access-plugin/server/utils';
11+
import {
12+
ELASTIC_PROFILER_STACK_TRACE_IDS,
13+
SERVICE_NAME,
14+
TRANSACTION_NAME,
15+
TRANSACTION_PROFILER_STACK_TRACE_IDS,
16+
TRANSACTION_TYPE,
17+
} from '../../../common/es_fields/apm';
18+
import { environmentQuery } from '../../../common/utils/environment_query';
19+
import type { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client';
20+
21+
export async function getStacktracesIdsField({
22+
apmEventClient,
23+
start,
24+
end,
25+
environment,
26+
serviceName,
27+
transactionType,
28+
transactionName,
29+
kuery,
30+
}: {
31+
apmEventClient: APMEventClient;
32+
start: number;
33+
end: number;
34+
environment: string;
35+
serviceName: string;
36+
transactionType: string;
37+
transactionName?: string;
38+
kuery?: string;
39+
}) {
40+
const response = await apmEventClient.search('get_stacktraces_ids_field', {
41+
apm: {
42+
events: [ProcessorEvent.transaction],
43+
},
44+
size: 1,
45+
terminate_after: 1,
46+
track_total_hits: false,
47+
fields: [ELASTIC_PROFILER_STACK_TRACE_IDS, TRANSACTION_PROFILER_STACK_TRACE_IDS],
48+
_source: false,
49+
query: {
50+
bool: {
51+
filter: [
52+
...rangeQuery(start, end),
53+
...termQuery(SERVICE_NAME, serviceName),
54+
...termQuery(TRANSACTION_TYPE, transactionType),
55+
...termQuery(TRANSACTION_NAME, transactionName),
56+
...kqlQuery(kuery),
57+
...environmentQuery(environment),
58+
],
59+
should: [
60+
{ exists: { field: ELASTIC_PROFILER_STACK_TRACE_IDS } },
61+
{ exists: { field: TRANSACTION_PROFILER_STACK_TRACE_IDS } },
62+
],
63+
},
64+
},
65+
});
66+
67+
const field = unflattenKnownApmEventFields(response.hits.hits[0]?.fields, [
68+
ELASTIC_PROFILER_STACK_TRACE_IDS,
69+
]);
70+
71+
if (!isEmpty(field.elastic.profiler_stack_trace_ids)) {
72+
return ELASTIC_PROFILER_STACK_TRACE_IDS;
73+
}
74+
75+
return TRANSACTION_PROFILER_STACK_TRACE_IDS;
76+
}

x-pack/solutions/observability/plugins/apm/server/routes/profiling/route.ts

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55
* 2.0.
66
*/
77

8-
import { isoToEpochSecsRt, toNumberRt } from '@kbn/io-ts-utils';
8+
import { toNumberRt } from '@kbn/io-ts-utils';
9+
import { ProcessorEvent } from '@kbn/observability-plugin/common';
910
import type { BaseFlameGraph, TopNFunctions } from '@kbn/profiling-utils';
1011
import * as t from 'io-ts';
11-
import { ProcessorEvent } from '@kbn/observability-plugin/common';
1212
import { getApmEventClient } from '../../lib/helpers/get_apm_event_client';
1313
import { createApmServerRoute } from '../apm_routes/create_apm_server_route';
14-
import { environmentRt, kueryRt } from '../default_api_types';
14+
import { environmentRt, kueryRt, rangeRt } from '../default_api_types';
1515
import { fetchFlamegraph } from './fetch_flamegraph';
1616
import { fetchFunctions } from './fetch_functions';
17-
import { TRANSACTION_PROFILER_STACK_TRACE_IDS } from '../../../common/es_fields/apm';
17+
import { getStacktracesIdsField } from './get_stacktraces_ids_field';
1818

1919
const servicesFlamegraphRoute = createApmServerRoute({
2020
endpoint: 'GET /internal/apm/services/{serviceName}/profiling/flamegraph',
@@ -23,12 +23,11 @@ const servicesFlamegraphRoute = createApmServerRoute({
2323
query: t.intersection([
2424
kueryRt,
2525
environmentRt,
26+
rangeRt,
2627
t.partial({
2728
transactionName: t.string,
2829
}),
2930
t.type({
30-
start: isoToEpochSecsRt,
31-
end: isoToEpochSecsRt,
3231
transactionType: t.string,
3332
}),
3433
]),
@@ -47,20 +46,30 @@ const servicesFlamegraphRoute = createApmServerRoute({
4746
const { start, end, kuery, transactionName, transactionType, environment } = params.query;
4847

4948
const indices = apmEventClient.getIndicesFromProcessorEvent(ProcessorEvent.transaction);
49+
const stacktraceIdsField = await getStacktracesIdsField({
50+
apmEventClient,
51+
start,
52+
end,
53+
environment,
54+
serviceName,
55+
transactionType,
56+
transactionName,
57+
kuery,
58+
});
5059

5160
return fetchFlamegraph({
5261
profilingDataAccessStart,
5362
core,
5463
esClient: esClient.asCurrentUser,
55-
start,
56-
end,
64+
start: start / 1000,
65+
end: end / 1000,
5766
kuery,
5867
serviceName,
5968
transactionName,
6069
environment,
6170
transactionType,
6271
indices,
63-
stacktraceIdsField: TRANSACTION_PROFILER_STACK_TRACE_IDS,
72+
stacktraceIdsField,
6473
});
6574
}
6675

@@ -74,12 +83,11 @@ const servicesFunctionsRoute = createApmServerRoute({
7483
path: t.type({ serviceName: t.string }),
7584
query: t.intersection([
7685
environmentRt,
86+
rangeRt,
7787
t.partial({
7888
transactionName: t.string,
7989
}),
8090
t.type({
81-
start: isoToEpochSecsRt,
82-
end: isoToEpochSecsRt,
8391
startIndex: toNumberRt,
8492
endIndex: toNumberRt,
8593
transactionType: t.string,
@@ -111,6 +119,16 @@ const servicesFunctionsRoute = createApmServerRoute({
111119
const { serviceName } = params.path;
112120

113121
const indices = apmEventClient.getIndicesFromProcessorEvent(ProcessorEvent.transaction);
122+
const stacktraceIdsField = await getStacktracesIdsField({
123+
apmEventClient,
124+
start,
125+
end,
126+
environment,
127+
serviceName,
128+
transactionType,
129+
transactionName,
130+
kuery,
131+
});
114132

115133
return fetchFunctions({
116134
profilingDataAccessStart,
@@ -119,9 +137,9 @@ const servicesFunctionsRoute = createApmServerRoute({
119137
startIndex,
120138
endIndex,
121139
indices,
122-
stacktraceIdsField: TRANSACTION_PROFILER_STACK_TRACE_IDS,
123-
start,
124-
end,
140+
stacktraceIdsField,
141+
start: start / 1000,
142+
end: end / 1000,
125143
kuery,
126144
serviceName,
127145
transactionName,

0 commit comments

Comments
 (0)