mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ML] APM Correlations: Chart for failed transactions correlations tab. (#110172)
* [ML] Fix tooltip text.
* Revert "[ML] Fix tooltip text."
This reverts commit ca86f769d7
.
* [ML] Chart prototype.
* [ML] Hover support for failed transactions chart.
* [ML] Add p-value column.
* [ML] Code consolidation.
* [ML] Fix naming inconsistencies.
* [ML] Fix naming inconsistencies.
* [ML] Fix naming inconsistencies.
* [ML] Consolidate hooks.
* [ML] Consolidate hooks.
* [ML] Consolidate hooks.
* [ML] Use function overloads.
* [ML] Fix naming inconsistencies.
* [ML] Fix jest test.
* [ML] Fix chart loading behavior.
* [ML] Rename values to latencyCorrelations.
* [ML] Clean up types.
* [ML] Add function overloads.
* [Ml] Update API integration tests.
* [ML] Rename values to failedTransactionsCorrelations.
* [ML] Fix naming inconsistencies.
* [ML] Fix naming inconsistencies.
* [ML] Fix naming inconsistencies.
* [ML] Fix jest test.
* [ML] Fix API integration test
* [ML] Clean up chart data.
* [ML] Fix chart props.
* [ML] Tweak types for failed correlations.
* [ML] Improve FieldValuePair type usage.
* [ML] Remove 'async' from variable names.
* [ML] Fix typo.
* [ML] Simplify mock.
* [ML] Refactor code that used type guards.
* [ML] Comment about feature availability.
* [ML] Simplify check.
* [ML] Simplify selectedHistogram.
* [ML] Improve column type safety.
* [ML] Simplify selectedTerm.
* [ML] Simplify sorting.
* [ML] Fix regresssion when there's no data for failed transactions.
* [ML] Rename fieldFilter to termFilters.
* [ML] Update api integration test assertions.
* [ML] Fix failed transactions params.
* [ML] Tweak chart title.
* [ML] Tweak chart colors.
* [ML] Add translation.
* [ML] Tweak selectedTerm if statement.
* [ML] Fix types.
* [ML] Fix assertion text.
* [ML] Refactor replaceHistogramDotsWithBars.
* [ML] Refactor fetchFailedTransactionsCorrelationPValues.
* [ML] Fix score column width.
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
4dc72140be
commit
065701a0c3
23 changed files with 427 additions and 183 deletions
|
@ -7,6 +7,7 @@
|
|||
|
||||
import {
|
||||
FieldValuePair,
|
||||
HistogramItem,
|
||||
RawResponseBase,
|
||||
SearchStrategyClientParams,
|
||||
} from '../types';
|
||||
|
@ -21,14 +22,23 @@ export interface FailedTransactionsCorrelation extends FieldValuePair {
|
|||
normalizedScore: number;
|
||||
failurePercentage: number;
|
||||
successPercentage: number;
|
||||
histogram: HistogramItem[];
|
||||
}
|
||||
|
||||
export type FailedTransactionsCorrelationsImpactThreshold = typeof FAILED_TRANSACTIONS_IMPACT_THRESHOLD[keyof typeof FAILED_TRANSACTIONS_IMPACT_THRESHOLD];
|
||||
|
||||
export type FailedTransactionsCorrelationsParams = SearchStrategyClientParams;
|
||||
export interface FailedTransactionsCorrelationsParams {
|
||||
percentileThreshold: number;
|
||||
}
|
||||
|
||||
export type FailedTransactionsCorrelationsRequestParams = FailedTransactionsCorrelationsParams &
|
||||
SearchStrategyClientParams;
|
||||
|
||||
export interface FailedTransactionsCorrelationsRawResponse
|
||||
extends RawResponseBase {
|
||||
log: string[];
|
||||
failedTransactionsCorrelations: FailedTransactionsCorrelation[];
|
||||
failedTransactionsCorrelations?: FailedTransactionsCorrelation[];
|
||||
percentileThresholdValue?: number;
|
||||
overallHistogram?: HistogramItem[];
|
||||
errorHistogram?: HistogramItem[];
|
||||
}
|
||||
|
|
|
@ -27,11 +27,14 @@ export interface LatencyCorrelationSearchServiceProgress {
|
|||
loadedHistograms: number;
|
||||
}
|
||||
|
||||
export interface LatencyCorrelationsParams extends SearchStrategyClientParams {
|
||||
export interface LatencyCorrelationsParams {
|
||||
percentileThreshold: number;
|
||||
analyzeCorrelations: boolean;
|
||||
}
|
||||
|
||||
export type LatencyCorrelationsRequestParams = LatencyCorrelationsParams &
|
||||
SearchStrategyClientParams;
|
||||
|
||||
export interface LatencyCorrelationsRawResponse extends RawResponseBase {
|
||||
log: string[];
|
||||
overallHistogram?: HistogramItem[];
|
||||
|
|
|
@ -33,7 +33,10 @@ import {
|
|||
|
||||
import { asPercent } from '../../../../common/utils/formatters';
|
||||
import { FailedTransactionsCorrelation } from '../../../../common/search_strategies/failed_transactions_correlations/types';
|
||||
import { APM_SEARCH_STRATEGIES } from '../../../../common/search_strategies/constants';
|
||||
import {
|
||||
APM_SEARCH_STRATEGIES,
|
||||
DEFAULT_PERCENTILE_THRESHOLD,
|
||||
} from '../../../../common/search_strategies/constants';
|
||||
|
||||
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
|
||||
|
@ -47,6 +50,11 @@ import { CorrelationsTable } from './correlations_table';
|
|||
import { FailedTransactionsCorrelationsHelpPopover } from './failed_transactions_correlations_help_popover';
|
||||
import { isErrorMessage } from './utils/is_error_message';
|
||||
import { getFailedTransactionsCorrelationImpactLabel } from './utils/get_failed_transactions_correlation_impact_label';
|
||||
import { getOverallHistogram } from './utils/get_overall_histogram';
|
||||
import {
|
||||
TransactionDistributionChart,
|
||||
TransactionDistributionChartData,
|
||||
} from '../../shared/charts/transaction_distribution_chart';
|
||||
import { CorrelationsLog } from './correlations_log';
|
||||
import { CorrelationsEmptyStatePrompt } from './empty_state_prompt';
|
||||
import { CrossClusterSearchCompatibilityWarning } from './cross_cluster_search_warning';
|
||||
|
@ -65,9 +73,16 @@ export function FailedTransactionsCorrelations({
|
|||
const inspectEnabled = uiSettings.get<boolean>(enableInspectEsQueries);
|
||||
|
||||
const { progress, response, startFetch, cancelFetch } = useSearchStrategy(
|
||||
APM_SEARCH_STRATEGIES.APM_FAILED_TRANSACTIONS_CORRELATIONS
|
||||
APM_SEARCH_STRATEGIES.APM_FAILED_TRANSACTIONS_CORRELATIONS,
|
||||
{
|
||||
percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD,
|
||||
}
|
||||
);
|
||||
const progressNormalized = progress.loaded / progress.total;
|
||||
const { overallHistogram, hasData, status } = getOverallHistogram(
|
||||
response,
|
||||
progress.isRunning
|
||||
);
|
||||
|
||||
const [
|
||||
selectedSignificantTerm,
|
||||
|
@ -86,6 +101,13 @@ export function FailedTransactionsCorrelations({
|
|||
EuiBasicTableColumn<FailedTransactionsCorrelation>
|
||||
> = inspectEnabled
|
||||
? [
|
||||
{
|
||||
width: '100px',
|
||||
field: 'pValue',
|
||||
name: 'p-value',
|
||||
render: (pValue: number) => pValue.toPrecision(3),
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
width: '100px',
|
||||
field: 'failurePercentage',
|
||||
|
@ -157,6 +179,7 @@ export function FailedTransactionsCorrelations({
|
|||
: [];
|
||||
return [
|
||||
{
|
||||
width: '116px',
|
||||
field: 'normalizedScore',
|
||||
name: (
|
||||
<>
|
||||
|
@ -350,6 +373,35 @@ export function FailedTransactionsCorrelations({
|
|||
const showSummaryBadge =
|
||||
inspectEnabled && (progress.isRunning || correlationTerms.length > 0);
|
||||
|
||||
const transactionDistributionChartData: TransactionDistributionChartData[] = [];
|
||||
|
||||
if (Array.isArray(overallHistogram)) {
|
||||
transactionDistributionChartData.push({
|
||||
id: i18n.translate(
|
||||
'xpack.apm.transactionDistribution.chart.allTransactionsLabel',
|
||||
{ defaultMessage: 'All transactions' }
|
||||
),
|
||||
histogram: overallHistogram,
|
||||
});
|
||||
}
|
||||
|
||||
if (Array.isArray(response.errorHistogram)) {
|
||||
transactionDistributionChartData.push({
|
||||
id: i18n.translate(
|
||||
'xpack.apm.transactionDistribution.chart.allFailedTransactionsLabel',
|
||||
{ defaultMessage: 'All failed transactions' }
|
||||
),
|
||||
histogram: response.errorHistogram,
|
||||
});
|
||||
}
|
||||
|
||||
if (selectedTerm && Array.isArray(selectedTerm.histogram)) {
|
||||
transactionDistributionChartData.push({
|
||||
id: `${selectedTerm.fieldName}:${selectedTerm.fieldValue}`,
|
||||
histogram: selectedTerm.histogram,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-test-subj="apmFailedTransactionsCorrelationsTabContent">
|
||||
<EuiFlexItem style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
|
@ -363,7 +415,7 @@ export function FailedTransactionsCorrelations({
|
|||
{i18n.translate(
|
||||
'xpack.apm.correlations.failedTransactions.panelTitle',
|
||||
{
|
||||
defaultMessage: 'Failed transactions',
|
||||
defaultMessage: 'Failed transactions latency distribution',
|
||||
}
|
||||
)}
|
||||
</h5>
|
||||
|
@ -402,6 +454,16 @@ export function FailedTransactionsCorrelations({
|
|||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<TransactionDistributionChart
|
||||
markerPercentile={DEFAULT_PERCENTILE_THRESHOLD}
|
||||
markerValue={response.percentileThresholdValue ?? 0}
|
||||
data={transactionDistributionChartData}
|
||||
hasData={hasData}
|
||||
status={status}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiTitle size="xs">
|
||||
<span data-test-subj="apmFailedTransactionsCorrelationsTablePanelTitle">
|
||||
{i18n.translate(
|
||||
|
|
|
@ -39,7 +39,10 @@ import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_
|
|||
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
|
||||
import { useSearchStrategy } from '../../../hooks/use_search_strategy';
|
||||
|
||||
import { TransactionDistributionChart } from '../../shared/charts/transaction_distribution_chart';
|
||||
import {
|
||||
TransactionDistributionChart,
|
||||
TransactionDistributionChartData,
|
||||
} from '../../shared/charts/transaction_distribution_chart';
|
||||
import { push } from '../../shared/Links/url_helpers';
|
||||
|
||||
import { CorrelationsTable } from './correlations_table';
|
||||
|
@ -239,6 +242,25 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) {
|
|||
histogramTerms.length < 1 &&
|
||||
(progressNormalized === 1 || !progress.isRunning);
|
||||
|
||||
const transactionDistributionChartData: TransactionDistributionChartData[] = [];
|
||||
|
||||
if (Array.isArray(overallHistogram)) {
|
||||
transactionDistributionChartData.push({
|
||||
id: i18n.translate(
|
||||
'xpack.apm.transactionDistribution.chart.allTransactionsLabel',
|
||||
{ defaultMessage: 'All transactions' }
|
||||
),
|
||||
histogram: overallHistogram,
|
||||
});
|
||||
}
|
||||
|
||||
if (selectedHistogram && Array.isArray(selectedHistogram.histogram)) {
|
||||
transactionDistributionChartData.push({
|
||||
id: `${selectedHistogram.fieldName}:${selectedHistogram.fieldValue}`,
|
||||
histogram: selectedHistogram.histogram,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-test-subj="apmLatencyCorrelationsTabContent">
|
||||
<EuiFlexGroup>
|
||||
|
@ -264,8 +286,7 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) {
|
|||
<TransactionDistributionChart
|
||||
markerPercentile={DEFAULT_PERCENTILE_THRESHOLD}
|
||||
markerValue={response.percentileThresholdValue ?? 0}
|
||||
{...selectedHistogram}
|
||||
overallHistogram={overallHistogram}
|
||||
data={transactionDistributionChartData}
|
||||
hasData={hasData}
|
||||
status={status}
|
||||
/>
|
||||
|
|
|
@ -17,18 +17,24 @@ import {
|
|||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useUiTracker } from '../../../../../../observability/public';
|
||||
|
||||
import { getDurationFormatter } from '../../../../../common/utils/formatters';
|
||||
import {
|
||||
APM_SEARCH_STRATEGIES,
|
||||
DEFAULT_PERCENTILE_THRESHOLD,
|
||||
} from '../../../../../common/search_strategies/constants';
|
||||
|
||||
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
|
||||
import { useSearchStrategy } from '../../../../hooks/use_search_strategy';
|
||||
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
|
||||
|
||||
import { TransactionDistributionChart } from '../../../shared/charts/transaction_distribution_chart';
|
||||
import { useUiTracker } from '../../../../../../observability/public';
|
||||
import {
|
||||
TransactionDistributionChart,
|
||||
TransactionDistributionChartData,
|
||||
} from '../../../shared/charts/transaction_distribution_chart';
|
||||
import { isErrorMessage } from '../../correlations/utils/is_error_message';
|
||||
import { getOverallHistogram } from '../../correlations/utils/get_overall_histogram';
|
||||
|
||||
|
@ -132,6 +138,18 @@ export function TransactionDistribution({
|
|||
trackApmEvent({ metric: 'transaction_distribution_chart_clear_selection' });
|
||||
};
|
||||
|
||||
const transactionDistributionChartData: TransactionDistributionChartData[] = [];
|
||||
|
||||
if (Array.isArray(overallHistogram)) {
|
||||
transactionDistributionChartData.push({
|
||||
id: i18n.translate(
|
||||
'xpack.apm.transactionDistribution.chart.allTransactionsLabel',
|
||||
{ defaultMessage: 'All transactions' }
|
||||
),
|
||||
histogram: overallHistogram,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-test-subj="apmTransactionDistributionTabContent">
|
||||
<EuiFlexGroup style={{ minHeight: MIN_TAB_TITLE_HEIGHT }}>
|
||||
|
@ -193,10 +211,10 @@ export function TransactionDistribution({
|
|||
<EuiSpacer size="s" />
|
||||
|
||||
<TransactionDistributionChart
|
||||
data={transactionDistributionChartData}
|
||||
markerCurrentTransaction={markerCurrentTransaction}
|
||||
markerPercentile={DEFAULT_PERCENTILE_THRESHOLD}
|
||||
markerValue={response.percentileThresholdValue ?? 0}
|
||||
overallHistogram={overallHistogram}
|
||||
onChartSelection={onTrackedChartSelection}
|
||||
hasData={hasData}
|
||||
selection={selection}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { HistogramItem } from '../../../../../common/search_strategies/types';
|
||||
|
||||
import { replaceHistogramDotsWithBars } from './index';
|
||||
|
||||
describe('TransactionDistributionChart', () => {
|
||||
describe('replaceHistogramDotsWithBars', () => {
|
||||
it('does the thing', () => {
|
||||
const mockHistogram = [
|
||||
{ doc_count: 10 },
|
||||
{ doc_count: 10 },
|
||||
{ doc_count: 0 },
|
||||
{ doc_count: 0 },
|
||||
{ doc_count: 0 },
|
||||
{ doc_count: 10 },
|
||||
{ doc_count: 10 },
|
||||
{ doc_count: 0 },
|
||||
{ doc_count: 10 },
|
||||
{ doc_count: 10 },
|
||||
] as HistogramItem[];
|
||||
|
||||
expect(replaceHistogramDotsWithBars(mockHistogram)).toEqual([
|
||||
{ doc_count: 10 },
|
||||
{ doc_count: 10 },
|
||||
{ doc_count: 0.0001 },
|
||||
{ doc_count: 0 },
|
||||
{ doc_count: 0 },
|
||||
{ doc_count: 10 },
|
||||
{ doc_count: 10 },
|
||||
{ doc_count: 0.0001 },
|
||||
{ doc_count: 10 },
|
||||
{ doc_count: 10 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,7 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import { flatten } from 'lodash';
|
||||
|
||||
import {
|
||||
AnnotationDomainType,
|
||||
AreaSeries,
|
||||
|
@ -30,25 +32,24 @@ import { i18n } from '@kbn/i18n';
|
|||
import { useChartTheme } from '../../../../../../observability/public';
|
||||
|
||||
import { getDurationFormatter } from '../../../../../common/utils/formatters';
|
||||
import type {
|
||||
FieldValuePair,
|
||||
HistogramItem,
|
||||
} from '../../../../../common/search_strategies/types';
|
||||
import type { HistogramItem } from '../../../../../common/search_strategies/types';
|
||||
|
||||
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
|
||||
import { useTheme } from '../../../../hooks/use_theme';
|
||||
|
||||
import { ChartContainer } from '../chart_container';
|
||||
|
||||
export interface TransactionDistributionChartData {
|
||||
id: string;
|
||||
histogram: HistogramItem[];
|
||||
}
|
||||
|
||||
interface TransactionDistributionChartProps {
|
||||
fieldName?: FieldValuePair['fieldName'];
|
||||
fieldValue?: FieldValuePair['fieldValue'];
|
||||
data: TransactionDistributionChartData[];
|
||||
hasData: boolean;
|
||||
histogram?: HistogramItem[];
|
||||
markerCurrentTransaction?: number;
|
||||
markerValue: number;
|
||||
markerPercentile: number;
|
||||
overallHistogram?: HistogramItem[];
|
||||
onChartSelection?: BrushEndListener;
|
||||
selection?: [number, number];
|
||||
status: FETCH_STATUS;
|
||||
|
@ -69,29 +70,24 @@ const getAnnotationsStyle = (color = 'gray'): LineAnnotationStyle => ({
|
|||
},
|
||||
});
|
||||
|
||||
// TODO Revisit this approach since it actually manipulates the numbers
|
||||
// showing in the chart and its tooltips.
|
||||
const CHART_PLACEHOLDER_VALUE = 0.0001;
|
||||
|
||||
// Elastic charts will show any lone bin (i.e. a populated bin followed by empty bin)
|
||||
// as a circular marker instead of a bar
|
||||
// This provides a workaround by making the next bin not empty
|
||||
export const replaceHistogramDotsWithBars = (
|
||||
originalHistogram: HistogramItem[] | undefined
|
||||
) => {
|
||||
if (originalHistogram === undefined) return;
|
||||
const histogram = [...originalHistogram];
|
||||
{
|
||||
for (let i = 0; i < histogram.length - 1; i++) {
|
||||
if (
|
||||
histogram[i].doc_count > 0 &&
|
||||
histogram[i].doc_count !== CHART_PLACEHOLDER_VALUE &&
|
||||
histogram[i + 1].doc_count === 0
|
||||
) {
|
||||
histogram[i + 1].doc_count = CHART_PLACEHOLDER_VALUE;
|
||||
}
|
||||
export const replaceHistogramDotsWithBars = (histogramItems: HistogramItem[]) =>
|
||||
histogramItems.reduce((histogramItem, _, i) => {
|
||||
if (
|
||||
histogramItem[i - 1]?.doc_count > 0 &&
|
||||
histogramItem[i - 1]?.doc_count !== CHART_PLACEHOLDER_VALUE &&
|
||||
histogramItem[i].doc_count === 0
|
||||
) {
|
||||
histogramItem[i].doc_count = CHART_PLACEHOLDER_VALUE;
|
||||
}
|
||||
return histogram;
|
||||
}
|
||||
};
|
||||
return histogramItem;
|
||||
}, histogramItems);
|
||||
|
||||
// Create and call a duration formatter for every value since the durations for the
|
||||
// x axis might have a wide range of values e.g. from low milliseconds to large seconds.
|
||||
|
@ -100,14 +96,11 @@ const xAxisTickFormat: TickFormatter<number> = (d) =>
|
|||
getDurationFormatter(d, 0.9999)(d).formatted;
|
||||
|
||||
export function TransactionDistributionChart({
|
||||
fieldName,
|
||||
fieldValue,
|
||||
data,
|
||||
hasData,
|
||||
histogram: originalHistogram,
|
||||
markerCurrentTransaction,
|
||||
markerValue,
|
||||
markerPercentile,
|
||||
overallHistogram,
|
||||
onChartSelection,
|
||||
selection,
|
||||
status,
|
||||
|
@ -115,10 +108,11 @@ export function TransactionDistributionChart({
|
|||
const chartTheme = useChartTheme();
|
||||
const euiTheme = useTheme();
|
||||
|
||||
const patchedOverallHistogram = useMemo(
|
||||
() => replaceHistogramDotsWithBars(overallHistogram),
|
||||
[overallHistogram]
|
||||
);
|
||||
const areaSeriesColors = [
|
||||
euiTheme.eui.euiColorVis1,
|
||||
euiTheme.eui.euiColorVis2,
|
||||
euiTheme.eui.euiColorVis5,
|
||||
];
|
||||
|
||||
const annotationsDataValues: LineAnnotationDatum[] = [
|
||||
{
|
||||
|
@ -137,15 +131,15 @@ export function TransactionDistributionChart({
|
|||
|
||||
// This will create y axis ticks for 1, 10, 100, 1000 ...
|
||||
const yMax =
|
||||
Math.max(...(overallHistogram ?? []).map((d) => d.doc_count)) ?? 0;
|
||||
Math.max(
|
||||
...flatten(data.map((d) => d.histogram)).map((d) => d.doc_count)
|
||||
) ?? 0;
|
||||
const yTicks = Math.ceil(Math.log10(yMax));
|
||||
const yAxisDomain = {
|
||||
min: 0.9,
|
||||
max: Math.pow(10, yTicks),
|
||||
};
|
||||
|
||||
const histogram = replaceHistogramDotsWithBars(originalHistogram);
|
||||
|
||||
const selectionAnnotation =
|
||||
selection !== undefined
|
||||
? [
|
||||
|
@ -260,35 +254,19 @@ export function TransactionDistributionChart({
|
|||
ticks={yTicks}
|
||||
gridLine={{ visible: true }}
|
||||
/>
|
||||
<AreaSeries
|
||||
id={i18n.translate(
|
||||
'xpack.apm.transactionDistribution.chart.allTransactionsLabel',
|
||||
{ defaultMessage: 'All transactions' }
|
||||
)}
|
||||
xScaleType={ScaleType.Log}
|
||||
yScaleType={ScaleType.Log}
|
||||
data={patchedOverallHistogram ?? []}
|
||||
curve={CurveType.CURVE_STEP_AFTER}
|
||||
xAccessor="key"
|
||||
yAccessors={['doc_count']}
|
||||
color={euiTheme.eui.euiColorVis1}
|
||||
fit="lookahead"
|
||||
/>
|
||||
{Array.isArray(histogram) &&
|
||||
fieldName !== undefined &&
|
||||
fieldValue !== undefined && (
|
||||
<AreaSeries
|
||||
// id is used as the label for the legend
|
||||
id={`${fieldName}:${fieldValue}`}
|
||||
xScaleType={ScaleType.Log}
|
||||
yScaleType={ScaleType.Log}
|
||||
data={histogram}
|
||||
curve={CurveType.CURVE_STEP_AFTER}
|
||||
xAccessor="key"
|
||||
yAccessors={['doc_count']}
|
||||
color={euiTheme.eui.euiColorVis2}
|
||||
/>
|
||||
)}
|
||||
{data.map((d, i) => (
|
||||
<AreaSeries
|
||||
id={d.id}
|
||||
xScaleType={ScaleType.Log}
|
||||
yScaleType={ScaleType.Log}
|
||||
data={replaceHistogramDotsWithBars(d.histogram)}
|
||||
curve={CurveType.CURVE_STEP_AFTER}
|
||||
xAccessor="key"
|
||||
yAccessors={['doc_count']}
|
||||
color={areaSeriesColors[i]}
|
||||
fit="lookahead"
|
||||
/>
|
||||
))}
|
||||
</Chart>
|
||||
</ChartContainer>
|
||||
</div>
|
||||
|
|
|
@ -18,8 +18,14 @@ import { useKibana } from '../../../../../src/plugins/kibana_react/public';
|
|||
|
||||
import type { SearchStrategyClientParams } from '../../common/search_strategies/types';
|
||||
import type { RawResponseBase } from '../../common/search_strategies/types';
|
||||
import type { LatencyCorrelationsRawResponse } from '../../common/search_strategies/latency_correlations/types';
|
||||
import type { FailedTransactionsCorrelationsRawResponse } from '../../common/search_strategies/failed_transactions_correlations/types';
|
||||
import type {
|
||||
LatencyCorrelationsParams,
|
||||
LatencyCorrelationsRawResponse,
|
||||
} from '../../common/search_strategies/latency_correlations/types';
|
||||
import type {
|
||||
FailedTransactionsCorrelationsParams,
|
||||
FailedTransactionsCorrelationsRawResponse,
|
||||
} from '../../common/search_strategies/failed_transactions_correlations/types';
|
||||
import {
|
||||
ApmSearchStrategies,
|
||||
APM_SEARCH_STRATEGIES,
|
||||
|
@ -58,8 +64,9 @@ const getReducer = <T>() => (prev: T, update: Partial<T>): T => ({
|
|||
...update,
|
||||
});
|
||||
|
||||
interface SearchStrategyReturnBase {
|
||||
interface SearchStrategyReturnBase<TRawResponse extends RawResponseBase> {
|
||||
progress: SearchStrategyProgress;
|
||||
response: TRawResponse;
|
||||
startFetch: () => void;
|
||||
cancelFetch: () => void;
|
||||
}
|
||||
|
@ -67,25 +74,22 @@ interface SearchStrategyReturnBase {
|
|||
// Function overload for Latency Correlations
|
||||
export function useSearchStrategy(
|
||||
searchStrategyName: typeof APM_SEARCH_STRATEGIES.APM_LATENCY_CORRELATIONS,
|
||||
options: {
|
||||
percentileThreshold: number;
|
||||
analyzeCorrelations: boolean;
|
||||
}
|
||||
): {
|
||||
response: LatencyCorrelationsRawResponse;
|
||||
} & SearchStrategyReturnBase;
|
||||
searchStrategyParams: LatencyCorrelationsParams
|
||||
): SearchStrategyReturnBase<LatencyCorrelationsRawResponse>;
|
||||
|
||||
// Function overload for Failed Transactions Correlations
|
||||
export function useSearchStrategy(
|
||||
searchStrategyName: typeof APM_SEARCH_STRATEGIES.APM_FAILED_TRANSACTIONS_CORRELATIONS
|
||||
): {
|
||||
response: FailedTransactionsCorrelationsRawResponse;
|
||||
} & SearchStrategyReturnBase;
|
||||
searchStrategyName: typeof APM_SEARCH_STRATEGIES.APM_FAILED_TRANSACTIONS_CORRELATIONS,
|
||||
searchStrategyParams: FailedTransactionsCorrelationsParams
|
||||
): SearchStrategyReturnBase<FailedTransactionsCorrelationsRawResponse>;
|
||||
|
||||
export function useSearchStrategy<
|
||||
TRawResponse extends RawResponseBase,
|
||||
TOptions = unknown
|
||||
>(searchStrategyName: ApmSearchStrategies, options?: TOptions): unknown {
|
||||
TParams = unknown
|
||||
>(
|
||||
searchStrategyName: ApmSearchStrategies,
|
||||
searchStrategyParams?: TParams
|
||||
): SearchStrategyReturnBase<TRawResponse> {
|
||||
const {
|
||||
services: { data },
|
||||
} = useKibana<ApmPluginStartDeps>();
|
||||
|
@ -110,7 +114,7 @@ export function useSearchStrategy<
|
|||
|
||||
const abortCtrl = useRef(new AbortController());
|
||||
const searchSubscription$ = useRef<Subscription>();
|
||||
const optionsRef = useRef(options);
|
||||
const searchStrategyParamsRef = useRef(searchStrategyParams);
|
||||
|
||||
const startFetch = useCallback(() => {
|
||||
searchSubscription$.current?.unsubscribe();
|
||||
|
@ -130,14 +134,16 @@ export function useSearchStrategy<
|
|||
kuery,
|
||||
start,
|
||||
end,
|
||||
...(optionsRef.current ? { ...optionsRef.current } : {}),
|
||||
...(searchStrategyParamsRef.current
|
||||
? { ...searchStrategyParamsRef.current }
|
||||
: {}),
|
||||
},
|
||||
};
|
||||
|
||||
// Submit the search request using the `data.search` service.
|
||||
searchSubscription$.current = data.search
|
||||
.search<
|
||||
IKibanaSearchRequest<SearchStrategyClientParams & (TOptions | {})>,
|
||||
IKibanaSearchRequest<SearchStrategyClientParams & (TParams | {})>,
|
||||
IKibanaSearchResponse<TRawResponse>
|
||||
>(request, {
|
||||
strategy: searchStrategyName,
|
||||
|
|
|
@ -16,9 +16,10 @@ import {
|
|||
} from '../../../../../../../src/plugins/data/common';
|
||||
|
||||
import { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames';
|
||||
import type { SearchStrategyParams } from '../../../../common/search_strategies/types';
|
||||
import { EventOutcome } from '../../../../common/event_outcome';
|
||||
import type { SearchStrategyServerParams } from '../../../../common/search_strategies/types';
|
||||
import type {
|
||||
FailedTransactionsCorrelationsParams,
|
||||
FailedTransactionsCorrelationsRequestParams,
|
||||
FailedTransactionsCorrelationsRawResponse,
|
||||
} from '../../../../common/search_strategies/failed_transactions_correlations/types';
|
||||
import type { ApmIndicesConfig } from '../../settings/apm_indices/get_apm_indices';
|
||||
|
@ -26,6 +27,9 @@ import { searchServiceLogProvider } from '../search_service_log';
|
|||
import {
|
||||
fetchFailedTransactionsCorrelationPValues,
|
||||
fetchTransactionDurationFieldCandidates,
|
||||
fetchTransactionDurationPercentiles,
|
||||
fetchTransactionDurationRanges,
|
||||
fetchTransactionDurationHistogramRangeSteps,
|
||||
} from '../queries';
|
||||
import type { SearchServiceProvider } from '../search_strategy_provider';
|
||||
|
||||
|
@ -34,19 +38,19 @@ import { failedTransactionsCorrelationsSearchServiceStateProvider } from './fail
|
|||
import { ERROR_CORRELATION_THRESHOLD } from '../constants';
|
||||
|
||||
export type FailedTransactionsCorrelationsSearchServiceProvider = SearchServiceProvider<
|
||||
FailedTransactionsCorrelationsParams,
|
||||
FailedTransactionsCorrelationsRequestParams,
|
||||
FailedTransactionsCorrelationsRawResponse
|
||||
>;
|
||||
|
||||
export type FailedTransactionsCorrelationsSearchStrategy = ISearchStrategy<
|
||||
IKibanaSearchRequest<FailedTransactionsCorrelationsParams>,
|
||||
IKibanaSearchRequest<FailedTransactionsCorrelationsRequestParams>,
|
||||
IKibanaSearchResponse<FailedTransactionsCorrelationsRawResponse>
|
||||
>;
|
||||
|
||||
export const failedTransactionsCorrelationsSearchServiceProvider: FailedTransactionsCorrelationsSearchServiceProvider = (
|
||||
esClient: ElasticsearchClient,
|
||||
getApmIndices: () => Promise<ApmIndicesConfig>,
|
||||
searchServiceParams: FailedTransactionsCorrelationsParams,
|
||||
searchServiceParams: FailedTransactionsCorrelationsRequestParams,
|
||||
includeFrozen: boolean
|
||||
) => {
|
||||
const { addLogMessage, getLogMessages } = searchServiceLogProvider();
|
||||
|
@ -56,12 +60,66 @@ export const failedTransactionsCorrelationsSearchServiceProvider: FailedTransact
|
|||
async function fetchErrorCorrelations() {
|
||||
try {
|
||||
const indices = await getApmIndices();
|
||||
const params: SearchStrategyParams = {
|
||||
const params: FailedTransactionsCorrelationsRequestParams &
|
||||
SearchStrategyServerParams = {
|
||||
...searchServiceParams,
|
||||
index: indices['apm_oss.transactionIndices'],
|
||||
includeFrozen,
|
||||
};
|
||||
|
||||
// 95th percentile to be displayed as a marker in the log log chart
|
||||
const {
|
||||
totalDocs,
|
||||
percentiles: percentilesResponseThresholds,
|
||||
} = await fetchTransactionDurationPercentiles(
|
||||
esClient,
|
||||
params,
|
||||
params.percentileThreshold ? [params.percentileThreshold] : undefined
|
||||
);
|
||||
const percentileThresholdValue =
|
||||
percentilesResponseThresholds[`${params.percentileThreshold}.0`];
|
||||
state.setPercentileThresholdValue(percentileThresholdValue);
|
||||
|
||||
addLogMessage(
|
||||
`Fetched ${params.percentileThreshold}th percentile value of ${percentileThresholdValue} based on ${totalDocs} documents.`
|
||||
);
|
||||
|
||||
// finish early if we weren't able to identify the percentileThresholdValue.
|
||||
if (percentileThresholdValue === undefined) {
|
||||
addLogMessage(
|
||||
`Abort service since percentileThresholdValue could not be determined.`
|
||||
);
|
||||
state.setProgress({
|
||||
loadedFieldCandidates: 1,
|
||||
loadedErrorCorrelations: 1,
|
||||
loadedOverallHistogram: 1,
|
||||
loadedFailedTransactionsCorrelations: 1,
|
||||
});
|
||||
state.setIsRunning(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const histogramRangeSteps = await fetchTransactionDurationHistogramRangeSteps(
|
||||
esClient,
|
||||
params
|
||||
);
|
||||
|
||||
const overallLogHistogramChartData = await fetchTransactionDurationRanges(
|
||||
esClient,
|
||||
params,
|
||||
histogramRangeSteps
|
||||
);
|
||||
const errorLogHistogramChartData = await fetchTransactionDurationRanges(
|
||||
esClient,
|
||||
params,
|
||||
histogramRangeSteps,
|
||||
[{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure }]
|
||||
);
|
||||
|
||||
state.setProgress({ loadedOverallHistogram: 1 });
|
||||
state.setErrorHistogram(errorLogHistogramChartData);
|
||||
state.setOverallHistogram(overallLogHistogramChartData);
|
||||
|
||||
const {
|
||||
fieldCandidates: candidates,
|
||||
} = await fetchTransactionDurationFieldCandidates(esClient, params);
|
||||
|
@ -82,6 +140,7 @@ export const failedTransactionsCorrelationsSearchServiceProvider: FailedTransact
|
|||
fetchFailedTransactionsCorrelationPValues(
|
||||
esClient,
|
||||
params,
|
||||
histogramRangeSteps,
|
||||
fieldName
|
||||
)
|
||||
)
|
||||
|
@ -139,7 +198,15 @@ export const failedTransactionsCorrelationsSearchServiceProvider: FailedTransact
|
|||
fetchErrorCorrelations();
|
||||
|
||||
return () => {
|
||||
const { ccsWarning, error, isRunning, progress } = state.getState();
|
||||
const {
|
||||
ccsWarning,
|
||||
error,
|
||||
isRunning,
|
||||
overallHistogram,
|
||||
errorHistogram,
|
||||
percentileThresholdValue,
|
||||
progress,
|
||||
} = state.getState();
|
||||
|
||||
return {
|
||||
cancel: () => {
|
||||
|
@ -158,6 +225,9 @@ export const failedTransactionsCorrelationsSearchServiceProvider: FailedTransact
|
|||
log: getLogMessages(),
|
||||
took: Date.now() - progress.started,
|
||||
failedTransactionsCorrelations: state.getFailedTransactionsCorrelationsSortedByScore(),
|
||||
overallHistogram,
|
||||
errorHistogram,
|
||||
percentileThresholdValue,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -7,11 +7,16 @@
|
|||
|
||||
import { FailedTransactionsCorrelation } from '../../../../common/search_strategies/failed_transactions_correlations/types';
|
||||
|
||||
import type { HistogramItem } from '../../../../common/search_strategies/types';
|
||||
|
||||
interface Progress {
|
||||
started: number;
|
||||
loadedFieldCandidates: number;
|
||||
loadedErrorCorrelations: number;
|
||||
loadedOverallHistogram: number;
|
||||
loadedFailedTransactionsCorrelations: number;
|
||||
}
|
||||
|
||||
export const failedTransactionsCorrelationsSearchServiceStateProvider = () => {
|
||||
let ccsWarning = false;
|
||||
function setCcsWarning(d: boolean) {
|
||||
|
@ -33,9 +38,26 @@ export const failedTransactionsCorrelationsSearchServiceStateProvider = () => {
|
|||
isRunning = d;
|
||||
}
|
||||
|
||||
let errorHistogram: HistogramItem[] | undefined;
|
||||
function setErrorHistogram(d: HistogramItem[]) {
|
||||
errorHistogram = d;
|
||||
}
|
||||
|
||||
let overallHistogram: HistogramItem[] | undefined;
|
||||
function setOverallHistogram(d: HistogramItem[]) {
|
||||
overallHistogram = d;
|
||||
}
|
||||
|
||||
let percentileThresholdValue: number;
|
||||
function setPercentileThresholdValue(d: number) {
|
||||
percentileThresholdValue = d;
|
||||
}
|
||||
|
||||
let progress: Progress = {
|
||||
started: Date.now(),
|
||||
loadedFieldCandidates: 0,
|
||||
loadedErrorCorrelations: 0,
|
||||
loadedOverallHistogram: 0,
|
||||
loadedFailedTransactionsCorrelations: 0,
|
||||
};
|
||||
function getOverallProgress() {
|
||||
|
@ -71,6 +93,9 @@ export const failedTransactionsCorrelationsSearchServiceStateProvider = () => {
|
|||
error,
|
||||
isCancelled,
|
||||
isRunning,
|
||||
overallHistogram,
|
||||
errorHistogram,
|
||||
percentileThresholdValue,
|
||||
progress,
|
||||
failedTransactionsCorrelations,
|
||||
};
|
||||
|
@ -86,6 +111,9 @@ export const failedTransactionsCorrelationsSearchServiceStateProvider = () => {
|
|||
setError,
|
||||
setIsCancelled,
|
||||
setIsRunning,
|
||||
setOverallHistogram,
|
||||
setErrorHistogram,
|
||||
setPercentileThresholdValue,
|
||||
setProgress,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
|
||||
import type { SearchStrategyServerParams } from '../../../../common/search_strategies/types';
|
||||
import type {
|
||||
LatencyCorrelationsParams,
|
||||
LatencyCorrelationsRequestParams,
|
||||
LatencyCorrelationsRawResponse,
|
||||
} from '../../../../common/search_strategies/latency_correlations/types';
|
||||
|
||||
|
@ -38,19 +38,19 @@ import type { SearchServiceProvider } from '../search_strategy_provider';
|
|||
import { latencyCorrelationsSearchServiceStateProvider } from './latency_correlations_search_service_state';
|
||||
|
||||
export type LatencyCorrelationsSearchServiceProvider = SearchServiceProvider<
|
||||
LatencyCorrelationsParams,
|
||||
LatencyCorrelationsRequestParams,
|
||||
LatencyCorrelationsRawResponse
|
||||
>;
|
||||
|
||||
export type LatencyCorrelationsSearchStrategy = ISearchStrategy<
|
||||
IKibanaSearchRequest<LatencyCorrelationsParams>,
|
||||
IKibanaSearchRequest<LatencyCorrelationsRequestParams>,
|
||||
IKibanaSearchResponse<LatencyCorrelationsRawResponse>
|
||||
>;
|
||||
|
||||
export const latencyCorrelationsSearchServiceProvider: LatencyCorrelationsSearchServiceProvider = (
|
||||
esClient: ElasticsearchClient,
|
||||
getApmIndices: () => Promise<ApmIndicesConfig>,
|
||||
searchServiceParams: LatencyCorrelationsParams,
|
||||
searchServiceParams: LatencyCorrelationsRequestParams,
|
||||
includeFrozen: boolean
|
||||
) => {
|
||||
const { addLogMessage, getLogMessages } = searchServiceLogProvider();
|
||||
|
@ -59,7 +59,7 @@ export const latencyCorrelationsSearchServiceProvider: LatencyCorrelationsSearch
|
|||
|
||||
async function fetchCorrelations() {
|
||||
let params:
|
||||
| (LatencyCorrelationsParams & SearchStrategyServerParams)
|
||||
| (LatencyCorrelationsRequestParams & SearchStrategyServerParams)
|
||||
| undefined;
|
||||
|
||||
try {
|
||||
|
|
|
@ -99,8 +99,12 @@ describe('correlations', () => {
|
|||
environment: ENVIRONMENT_ALL.value,
|
||||
kuery: '',
|
||||
},
|
||||
fieldName: 'actualFieldName',
|
||||
fieldValue: 'actualFieldValue',
|
||||
termFilters: [
|
||||
{
|
||||
fieldName: 'actualFieldName',
|
||||
fieldValue: 'actualFieldValue',
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(query).toEqual({
|
||||
bool: {
|
||||
|
|
|
@ -18,23 +18,15 @@ import { rangeRt } from '../../../routes/default_api_types';
|
|||
import { getCorrelationsFilters } from '../../correlations/get_filters';
|
||||
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
|
||||
|
||||
export const getTermsQuery = (
|
||||
fieldName: FieldValuePair['fieldName'] | undefined,
|
||||
fieldValue: FieldValuePair['fieldValue'] | undefined
|
||||
) => {
|
||||
return fieldName && fieldValue ? [{ term: { [fieldName]: fieldValue } }] : [];
|
||||
export const getTermsQuery = ({ fieldName, fieldValue }: FieldValuePair) => {
|
||||
return { term: { [fieldName]: fieldValue } };
|
||||
};
|
||||
|
||||
interface QueryParams {
|
||||
params: SearchStrategyParams;
|
||||
fieldName?: FieldValuePair['fieldName'];
|
||||
fieldValue?: FieldValuePair['fieldValue'];
|
||||
termFilters?: FieldValuePair[];
|
||||
}
|
||||
export const getQueryWithParams = ({
|
||||
params,
|
||||
fieldName,
|
||||
fieldValue,
|
||||
}: QueryParams) => {
|
||||
export const getQueryWithParams = ({ params, termFilters }: QueryParams) => {
|
||||
const {
|
||||
environment,
|
||||
kuery,
|
||||
|
@ -53,7 +45,7 @@ export const getQueryWithParams = ({
|
|||
})
|
||||
) as Setup & SetupTimeRange;
|
||||
|
||||
const filters = getCorrelationsFilters({
|
||||
const correlationFilters = getCorrelationsFilters({
|
||||
setup,
|
||||
environment,
|
||||
kuery,
|
||||
|
@ -65,8 +57,8 @@ export const getQueryWithParams = ({
|
|||
return {
|
||||
bool: {
|
||||
filter: [
|
||||
...filters,
|
||||
...getTermsQuery(fieldName, fieldValue),
|
||||
...correlationFilters,
|
||||
...(Array.isArray(termFilters) ? termFilters.map(getTermsQuery) : []),
|
||||
] as estypes.QueryDslQueryContainer[],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -38,10 +38,9 @@ export const getTransactionDurationCorrelationRequest = (
|
|||
ranges: estypes.AggregationsAggregationRange[],
|
||||
fractions: number[],
|
||||
totalDocCount: number,
|
||||
fieldName?: FieldValuePair['fieldName'],
|
||||
fieldValue?: FieldValuePair['fieldValue']
|
||||
termFilters?: FieldValuePair[]
|
||||
): estypes.SearchRequest => {
|
||||
const query = getQueryWithParams({ params, fieldName, fieldValue });
|
||||
const query = getQueryWithParams({ params, termFilters });
|
||||
|
||||
const bucketCorrelation: BucketCorrelation = {
|
||||
buckets_path: 'latency_ranges>_count',
|
||||
|
@ -93,8 +92,7 @@ export const fetchTransactionDurationCorrelation = async (
|
|||
ranges: estypes.AggregationsAggregationRange[],
|
||||
fractions: number[],
|
||||
totalDocCount: number,
|
||||
fieldName?: FieldValuePair['fieldName'],
|
||||
fieldValue?: FieldValuePair['fieldValue']
|
||||
termFilters?: FieldValuePair[]
|
||||
): Promise<{
|
||||
ranges: unknown[];
|
||||
correlation: number | null;
|
||||
|
@ -107,8 +105,7 @@ export const fetchTransactionDurationCorrelation = async (
|
|||
ranges,
|
||||
fractions,
|
||||
totalDocCount,
|
||||
fieldName,
|
||||
fieldValue
|
||||
termFilters
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import { ElasticsearchClient } from 'kibana/server';
|
|||
import { SearchStrategyParams } from '../../../../common/search_strategies/types';
|
||||
import { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames';
|
||||
import { EventOutcome } from '../../../../common/event_outcome';
|
||||
import { fetchTransactionDurationRanges } from './query_ranges';
|
||||
import { getQueryWithParams, getTermsQuery } from './get_query_with_params';
|
||||
import { getRequestBase } from './get_request_base';
|
||||
|
||||
|
@ -26,7 +27,12 @@ export const getFailureCorrelationRequest = (
|
|||
...query.bool,
|
||||
filter: [
|
||||
...query.bool.filter,
|
||||
...getTermsQuery(EVENT_OUTCOME, EventOutcome.failure),
|
||||
...[
|
||||
getTermsQuery({
|
||||
fieldName: EVENT_OUTCOME,
|
||||
fieldValue: EventOutcome.failure,
|
||||
}),
|
||||
],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@ -60,6 +66,7 @@ export const getFailureCorrelationRequest = (
|
|||
export const fetchFailedTransactionsCorrelationPValues = async (
|
||||
esClient: ElasticsearchClient,
|
||||
params: SearchStrategyParams,
|
||||
histogramRangeSteps: number[],
|
||||
fieldName: string
|
||||
) => {
|
||||
const resp = await esClient.search(
|
||||
|
@ -79,7 +86,10 @@ export const fetchFailedTransactionsCorrelationPValues = async (
|
|||
bg_count: number;
|
||||
score: number;
|
||||
}>;
|
||||
const result = overallResult.buckets.map((bucket) => {
|
||||
|
||||
// Using for of to sequentially augment the results with histogram data.
|
||||
const result = [];
|
||||
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)
|
||||
const normalizedScore =
|
||||
|
@ -87,7 +97,17 @@ export const fetchFailedTransactionsCorrelationPValues = async (
|
|||
0.25 * Math.min(Math.max((bucket.score - 6.908) / 6.908, 0), 1) +
|
||||
0.25 * Math.min(Math.max((bucket.score - 13.816) / 101.314, 0), 1);
|
||||
|
||||
return {
|
||||
const histogram = await fetchTransactionDurationRanges(
|
||||
esClient,
|
||||
params,
|
||||
histogramRangeSteps,
|
||||
[
|
||||
{ fieldName: EVENT_OUTCOME, fieldValue: EventOutcome.failure },
|
||||
{ fieldName, fieldValue: bucket.key },
|
||||
]
|
||||
);
|
||||
|
||||
result.push({
|
||||
fieldName,
|
||||
fieldValue: bucket.key,
|
||||
doc_count: bucket.doc_count,
|
||||
|
@ -101,8 +121,9 @@ export const fetchFailedTransactionsCorrelationPValues = async (
|
|||
successPercentage:
|
||||
(bucket.bg_count - bucket.doc_count) /
|
||||
(overallResult.bg_count - overallResult.doc_count),
|
||||
};
|
||||
});
|
||||
histogram,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
|
|
@ -23,12 +23,11 @@ import { getRequestBase } from './get_request_base';
|
|||
export const getTransactionDurationHistogramRequest = (
|
||||
params: SearchStrategyParams,
|
||||
interval: number,
|
||||
fieldName?: FieldValuePair['fieldName'],
|
||||
fieldValue?: FieldValuePair['fieldValue']
|
||||
termFilters?: FieldValuePair[]
|
||||
): estypes.SearchRequest => ({
|
||||
...getRequestBase(params),
|
||||
body: {
|
||||
query: getQueryWithParams({ params, fieldName, fieldValue }),
|
||||
query: getQueryWithParams({ params, termFilters }),
|
||||
size: 0,
|
||||
aggs: {
|
||||
transaction_duration_histogram: {
|
||||
|
@ -42,16 +41,10 @@ export const fetchTransactionDurationHistogram = async (
|
|||
esClient: ElasticsearchClient,
|
||||
params: SearchStrategyParams,
|
||||
interval: number,
|
||||
fieldName?: FieldValuePair['fieldName'],
|
||||
fieldValue?: FieldValuePair['fieldValue']
|
||||
termFilters?: FieldValuePair[]
|
||||
): Promise<HistogramItem[]> => {
|
||||
const resp = await esClient.search<ResponseHit>(
|
||||
getTransactionDurationHistogramRequest(
|
||||
params,
|
||||
interval,
|
||||
fieldName,
|
||||
fieldValue
|
||||
)
|
||||
getTransactionDurationHistogramRequest(params, interval, termFilters)
|
||||
);
|
||||
|
||||
if (resp.body.aggregations === undefined) {
|
||||
|
|
|
@ -43,15 +43,17 @@ export async function* fetchTransactionDurationHistograms(
|
|||
// If one of the fields have an error
|
||||
// We don't want to stop the whole process
|
||||
try {
|
||||
const { correlation, ksTest } = await fetchTransactionDurationCorrelation(
|
||||
const {
|
||||
correlation,
|
||||
ksTest,
|
||||
} = await fetchTransactionDurationCorrelation(
|
||||
esClient,
|
||||
params,
|
||||
expectations,
|
||||
ranges,
|
||||
fractions,
|
||||
totalDocCount,
|
||||
item.fieldName,
|
||||
item.fieldValue
|
||||
[item]
|
||||
);
|
||||
|
||||
if (state.getIsCancelled()) {
|
||||
|
@ -69,8 +71,7 @@ export async function* fetchTransactionDurationHistograms(
|
|||
esClient,
|
||||
params,
|
||||
histogramRangeSteps,
|
||||
item.fieldName,
|
||||
item.fieldValue
|
||||
[item]
|
||||
);
|
||||
yield {
|
||||
...item,
|
||||
|
|
|
@ -23,10 +23,9 @@ import { SIGNIFICANT_VALUE_DIGITS } from '../constants';
|
|||
export const getTransactionDurationPercentilesRequest = (
|
||||
params: SearchStrategyParams,
|
||||
percents?: number[],
|
||||
fieldName?: FieldValuePair['fieldName'],
|
||||
fieldValue?: FieldValuePair['fieldValue']
|
||||
termFilters?: FieldValuePair[]
|
||||
): estypes.SearchRequest => {
|
||||
const query = getQueryWithParams({ params, fieldName, fieldValue });
|
||||
const query = getQueryWithParams({ params, termFilters });
|
||||
|
||||
return {
|
||||
...getRequestBase(params),
|
||||
|
@ -53,16 +52,10 @@ export const fetchTransactionDurationPercentiles = async (
|
|||
esClient: ElasticsearchClient,
|
||||
params: SearchStrategyParams,
|
||||
percents?: number[],
|
||||
fieldName?: FieldValuePair['fieldName'],
|
||||
fieldValue?: FieldValuePair['fieldValue']
|
||||
termFilters?: FieldValuePair[]
|
||||
): Promise<{ totalDocs: number; percentiles: Record<string, number> }> => {
|
||||
const resp = await esClient.search<ResponseHit>(
|
||||
getTransactionDurationPercentilesRequest(
|
||||
params,
|
||||
percents,
|
||||
fieldName,
|
||||
fieldValue
|
||||
)
|
||||
getTransactionDurationPercentilesRequest(params, percents, termFilters)
|
||||
);
|
||||
|
||||
// return early with no results if the search didn't return any documents
|
||||
|
|
|
@ -22,10 +22,9 @@ import { getRequestBase } from './get_request_base';
|
|||
export const getTransactionDurationRangesRequest = (
|
||||
params: SearchStrategyParams,
|
||||
rangesSteps: number[],
|
||||
fieldName?: FieldValuePair['fieldName'],
|
||||
fieldValue?: FieldValuePair['fieldValue']
|
||||
termFilters?: FieldValuePair[]
|
||||
): estypes.SearchRequest => {
|
||||
const query = getQueryWithParams({ params, fieldName, fieldValue });
|
||||
const query = getQueryWithParams({ params, termFilters });
|
||||
|
||||
const ranges = rangesSteps.reduce(
|
||||
(p, to) => {
|
||||
|
@ -60,16 +59,10 @@ export const fetchTransactionDurationRanges = async (
|
|||
esClient: ElasticsearchClient,
|
||||
params: SearchStrategyParams,
|
||||
rangesSteps: number[],
|
||||
fieldName?: FieldValuePair['fieldName'],
|
||||
fieldValue?: FieldValuePair['fieldValue']
|
||||
termFilters?: FieldValuePair[]
|
||||
): Promise<Array<{ key: number; doc_count: number }>> => {
|
||||
const resp = await esClient.search<ResponseHit>(
|
||||
getTransactionDurationRangesRequest(
|
||||
params,
|
||||
rangesSteps,
|
||||
fieldName,
|
||||
fieldValue
|
||||
)
|
||||
getTransactionDurationRangesRequest(params, rangesSteps, termFilters)
|
||||
);
|
||||
|
||||
if (resp.body.aggregations === undefined) {
|
||||
|
|
|
@ -13,6 +13,7 @@ import { IKibanaSearchRequest } from '../../../../../../src/plugins/data/common'
|
|||
|
||||
import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values';
|
||||
import type { LatencyCorrelationsParams } from '../../../common/search_strategies/latency_correlations/types';
|
||||
import type { SearchStrategyClientParams } from '../../../common/search_strategies/types';
|
||||
|
||||
import type { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices';
|
||||
|
||||
|
@ -124,7 +125,9 @@ describe('APM Correlations search strategy', () => {
|
|||
let mockGetApmIndicesMock: jest.Mock;
|
||||
let mockDeps: SearchStrategyDependencies;
|
||||
let params: Required<
|
||||
IKibanaSearchRequest<LatencyCorrelationsParams>
|
||||
IKibanaSearchRequest<
|
||||
LatencyCorrelationsParams & SearchStrategyClientParams
|
||||
>
|
||||
>['params'];
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -10,6 +10,7 @@ import expect from '@kbn/expect';
|
|||
import { IKibanaSearchRequest } from '../../../../../src/plugins/data/common';
|
||||
|
||||
import type { FailedTransactionsCorrelationsParams } from '../../../../plugins/apm/common/search_strategies/failed_transactions_correlations/types';
|
||||
import type { SearchStrategyClientParams } from '../../../../plugins/apm/common/search_strategies/types';
|
||||
import { APM_SEARCH_STRATEGIES } from '../../../../plugins/apm/common/search_strategies/constants';
|
||||
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
|
@ -21,12 +22,15 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
const supertest = getService('legacySupertestAsApmReadUser');
|
||||
|
||||
const getRequestBody = () => {
|
||||
const request: IKibanaSearchRequest<FailedTransactionsCorrelationsParams> = {
|
||||
const request: IKibanaSearchRequest<
|
||||
FailedTransactionsCorrelationsParams & SearchStrategyClientParams
|
||||
> = {
|
||||
params: {
|
||||
environment: 'ENVIRONMENT_ALL',
|
||||
start: '2020',
|
||||
end: '2021',
|
||||
kuery: '',
|
||||
percentileThreshold: 95,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -210,8 +214,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
const { rawResponse: finalRawResponse } = followUpResult;
|
||||
|
||||
expect(typeof finalRawResponse?.took).to.be('number');
|
||||
expect(finalRawResponse?.percentileThresholdValue).to.be(undefined);
|
||||
expect(finalRawResponse?.overallHistogram).to.be(undefined);
|
||||
expect(finalRawResponse?.percentileThresholdValue).to.be(1309695.875);
|
||||
expect(finalRawResponse?.errorHistogram.length).to.be(101);
|
||||
expect(finalRawResponse?.overallHistogram.length).to.be(101);
|
||||
|
||||
expect(finalRawResponse?.failedTransactionsCorrelations.length).to.eql(
|
||||
30,
|
||||
|
@ -219,6 +224,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
);
|
||||
|
||||
expect(finalRawResponse?.log.map((d: string) => d.split(': ')[1])).to.eql([
|
||||
'Fetched 95th percentile value of 1309695.875 based on 1244 documents.',
|
||||
'Identified 68 fieldCandidates.',
|
||||
'Identified correlations for 68 fields out of 68 candidates.',
|
||||
'Identified 30 significant correlations relating to failed transactions.',
|
||||
|
|
|
@ -10,6 +10,7 @@ import expect from '@kbn/expect';
|
|||
import { IKibanaSearchRequest } from '../../../../../src/plugins/data/common';
|
||||
|
||||
import type { LatencyCorrelationsParams } from '../../../../plugins/apm/common/search_strategies/latency_correlations/types';
|
||||
import type { SearchStrategyClientParams } from '../../../../plugins/apm/common/search_strategies/types';
|
||||
import { APM_SEARCH_STRATEGIES } from '../../../../plugins/apm/common/search_strategies/constants';
|
||||
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
|
@ -21,7 +22,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
const supertest = getService('legacySupertestAsApmReadUser');
|
||||
|
||||
const getRequestBody = () => {
|
||||
const request: IKibanaSearchRequest<LatencyCorrelationsParams> = {
|
||||
const request: IKibanaSearchRequest<LatencyCorrelationsParams & SearchStrategyClientParams> = {
|
||||
params: {
|
||||
environment: 'ENVIRONMENT_ALL',
|
||||
start: '2020',
|
||||
|
@ -138,7 +139,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
);
|
||||
|
||||
registry.when(
|
||||
'Correlations latency_ml with data and opbeans-node args',
|
||||
'correlations latency with data and opbeans-node args',
|
||||
{ config: 'trial', archives: ['8.0.0'] },
|
||||
() => {
|
||||
// putting this into a single `it` because the responses depend on each other
|
||||
|
|
|
@ -135,7 +135,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
const apmFailedTransactionsCorrelationsTabTitle = await testSubjects.getVisibleText(
|
||||
'apmFailedTransactionsCorrelationsTabTitle'
|
||||
);
|
||||
expect(apmFailedTransactionsCorrelationsTabTitle).to.be('Failed transactions');
|
||||
expect(apmFailedTransactionsCorrelationsTabTitle).to.be(
|
||||
'Failed transactions latency distribution'
|
||||
);
|
||||
|
||||
// Assert that the data fully loaded to 100%
|
||||
const apmFailedTransactionsCorrelationsProgressTitle = await testSubjects.getVisibleText(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue