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

[ML] APM Correlations: Fix usage in load balancing/HA setups. #115145

Merged
Merged
Changes from 1 commit
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
e1fa9c4
[ML] Move data fetching for overall latency histogram to custom hook.
walterra Oct 14, 2021
46f59d6
[ML] Migrates field candidates to regular endpoint.
walterra Oct 15, 2021
0e37684
[ML] Fetch latency correlations via regular endpoints.
walterra Oct 15, 2021
7d75d5f
[ML] Fetch failed transaction correlations via regular endpoints.
walterra Oct 16, 2021
37aa29b
[ML] Fix types.
walterra Oct 17, 2021
414a992
[ML] Rename common/search_strategies to common/correlations.
walterra Oct 17, 2021
83f5ae0
[ML] Rename server/lib/search_strategies to server/lib/correlations.
walterra Oct 17, 2021
8d92d94
[ML] Fix API integration tests.
walterra Oct 17, 2021
9de6ff6
[ML] Remove the no longer needed 'took' attribute.
walterra Oct 17, 2021
30d3883
[ML] Adds chunking to field value pair loading.
walterra Oct 17, 2021
91e5f13
[ML] Fix ccsWarning.
walterra Oct 17, 2021
c1cf1e5
[ML] Fix jest test.
walterra Oct 17, 2021
72eb07d
[ML] Fix item check.
walterra Oct 17, 2021
ba86ce1
[ML] Refactor away from using generators.
walterra Oct 18, 2021
3368aba
[ML] Remove references to log.
walterra Oct 19, 2021
430aa12
[ML] Get rid of SearchStrategy references in type/var names.
walterra Oct 19, 2021
aa80643
[ML] Deduplicate some code.
walterra Oct 19, 2021
4f080bf
[ML] Adds debouncing to correlation analysis.
walterra Oct 19, 2021
8868bb9
[ML] Fix types.
walterra Oct 19, 2021
5a3b927
[ML] Cleanup. Fix progress.
walterra Oct 20, 2021
9ccd53b
[ML] Add comments.
walterra Oct 20, 2021
d15d18e
[ML] Fix chart labels.
walterra Oct 20, 2021
8bb304b
[ML] Add platinum license guard to correlations API endpoints.
walterra Oct 20, 2021
10952cc
Merge branch 'main' into ml-apm-correlations-fix-load-balancing
walterra Oct 29, 2021
da37fd3
[ML] Move progress parts to constants.
walterra Oct 29, 2021
cbeb5ef
[ML] Adds comments to explain the purpose of responseUpdate.
walterra Oct 29, 2021
a0992ef
[ML] Remove unnecessary cast to string.
walterra Oct 29, 2021
a6e75d7
[ML] Remove usage of deprecated useUrlParams.
walterra Oct 29, 2021
deddd79
[ML] Remove casting as Response.
walterra Oct 29, 2021
45da6c5
[ML] Add abort signal.
walterra Oct 29, 2021
71270c1
[ML] Switch endpoint to POST.
walterra Oct 29, 2021
312e9b2
[ML] Improved error handling.
walterra Oct 29, 2021
0cdb94f
Merge branch 'main' into ml-apm-correlations-fix-load-balancing
kibanamachine Nov 1, 2021
f0f0d44
Merge branch 'main' into ml-apm-correlations-fix-load-balancing
kibanamachine Nov 2, 2021
4497e74
Merge branch 'main' into ml-apm-correlations-fix-load-balancing
walterra Nov 2, 2021
81139a8
Merge branch 'main' into ml-apm-correlations-fix-load-balancing
walterra Nov 2, 2021
87c4222
Merge branch 'main' into ml-apm-correlations-fix-load-balancing
walterra Nov 3, 2021
f47e654
[ML] Fix unmounting. Fix percentileThresholdValue. Add unit tests to …
walterra Nov 3, 2021
b65bab6
[ML] Focus assertion on error only.
walterra Nov 3, 2021
c0b6b3e
Merge branch 'main' into ml-apm-correlations-fix-load-balancing
walterra Nov 3, 2021
2b755d3
Merge branch 'main' into ml-apm-correlations-fix-load-balancing
kibanamachine Nov 3, 2021
5310686
Merge branch 'main' into ml-apm-correlations-fix-load-balancing
kibanamachine Nov 3, 2021
0e5de24
Merge branch 'main' into ml-apm-correlations-fix-load-balancing
walterra Nov 5, 2021
55472ef
[ML] Tweak test assertions and adds tests for cancellation.
walterra Nov 5, 2021
72bcc11
[ML] Adds comment to clarify the use of abortControler and isCancelle…
walterra Nov 5, 2021
3e411cd
Merge branch 'ml-apm-correlations-fix-load-balancing' of github.com:w…
walterra Nov 5, 2021
97890cf
[ML] Use abort signal instead of isCancelledRef.
walterra Nov 5, 2021
24d1d2b
Merge branch 'main' into ml-apm-correlations-fix-load-balancing
walterra Nov 5, 2021
86b78c1
[ML] Fix test imports.
walterra Nov 5, 2021
de63d24
Merge branch 'main' into ml-apm-correlations-fix-load-balancing
walterra Nov 8, 2021
22f785c
fix field value pair error handling
walterra Nov 8, 2021
9872afc
[ML] Fix jest test.
walterra Nov 8, 2021
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
Prev Previous commit
Next Next commit
[ML] Fetch failed transaction correlations via regular endpoints.
walterra committed Oct 20, 2021
commit 7d75d5f2f9cbec5b7a7023b6a3bcd2896ad95826
Original file line number Diff line number Diff line change
@@ -36,16 +36,12 @@ import {

import { asPercent } from '../../../../common/utils/formatters';
import { FailedTransactionsCorrelation } from '../../../../common/search_strategies/failed_transactions_correlations/types';
import {
APM_SEARCH_STRATEGIES,
DEFAULT_PERCENTILE_THRESHOLD,
} from '../../../../common/search_strategies/constants';
import { DEFAULT_PERCENTILE_THRESHOLD } from '../../../../common/search_strategies/constants';
import { FieldStats } from '../../../../common/search_strategies/field_stats_types';

import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
import { useLocalStorage } from '../../../hooks/useLocalStorage';
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
import { useSearchStrategy } from '../../../hooks/use_search_strategy';
import { useTheme } from '../../../hooks/use_theme';

import { ImpactBar } from '../../shared/ImpactBar';
@@ -68,6 +64,8 @@ import { useTransactionColors } from './use_transaction_colors';
import { CorrelationsContextPopover } from './context_popover';
import { OnAddFilter } from './context_popover/top_values';

import { useFailedTransactionsCorrelations } from './use_failed_transactions_correlations';

export function FailedTransactionsCorrelations({
onFilter,
}: {
@@ -83,12 +81,8 @@ export function FailedTransactionsCorrelations({

const inspectEnabled = uiSettings.get<boolean>(enableInspectEsQueries);

const { progress, response, startFetch, cancelFetch } = useSearchStrategy(
APM_SEARCH_STRATEGIES.APM_FAILED_TRANSACTIONS_CORRELATIONS,
{
percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD,
}
);
const { progress, response, startFetch, cancelFetch } =
useFailedTransactionsCorrelations();

const fieldStats: Record<string, FieldStats> | undefined = useMemo(() => {
return response.fieldStats?.reduce((obj, field) => {
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/*
* 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 { useCallback, useEffect, useReducer, useRef } from 'react';
import { chunk } from 'lodash';

import { IHttpFetchError } from 'src/core/public';

import { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames';
import { EventOutcome } from '../../../../common/event_outcome';
import { DEFAULT_PERCENTILE_THRESHOLD } from '../../../../common/search_strategies/constants';
import type { RawResponseBase } from '../../../../common/search_strategies/types';
import type {
FailedTransactionsCorrelation,
FailedTransactionsCorrelationsRawResponse,
} from '../../../../common/search_strategies/failed_transactions_correlations/types';

import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
import { useUrlParams } from '../../../context/url_params_context/use_url_params';

import { useApmParams } from '../../../hooks/use_apm_params';
import { useTimeRange } from '../../../hooks/use_time_range';
import { callApmApi } from '../../../services/rest/createCallApmApi';

type Response = FailedTransactionsCorrelationsRawResponse & RawResponseBase;

interface SearchStrategyProgress {
error?: Error | IHttpFetchError;
isRunning: boolean;
loaded: number;
total: number;
}

const getInitialRawResponse = (): Response =>
({
ccsWarning: false,
took: 0,
} as Response);

const getInitialProgress = (): SearchStrategyProgress => ({
isRunning: false,
loaded: 0,
total: 100,
});

const getReducer =
<T>() =>
(prev: T, update: Partial<T>): T => ({
...prev,
...update,
});

export function useFailedTransactionsCorrelations() {
const { serviceName, transactionType } = useApmServiceContext();

const { urlParams } = useUrlParams();
const { transactionName } = urlParams;

const {
query: { kuery, environment, rangeFrom, rangeTo },
} = useApmParams('/services/{serviceName}/transactions/view');

const { start, end } = useTimeRange({ rangeFrom, rangeTo });

const [rawResponse, setRawResponse] = useReducer(
getReducer<Response>(),
getInitialRawResponse()
);

const [fetchState, setFetchState] = useReducer(
getReducer<SearchStrategyProgress>(),
getInitialProgress()
);

const isCancelledRef = useRef(false);

const startFetch = useCallback(async () => {
isCancelledRef.current = false;

setFetchState({
...getInitialProgress(),
isRunning: true,
error: undefined,
});

const query = {
serviceName,
transactionName,
transactionType,
kuery,
environment,
start,
end,
};

try {
const rawResponseUpdate = (await callApmApi({
endpoint: 'POST /internal/apm/latency/overall_distribution',
signal: null,
params: {
body: {
...query,
percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD,
},
},
})) as Response;

const { overallHistogram: errorHistogram } = (await callApmApi({
endpoint: 'POST /internal/apm/latency/overall_distribution',
signal: null,
Copy link
Member

Choose a reason for hiding this comment

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

It looks like we are not aborting request when the component unmounts (signal is null). Is that correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added abort signals in 45da6c5. (Previously we aborted via the isCancelled ref in between queries, this adds support to cancel already running queries too).

params: {
body: {
...query,
percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD,
termFilters: [
{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure },
],
},
},
})) as Response;

if (isCancelledRef.current) {
return;
}

setRawResponse({
...rawResponseUpdate,
errorHistogram,
});
setFetchState({
loaded: 5,
});

const { fieldCandidates: candidates } = await callApmApi({
endpoint: 'GET /internal/apm/correlations/field_candidates',
signal: null,
params: {
query,
},
});

if (isCancelledRef.current) {
Copy link
Member

Choose a reason for hiding this comment

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

why is this necessary? wouldn't the same result be achieved by reinitializing the abort controller? and what happens if you pass an aborted signal to a fetch call? will it execute or be immediately aborted?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

abortController is used to be passed on to cancel individual requests that are already running. isCancelledRef is used to cancel the overall progress of all the tasks that are run within startFetch(). I added a comment about it in 72bcc11 to clarify. Does that explain it? Let me know if I might be missing something about the capabilities of abortController, could we use abortController itself in this check here?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, you can use controller.signal.aborted: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal. But you can also simply pass in the signal, and the request will immediately be aborted, which will then automatically be handled by your catch clause.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great, that let me get rid of isCancelledRef in 97890cf.

return;
}

const fieldCandidates = candidates.filter((t) => !(t === EVENT_OUTCOME));

setFetchState({
loaded: 10,
});

const failedTransactionsCorrelations: FailedTransactionsCorrelation[] =
[];
const fieldsToSample = new Set<string>();
const chunkSize = 10;
let loadCounter = 0;

const fieldCandidatesChunks = chunk(fieldCandidates, chunkSize);

for (const fieldCandidatesChunk of fieldCandidatesChunks) {
Copy link
Member

Choose a reason for hiding this comment

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

This too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This splits all field candidates into chunks, the chunks are called in sequence here on the client side, but all field candidates of a chunk are then queried in parallel on the Kibana server side.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, but why call these in sequence? how many blocking calls can we expect here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was made in the spirit of "make it slow". I'm sure this can be further optimized, in this PR we started to parallelize the server side calls and play it safe on the client side. Since the field candidates and field value pairs are generated dynamically, we don't want to allow to run an unlimited amount of queries in parallel. Field candidates are usually in the dozens, field value pairs can be in the hundreds.

Copy link
Member

Choose a reason for hiding this comment

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

I don't see how running dozens of requests sequentially is a better experience. We can use pLimit here to limit the number of concurrent requests. Something like 5 sounds like a good start.

Copy link
Member

@sorenlouv sorenlouv Nov 8, 2021

Choose a reason for hiding this comment

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

+1 for using p-limit here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

p-limit sounds like a good idea worth pursuing but can we agree to do this in a follow up?

Copy link
Member

Choose a reason for hiding this comment

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

yes that's fine 👍

const pValues = await callApmApi({
endpoint: 'POST /internal/apm/correlations/p_values',
signal: null,
params: {
body: { ...query, fieldCandidates: fieldCandidatesChunk },
},
});

if (pValues.failedTransactionsCorrelations.length > 0) {
pValues.failedTransactionsCorrelations.forEach((d) => {
fieldsToSample.add(d.fieldName);
});
failedTransactionsCorrelations.push(
...pValues.failedTransactionsCorrelations
);
rawResponseUpdate.failedTransactionsCorrelations =
failedTransactionsCorrelations;
setRawResponse(rawResponseUpdate);
}

if (isCancelledRef.current) {
return;
}

loadCounter += chunkSize;
setFetchState({
loaded: 20 + Math.round((loadCounter / fieldCandidates.length) * 80),
});
}

const fieldStats = await callApmApi({
endpoint: 'POST /internal/apm/correlations/field_stats',
signal: null,
params: {
body: {
...query,
fieldsToSample: [...fieldsToSample],
},
},
});

rawResponseUpdate.fieldStats = fieldStats.stats;
setRawResponse(rawResponseUpdate);

setFetchState({
loaded: 100,
});
} catch (e) {
// const err = e as Error | IHttpFetchError;
// const message = error.body?.message ?? error.response?.statusText;
setFetchState({
error: e as Error,
});
}

setFetchState({
isRunning: false,
});
}, [
environment,
serviceName,
transactionName,
transactionType,
kuery,
start,
end,
]);

const cancelFetch = useCallback(() => {
isCancelledRef.current = true;
setFetchState({
isRunning: false,
});
}, []);

// auto-update
useEffect(() => {
startFetch();
return cancelFetch;
}, [startFetch, cancelFetch]);

return {
progress: fetchState,
response: rawResponse,
startFetch,
cancelFetch,
};
}
Original file line number Diff line number Diff line change
@@ -5,7 +5,8 @@
* 2.0.
*/

import { useCallback, useEffect, useReducer } from 'react';
import { useCallback, useEffect, useReducer, useRef } from 'react';
import { chunk } from 'lodash';

import { IHttpFetchError } from 'src/core/public';

@@ -73,8 +74,11 @@ export function useLatencyCorrelations() {
getInitialProgress()
);

const isCancelledRef = useRef(false);

const startFetch = useCallback(async () => {
// TODO re-implemented cancelling
isCancelledRef.current = false;

setFetchState({
...getInitialProgress(),
isRunning: true,
@@ -93,16 +97,20 @@ export function useLatencyCorrelations() {

try {
const rawResponseUpdate = (await callApmApi({
endpoint: 'GET /internal/apm/latency/overall_distribution',
endpoint: 'POST /internal/apm/latency/overall_distribution',
Copy link
Member

Choose a reason for hiding this comment

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

Does this need to be POST?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had problems using GET and query since the data sent over would be too large. Unfortunately I couldn't use a GET request with body, unlike ES itself it seems the client doesn't allow it, so I had to switch it to use POST and body.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, makes sense.

signal: null,
params: {
query: {
body: {
...query,
percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD + '',
Copy link
Member

Choose a reason for hiding this comment

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

Why does this need to be converted to string?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in a0992ef, this was an oversight and the API already allows to send over a number already now.

},
},
})) as Response;
Copy link
Member

@sorenlouv sorenlouv Oct 25, 2021

Choose a reason for hiding this comment

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

Why the explicit type annotation? The returned type should already be correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It used the casting because Response was a type with all attributes the would be fetch via multiple different API calls. I refactored it in deddd79 so it's no longer necessary.


if (isCancelledRef.current) {
return;
}

setRawResponse(rawResponseUpdate);
setFetchState({
loaded: 5,
@@ -116,6 +124,10 @@ export function useLatencyCorrelations() {
},
});

if (isCancelledRef.current) {
return;
}

setFetchState({
loaded: 10,
});
@@ -130,36 +142,47 @@ export function useLatencyCorrelations() {
},
},
});

if (isCancelledRef.current) {
return;
}

setFetchState({
loaded: 20,
});

const fieldsToSample = new Set<string>();
const latencyCorrelations: LatencyCorrelation[] = [];
const chunkSize = 10;
let loadCounter = 0;
for (const { fieldName, fieldValue } of fieldValuePairs) {

const fieldValuePairChunks = chunk(fieldValuePairs, chunkSize);

for (const fieldValuePairChunk of fieldValuePairChunks) {
const significantCorrelations = await callApmApi({
endpoint: 'GET /internal/apm/correlations/significant_correlations',
endpoint: 'POST /internal/apm/correlations/significant_correlations',
signal: null,
params: {
query: {
...query,
fieldName,
fieldValue: fieldValue + '',
},
body: { ...query, fieldValuePairs: fieldValuePairChunk },
},
});

if (significantCorrelations.latencyCorrelations.length > 0) {
fieldsToSample.add(fieldName);
significantCorrelations.latencyCorrelations.forEach((d) => {
fieldsToSample.add(d.fieldName);
});
latencyCorrelations.push(
...significantCorrelations.latencyCorrelations
);
rawResponseUpdate.latencyCorrelations = latencyCorrelations;
setRawResponse(rawResponseUpdate);
}

loadCounter++;
if (isCancelledRef.current) {
return;
}

loadCounter += chunkSize;
setFetchState({
loaded: 20 + Math.round((loadCounter / fieldValuePairs.length) * 80),
});
@@ -188,7 +211,6 @@ export function useLatencyCorrelations() {
setFetchState({
error: e as Error,
});
return;
}

setFetchState({
@@ -205,6 +227,7 @@ export function useLatencyCorrelations() {
]);

const cancelFetch = useCallback(() => {
isCancelledRef.current = true;
setFetchState({
isRunning: false,
});
Original file line number Diff line number Diff line change
@@ -34,9 +34,9 @@ export function useLatencyOverallDistribution() {
(callApmApi) => {
if (serviceName && environment && start && end) {
return callApmApi({
endpoint: 'GET /internal/apm/latency/overall_distribution',
endpoint: 'POST /internal/apm/latency/overall_distribution',
params: {
query: {
body: {
serviceName,
transactionName,
transactionType,
218 changes: 0 additions & 218 deletions x-pack/plugins/apm/public/hooks/use_search_strategy.ts

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

8 changes: 0 additions & 8 deletions x-pack/plugins/apm/server/lib/search_strategies/index.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -6,6 +6,8 @@
*/

export { fetchFailedTransactionsCorrelationPValues } from './query_failure_correlation';
export { fetchPValues } from './query_p_values';
export { fetchSignificantCorrelations } from './query_significant_correlations';
export { fetchTransactionDurationFieldCandidates } from './query_field_candidates';
export { fetchTransactionDurationFieldValuePairs } from './query_field_value_pairs';
export { fetchTransactionDurationFractions } from './query_fractions';
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
import { estypes } from '@elastic/elasticsearch';
import { ElasticsearchClient } from 'kibana/server';
import { SearchStrategyParams } from '../../../../common/search_strategies/types';
import { FailedTransactionsCorrelation } from '../../../../common/search_strategies/failed_transactions_correlations/types';
import { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames';
import { EventOutcome } from '../../../../common/event_outcome';
import { fetchTransactionDurationRanges } from './query_ranges';
@@ -88,7 +89,7 @@ export const fetchFailedTransactionsCorrelationPValues = async (
}>;

// Using for of to sequentially augment the results with histogram data.
const result = [];
const result: FailedTransactionsCorrelation[] = [];
for (const bucket of overallResult.buckets) {
// Scale the score into a value from 0 - 1
// using a concave piecewise linear function in -log(p-value)
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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 { ElasticsearchClient } from 'src/core/server';

import type { SearchStrategyParams } from '../../../../common/search_strategies/types';
import type { FailedTransactionsCorrelation } from '../../../../common/search_strategies/failed_transactions_correlations/types';

import { ERROR_CORRELATION_THRESHOLD } from '../constants';

import {
fetchFailedTransactionsCorrelationPValues,
fetchTransactionDurationHistogramRangeSteps,
} from './index';

export const fetchPValues = async (
esClient: ElasticsearchClient,
paramsWithIndex: SearchStrategyParams,
fieldCandidates: string[]
) => {
const failedTransactionsCorrelations: FailedTransactionsCorrelation[] = [];

const histogramRangeSteps = await fetchTransactionDurationHistogramRangeSteps(
esClient,
paramsWithIndex
);

const results = await Promise.allSettled(
fieldCandidates.map((fieldName) =>
fetchFailedTransactionsCorrelationPValues(
esClient,
paramsWithIndex,
histogramRangeSteps,
fieldName
)
)
);

results.forEach((result, idx) => {
if (result.status === 'fulfilled') {
failedTransactionsCorrelations.push(
...result.value.filter(
(record) =>
record &&
typeof record.pValue === 'number' &&
record.pValue < ERROR_CORRELATION_THRESHOLD
)
);
} else {
// If one of the fields in the batch had an error
// addLogMessage(
// `Error getting error correlation for field ${fieldCandidates[idx]}: ${result.reason}.`
// );
}
});

// TODO Fix CCS warning
return { failedTransactionsCorrelations, ccsWarning: false };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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 { range } from 'lodash';

import type { ElasticsearchClient } from 'src/core/server';

import type {
FieldValuePair,
SearchStrategyParams,
} from '../../../../common/search_strategies/types';
import type { LatencyCorrelation } from '../../../../common/search_strategies/latency_correlations/types';

import {
fetchTransactionDurationFractions,
fetchTransactionDurationHistogramRangeSteps,
fetchTransactionDurationHistograms,
fetchTransactionDurationPercentiles,
} from './index';
import { computeExpectationsAndRanges } from '../utils';

export const fetchSignificantCorrelations = async (
esClient: ElasticsearchClient,
paramsWithIndex: SearchStrategyParams,
fieldValuePairs: FieldValuePair[]
) => {
// Create an array of ranges [2, 4, 6, ..., 98]
const percentileAggregationPercents = range(2, 100, 2);
const { percentiles: percentilesRecords } =
await fetchTransactionDurationPercentiles(
esClient,
paramsWithIndex,
percentileAggregationPercents
);
const percentiles = Object.values(percentilesRecords);

const { expectations, ranges } = computeExpectationsAndRanges(percentiles);

const { fractions, totalDocCount } = await fetchTransactionDurationFractions(
esClient,
paramsWithIndex,
ranges
);

const histogramRangeSteps = await fetchTransactionDurationHistogramRangeSteps(
esClient,
paramsWithIndex
);

const latencyCorrelations: LatencyCorrelation[] = [];

for await (const item of fetchTransactionDurationHistograms(
esClient,
() => {},
paramsWithIndex,
expectations,
ranges,
fractions,
histogramRangeSteps,
totalDocCount,
fieldValuePairs
)) {
if (item !== undefined) {
latencyCorrelations.push(item);
}
}

// TODO Fix CCS warning
return { latencyCorrelations, ccsWarning: false };
};

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

20 changes: 0 additions & 20 deletions x-pack/plugins/apm/server/plugin.ts
Original file line number Diff line number Diff line change
@@ -26,7 +26,6 @@ import { registerFleetPolicyCallbacks } from './lib/fleet/register_fleet_policy_
import { createApmTelemetry } from './lib/apm_telemetry';
import { createApmEventClient } from './lib/helpers/create_es_client/create_apm_event_client';
import { getInternalSavedObjectsClient } from './lib/helpers/get_internal_saved_objects_client';
import { registerSearchStrategies } from './lib/search_strategies';
import { createApmAgentConfigurationIndex } from './lib/settings/agent_configuration/create_agent_config_index';
import { getApmIndices } from './lib/settings/apm_indices/get_apm_indices';
import { createApmCustomLinkIndex } from './lib/settings/custom_link/create_custom_link_index';
@@ -197,25 +196,6 @@ export class APMPlugin
logger: this.logger,
});

// search strategies for async partial search results
core.getStartServices().then(([coreStart]) => {
(async () => {
const savedObjectsClient = new SavedObjectsClient(
coreStart.savedObjects.createInternalRepository()
);

const includeFrozen = await coreStart.uiSettings
.asScopedToClient(savedObjectsClient)
.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN);

registerSearchStrategies(
plugins.data.search.registerSearchStrategy,
boundGetApmIndices,
includeFrozen
);
})();
});

core.deprecations.registerDeprecations({
getDeprecations: getDeprecations({
cloudSetup: plugins.cloud,
115 changes: 52 additions & 63 deletions x-pack/plugins/apm/server/routes/correlations.ts
Original file line number Diff line number Diff line change
@@ -6,24 +6,17 @@
*/

import * as t from 'io-ts';
import { range } from 'lodash';

// import { toNumberRt } from '@kbn/io-ts-utils';

import type { FieldValuePair } from '../../common/search_strategies/types';
import type { LatencyCorrelation } from '../../common/search_strategies/latency_correlations/types';
import { toNumberRt } from '@kbn/io-ts-utils';

import { setupRequest } from '../lib/helpers/setup_request';
import {
fetchPValues,
fetchSignificantCorrelations,
fetchTransactionDurationFieldCandidates,
fetchTransactionDurationFieldValuePairs,
fetchTransactionDurationFractions,
fetchTransactionDurationHistogramRangeSteps,
fetchTransactionDurationHistograms,
fetchTransactionDurationPercentiles,
} from '../lib/search_strategies/queries';
import { fetchFieldsStats } from '../lib/search_strategies/queries/field_stats/get_fields_stats';
import { computeExpectationsAndRanges } from '../lib/search_strategies/utils';

import { withApmSpan } from '../utils/with_apm_span';

@@ -138,9 +131,9 @@ const fieldValuePairsRoute = createApmServerRoute({
});

const significantCorrelationsRoute = createApmServerRoute({
endpoint: 'GET /internal/apm/correlations/significant_correlations',
endpoint: 'POST /internal/apm/correlations/significant_correlations',
params: t.type({
query: t.intersection([
body: t.intersection([
t.partial({
serviceName: t.string,
transactionName: t.string,
@@ -150,8 +143,12 @@ const significantCorrelationsRoute = createApmServerRoute({
kueryRt,
rangeRt,
t.type({
fieldName: t.string, // t.array(t.string),
fieldValue: t.string, // t.array(t.union([t.string, toNumberRt])),
fieldValuePairs: t.array(
t.type({
fieldName: t.string,
fieldValue: t.union([t.string, toNumberRt]),
})
),
}),
]),
}),
@@ -160,71 +157,63 @@ const significantCorrelationsRoute = createApmServerRoute({
const { indices } = await setupRequest(resources);
const esClient = resources.context.core.elasticsearch.client.asCurrentUser;

const { fieldName, fieldValue, ...params } = resources.params.query;
const fieldValuePairs: FieldValuePair[] = [
{
fieldName,
fieldValue,
},
];
const { fieldValuePairs, ...params } = resources.params.body;

const paramsWithIndex = {
...params,
index: indices.transaction,
};

return withApmSpan('get_significant_correlations', async () => {
// Create an array of ranges [2, 4, 6, ..., 98]
const percentileAggregationPercents = range(2, 100, 2);
const { percentiles: percentilesRecords } =
await fetchTransactionDurationPercentiles(
return withApmSpan(
'get_significant_correlations',
async () =>
await fetchSignificantCorrelations(
esClient,
paramsWithIndex,
percentileAggregationPercents
);
const percentiles = Object.values(percentilesRecords);

const { expectations, ranges } =
computeExpectationsAndRanges(percentiles);
fieldValuePairs
)
);
},
});

const { fractions, totalDocCount } =
await fetchTransactionDurationFractions(
esClient,
paramsWithIndex,
ranges
);
const pValuesRoute = createApmServerRoute({
endpoint: 'POST /internal/apm/correlations/p_values',
params: t.type({
body: t.intersection([
t.partial({
serviceName: t.string,
transactionName: t.string,
transactionType: t.string,
}),
environmentRt,
kueryRt,
rangeRt,
t.type({
fieldCandidates: t.array(t.string),
}),
]),
}),
options: { tags: ['access:apm'] },
handler: async (resources) => {
const { indices } = await setupRequest(resources);
const esClient = resources.context.core.elasticsearch.client.asCurrentUser;

const histogramRangeSteps =
await fetchTransactionDurationHistogramRangeSteps(
esClient,
paramsWithIndex
);
const { fieldCandidates, ...params } = resources.params.body;

const latencyCorrelations: LatencyCorrelation[] = [];
const paramsWithIndex = {
...params,
index: indices.transaction,
};

for await (const item of fetchTransactionDurationHistograms(
esClient,
() => {},
paramsWithIndex,
expectations,
ranges,
fractions,
histogramRangeSteps,
totalDocCount,
fieldValuePairs
)) {
if (item !== undefined) {
latencyCorrelations.push(item);
}
}

// TODO Fix CCS warning
return { latencyCorrelations, ccsWarning: false };
});
return withApmSpan(
'get_p_values',
async () => await fetchPValues(esClient, paramsWithIndex, fieldCandidates)
);
},
});

export const correlationsRouteRepository = createApmServerRouteRepository()
.add(pValuesRoute)
.add(fieldCandidatesRoute)
.add(fieldStatsRoute)
.add(fieldValuePairsRoute)