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

[APM] Improve logic to determine when to use the transaction.duration.summary field #171315

Merged
merged 34 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0de5219
Improve logic to determine when to use the transaction.duration.summa…
crespocarlos Nov 15, 2023
6a53f6d
Merge branch 'main' of github.com:elastic/kibana into 167578-empty-la…
crespocarlos Nov 15, 2023
c7f7683
Simplifying the solution
crespocarlos Nov 16, 2023
ec4177d
Small optimization
crespocarlos Nov 20, 2023
9a1d36f
Add test case
crespocarlos Nov 20, 2023
83e1c55
Merge branch 'main' into 167578-empty-latency-chart-fix
kibanamachine Nov 20, 2023
0e329c9
Fix after merge
crespocarlos Nov 20, 2023
0351b2f
Clean up
crespocarlos Nov 20, 2023
618cddc
CR fixes
crespocarlos Nov 23, 2023
0416fe9
Merge branch 'main' into 167578-empty-latency-chart-fix
kibanamachine Nov 23, 2023
72782f3
Verify when hasDurationSummaryField is true on a cluster level
crespocarlos Nov 23, 2023
bb2d2cd
Merge branch 'main' into 167578-empty-latency-chart-fix
kibanamachine Nov 27, 2023
a109347
Merge branch 'main' into 167578-empty-latency-chart-fix
kibanamachine Dec 1, 2023
72f76b0
Merge branch 'main' into 167578-empty-latency-chart-fix
kibanamachine Dec 12, 2023
ea01c51
Type fix
crespocarlos Dec 12, 2023
cf8a8f7
Merge branch 'main' into 167578-empty-latency-chart-fix
kibanamachine Dec 13, 2023
28a82ee
CR fixes
crespocarlos Dec 13, 2023
3c89c0f
Revert changes to base_client
crespocarlos Dec 13, 2023
1710bf6
Revert changes to base_client
crespocarlos Dec 13, 2023
aa2e343
Clean up
crespocarlos Dec 13, 2023
7a80eb7
More clean up
crespocarlos Dec 13, 2023
196724c
Merge branch 'main' into 167578-empty-latency-chart-fix
kibanamachine Dec 18, 2023
c5addd4
Merge branch 'main' into 167578-empty-latency-chart-fix
kibanamachine Dec 20, 2023
f89d228
CR fixes
crespocarlos Dec 21, 2023
0709537
Merge branch 'main' into 167578-empty-latency-chart-fix
kibanamachine Jan 2, 2024
0ee3106
[CI] Auto-commit changed files from 'node scripts/notice'
kibanamachine Jan 2, 2024
22d3e61
Refactor get_document_sources
crespocarlos Jan 3, 2024
ed45132
Small improvement
crespocarlos Jan 3, 2024
6b3f0bf
Clean up
crespocarlos Jan 3, 2024
33efa72
Merge branch 'main' into 167578-empty-latency-chart-fix
kibanamachine Jan 4, 2024
6d5eed5
Merge branch 'main' into 167578-empty-latency-chart-fix
kibanamachine Jan 8, 2024
f207b54
Merge branch 'main' into 167578-empty-latency-chart-fix
kibanamachine Jan 15, 2024
5897ee7
Rename variable
crespocarlos Jan 15, 2024
ecfa42c
Fix types
crespocarlos Jan 15, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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.
*/

import { ApmFields, apm } from '@kbn/apm-synthtrace-client';
import { random } from 'lodash';
import { pipeline, Readable } from 'stream';
import semver from 'semver';
import { Scenario } from '../cli/scenario';
import { deleteSummaryFieldTransform } from '../lib/utils/transform_helpers';
import { withClient } from '../lib/utils/with_client';

const scenario: Scenario<ApmFields> = async ({ logger, versionOverride }) => {
const isLegacy = versionOverride && semver.lt(versionOverride, '8.7.0');
return {
bootstrap: async ({ apmEsClient }) => {
if (isLegacy) {
apmEsClient.pipeline((base: Readable) => {
const defaultPipeline = apmEsClient.getDefaultPipeline()(
base
) as unknown as NodeJS.ReadableStream;

return pipeline(defaultPipeline, deleteSummaryFieldTransform(), (err) => {
Copy link
Member

@sorenlouv sorenlouv Dec 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps also add observer.version to indicate which version of APM Server we are simulating

Suggested change
return pipeline(defaultPipeline, deleteSummaryFieldTransform(), (err) => {
return pipeline(defaultPipeline, addObserverVersionTransform(versionOverride), deleteSummaryFieldTransform(), (err) => {

if (err) {
logger.error(err);
}
});
});
}
},
generate: ({ range, clients: { apmEsClient } }) => {
const successfulTimestamps = range.ratePerMinute(6);
const instance = apm
.service({
name: `java${isLegacy ? '-legacy' : ''}`,
environment: 'production',
agentName: 'java',
})
.instance(`instance`);

return withClient(
apmEsClient,
successfulTimestamps.generator((timestamp) => {
const randomHigh = random(1000, 4000);
const randomLow = random(100, randomHigh / 5);
const duration = random(randomLow, randomHigh);
return instance
.transaction({ transactionName: 'GET /order/{id}' })
.timestamp(timestamp)
.duration(duration)
.success();
})
);
},
};
};

export default scenario;
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useApmRoutePath } from '../../hooks/use_apm_route_path';
import { FetcherResult, useFetcher } from '../../hooks/use_fetcher';
import { useTimeRange } from '../../hooks/use_time_range';
import { useApmPluginContext } from '../apm_plugin/use_apm_plugin_context';
import { useEnvironmentsContext } from '../environments_context/use_environments_context';

export const TimeRangeMetadataContext = createContext<
FetcherResult<TimeRangeMetadata> | undefined
Expand All @@ -30,9 +31,11 @@ export function ApmTimeRangeMetadataContextProvider({
core: { uiSettings },
} = useApmPluginContext();

const { query } = useApmParams('/*');
const { query, path } = useApmParams('/*');
const { environment } = useEnvironmentsContext();

const kuery = 'kuery' in query && query.kuery ? query.kuery : '';
const serviceName = 'serviceName' in path ? path.serviceName : undefined;

const range =
'rangeFrom' in query && 'rangeTo' in query
Expand All @@ -58,6 +61,8 @@ export function ApmTimeRangeMetadataContextProvider({
start={start}
end={end}
kuery={kuery}
serviceName={serviceName}
environment={environment}
>
{children}
</TimeRangeMetadataContextProvider>
Expand All @@ -71,13 +76,17 @@ export function TimeRangeMetadataContextProvider({
start,
end,
kuery,
serviceName,
environment,
}: {
children?: React.ReactNode;
uiSettings: IUiSettingsClient;
useSpanName: boolean;
start: string;
end: string;
kuery: string;
serviceName?: string;
environment?: string;
}) {
const enableServiceTransactionMetrics = uiSettings.get<boolean>(
apmEnableServiceMetrics,
Expand All @@ -100,6 +109,8 @@ export function TimeRangeMetadataContextProvider({
useSpanName,
enableServiceTransactionMetrics,
enableContinuousRollups,
serviceName,
environment,
},
},
});
Expand All @@ -111,6 +122,8 @@ export function TimeRangeMetadataContextProvider({
useSpanName,
enableServiceTransactionMetrics,
enableContinuousRollups,
serviceName,
environment,
]
);

Expand Down
139 changes: 86 additions & 53 deletions x-pack/plugins/apm/server/lib/helpers/get_document_sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,21 @@
*/
import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { Environment } from '../../../common/environment_rt';
import { ApmDocumentType } from '../../../common/document_type';
import { RollupInterval } from '../../../common/rollup';
import { APMEventClient } from './create_es_client/create_apm_event_client';
import { getConfigForDocumentType } from './create_es_client/document_type';
import { TRANSACTION_DURATION_SUMMARY } from '../../../common/es_fields/apm';
import { TimeRangeMetadata } from '../../../common/time_range_metadata';
import {
getDurationSummaryFilter,
getDurationLegacyFilter,
} from './transactions';
import {
SERVICE_ENVIRONMENT,
SERVICE_NAME,
} from '../../../common/es_fields/apm';

const getRequest = ({
documentType,
Expand Down Expand Up @@ -57,25 +66,50 @@ export async function getDocumentSources({
kuery,
enableServiceTransactionMetrics,
enableContinuousRollups,
environment,
serviceName,
}: {
apmEventClient: APMEventClient;
start: number;
end: number;
kuery: string;
enableServiceTransactionMetrics: boolean;
enableContinuousRollups: boolean;
environment?: Environment;
serviceName?: string;
}) {
const currentRange = rangeQuery(start, end);
const diff = end - start;
const kql = kqlQuery(kuery);
const beforeRange = rangeQuery(start - diff, end - diff);
const serviceFilter: QueryDslQueryContainer[] | undefined = serviceName
? [
{
term: {
[SERVICE_NAME]: serviceName,
},
},
...(environment &&
environment !== 'ENVIRONMENT_ALL' &&
environment !== 'ENVIRONMENT_NOT_DEFINED'
? [
{
term: {
[SERVICE_ENVIRONMENT]: environment,
},
},
]
: []),
crespocarlos marked this conversation as resolved.
Show resolved Hide resolved
]
: undefined;

const sourcesToCheck = [
const documentTypesToCheck = [
...(enableServiceTransactionMetrics
? [ApmDocumentType.ServiceTransactionMetric as const]
: []),
ApmDocumentType.TransactionMetric as const,
].flatMap((documentType) => {
];
const sourcesToCheck = documentTypesToCheck.flatMap((documentType) => {
const docTypeConfig = getConfigForDocumentType(documentType);

return (
Expand Down Expand Up @@ -103,47 +137,43 @@ export async function getDocumentSources({
});
});

const sourcesToCheckWithSummary = [
ApmDocumentType.TransactionMetric as const,
].flatMap((documentType) => {
const docTypeConfig = getConfigForDocumentType(documentType);
const sourcesToCheckWithSummary = documentTypesToCheck.flatMap(
(documentType) => {
const docTypeConfig = getConfigForDocumentType(documentType);

return (
enableContinuousRollups
? docTypeConfig.rollupIntervals
: [RollupInterval.OneMinute]
).flatMap((rollupInterval) => {
const summaryExistsFilter = {
bool: {
filter: [
{
exists: {
field: TRANSACTION_DURATION_SUMMARY,
},
},
],
},
};

return {
documentType,
rollupInterval,
meta: {
checkSummaryFieldExists: true,
},
before: getRequest({
return (
enableContinuousRollups
? docTypeConfig.rollupIntervals
: [RollupInterval.OneMinute]
).flatMap((rollupInterval) => {
return {
documentType,
rollupInterval,
filters: [...kql, ...beforeRange, summaryExistsFilter],
}),
current: getRequest({
documentType,
rollupInterval,
filters: [...kql, ...currentRange, summaryExistsFilter],
}),
};
});
});
meta: {
checkSummaryFieldExists: true,
},
before: getRequest({
documentType,
rollupInterval,
filters: [
...kql,
...currentRange,
getDurationLegacyFilter(serviceFilter),
],
}),
current: getRequest({
documentType,
rollupInterval,
filters: [
...kql,
...currentRange,
getDurationSummaryFilter(serviceFilter),
],
}),
};
});
}
);

const allSourcesToCheck = [...sourcesToCheck, ...sourcesToCheckWithSummary];

Expand Down Expand Up @@ -187,40 +217,43 @@ export async function getDocumentSources({
} = checkedSource;

const hasDocBeforeOrAfter = hasDocBefore || hasDocAfter;

// If there is any data before, we require that data is available before
// this time range to mark this source as available. If we don't do that,
// users that upgrade to a version that starts generating service tx metrics
// will see a mostly empty screen for a while after upgrading.
// If we only check before, users with a new deployment will use raw transaction
// events.
const hasDocs = hasAnySourceDocBefore ? hasDocBefore : hasDocBeforeOrAfter;
const hasOnlyDocsWithSummary = hasDocBefore ? false : hasDocAfter;

return {
documentType,
rollupInterval,
checkSummaryFieldExists,
hasDocs,
hasDocs: checkSummaryFieldExists ? hasOnlyDocsWithSummary : hasDocs,
};
});

const sources: TimeRangeMetadata['sources'] = sourcesWithHasDocs
.filter((source) => !source.checkSummaryFieldExists)
.map((checkedSource) => {
const { documentType, hasDocs, rollupInterval } = checkedSource;

const hasDurationSummaryField = sourcesWithHasDocs.some((eSource) => {
return (
eSource.documentType === documentType &&
eSource.rollupInterval === rollupInterval &&
eSource.checkSummaryFieldExists &&
eSource.hasDocs
);
});

return {
documentType,
rollupInterval,
hasDocs,
hasDurationSummaryField:
documentType === ApmDocumentType.ServiceTransactionMetric ||
Boolean(
sourcesWithHasDocs.find((eSource) => {
return (
eSource.documentType === documentType &&
eSource.rollupInterval === rollupInterval &&
eSource.checkSummaryFieldExists
);
})?.hasDocs
),
hasDurationSummaryField,
};
});

Expand Down
Loading