[ML] AIOps: Tweak log rate changes in log rate analysis results table. (#188648)

## Summary

Part of #187684.

This moves functions related to log rate changes to the
`@kbn/aiops_log_rate_analysis` package.

- `getLogRateAnalysisType` was renamed to
`getLogRateAnalysisTypeForHistogram` to indicate its use with histogram
data.
- `getLogRateAnalysisTypeForCounts` was added for cases where we don't
have the histogram data available but just the doc counts for baseline
an deviation time ranges. This isn't used yet as of this PR but will be
in a follow up in combination with the o11y AI assistant.
- `getSwappedWindowParameters` is a helper to consolidate inline code
that's used to swap baseline and deviation when we detected a dip in log
rate.
- Rounding for the log rate change messages was tweaked. Changes below
`10x` will now be rounded to one digit to avoid messages like `1x
increase`.
- Tweaked/Shortened the message for 0 in baseline or deviation to just
`45 up from 0 in baseline` / `down to 0 from 45 in baseline`.

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
Walter Rafelsberger 2024-07-23 10:25:29 +02:00 committed by GitHub
parent 3af5893f3b
commit dffc044211
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 551 additions and 159 deletions

View file

@ -5,6 +5,7 @@
"xpack.aiops": [
"packages/ml/aiops_components",
"packages/ml/aiops_log_pattern_analysis",
"packages/ml/aiops_log_rate_analysis",
"plugins/aiops"
],
"xpack.alerting": "plugins/alerting",

View file

@ -24,7 +24,7 @@ import { getTimeZone } from '@kbn/visualization-utils';
import { i18n } from '@kbn/i18n';
import type { IUiSettingsClient } from '@kbn/core/public';
import {
getLogRateAnalysisType,
getLogRateAnalysisTypeForHistogram,
getSnappedTimestamps,
getSnappedWindowParameters,
getWindowParametersForTrigger,
@ -334,7 +334,7 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = (props) => {
brushSelectionUpdateHandler({
windowParameters: wpSnap,
force: true,
analysisType: getLogRateAnalysisType(adjustedChartPoints, wpSnap),
analysisType: getLogRateAnalysisTypeForHistogram(adjustedChartPoints, wpSnap),
});
}
}
@ -391,7 +391,7 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = (props) => {
brushSelectionUpdateHandler({
windowParameters: wp,
force: false,
analysisType: getLogRateAnalysisType(adjustedChartPoints, wp),
analysisType: getLogRateAnalysisTypeForHistogram(adjustedChartPoints, wp),
});
}

View file

@ -11,7 +11,7 @@ import { finalSignificantItemGroups } from '@kbn/aiops-test-utils/artificial_log
import {
addSignificantItems,
addSignificantItemsGroup,
resetAll,
resetResults,
resetGroups,
updateLoadingState,
getDefaultState,
@ -58,7 +58,7 @@ describe('streamReducer', () => {
expect(state1.significantItems).toHaveLength(1);
const state2 = streamReducer(state1, resetAll());
const state2 = streamReducer(state1, resetResults());
expect(state2.significantItems).toHaveLength(0);
});

View file

@ -15,8 +15,13 @@ import type {
SignificantItemGroupHistogram,
} from '@kbn/ml-agg-utils';
import type { WindowParameters } from '../window_parameters';
import type { LogRateAnalysisType } from '../log_rate_analysis_type';
export interface StreamState {
ccsWarning: boolean;
currentAnalysisType?: LogRateAnalysisType;
currentAnalysisWindowParameters?: WindowParameters;
significantItems: SignificantItem[];
significantItemsGroups: SignificantItemGroup[];
errors: string[];
@ -80,7 +85,12 @@ export const logRateAnalysisResultsSlice = createSlice({
resetGroups: (state) => {
state.significantItemsGroups = [];
},
resetAll: () => getDefaultState(),
// Reset the results but keep the current analysis type and window parameters.
resetResults: (state) => ({
...getDefaultState(),
currentAnalysisType: state.currentAnalysisType,
currentAnalysisWindowParameters: state.currentAnalysisWindowParameters,
}),
updateLoadingState: (
state,
action: PayloadAction<{
@ -96,6 +106,15 @@ export const logRateAnalysisResultsSlice = createSlice({
setZeroDocsFallback: (state, action: PayloadAction<boolean>) => {
state.zeroDocsFallback = action.payload;
},
setCurrentAnalysisType: (state, action: PayloadAction<LogRateAnalysisType | undefined>) => {
state.currentAnalysisType = action.payload;
},
setCurrentAnalysisWindowParameters: (
state,
action: PayloadAction<WindowParameters | undefined>
) => {
state.currentAnalysisWindowParameters = action.payload;
},
},
});
@ -113,9 +132,11 @@ export const {
addSignificantItemsGroupHistogram,
addSignificantItemsHistogram,
ping,
resetAll,
resetResults,
resetErrors,
resetGroups,
setCurrentAnalysisType,
setCurrentAnalysisWindowParameters,
setZeroDocsFallback,
updateLoadingState,
} = logRateAnalysisResultsSlice.actions;

View file

@ -0,0 +1,77 @@
/*
* 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 { getBaselineAndDeviationRates } from './get_baseline_and_deviation_rates';
import { LOG_RATE_ANALYSIS_TYPE } from './log_rate_analysis_type';
describe('getBaselineAndDeviationRates', () => {
it('calculates rates for SPIKE analysis', () => {
const analysisType = LOG_RATE_ANALYSIS_TYPE.SPIKE;
const baselineBuckets = 10;
const deviationBuckets = 5;
const docCount = 100;
const bgCount = 50;
const expected = {
baselineBucketRate: 5, // 50 / 10
deviationBucketRate: 20, // 100 / 5
};
const result = getBaselineAndDeviationRates(
analysisType,
baselineBuckets,
deviationBuckets,
docCount,
bgCount
);
expect(result).toEqual(expected);
});
it('calculates rates for DIP analysis', () => {
const analysisType = LOG_RATE_ANALYSIS_TYPE.DIP;
const baselineBuckets = 8;
const deviationBuckets = 4;
const docCount = 80; // Now represents baseline period in DIP
const bgCount = 40; // Now represents deviation period in DIP
const expected = {
baselineBucketRate: 10, // 80 / 8
deviationBucketRate: 10, // 40 / 4
};
const result = getBaselineAndDeviationRates(
analysisType,
baselineBuckets,
deviationBuckets,
docCount,
bgCount
);
expect(result).toEqual(expected);
});
it('handles zero buckets without throwing error', () => {
const analysisType = LOG_RATE_ANALYSIS_TYPE.SPIKE;
const baselineBuckets = 0;
const deviationBuckets = 0;
const docCount = 100;
const bgCount = 50;
const expected = {
baselineBucketRate: 0,
deviationBucketRate: 0,
};
const result = getBaselineAndDeviationRates(
analysisType,
baselineBuckets,
deviationBuckets,
docCount,
bgCount
);
expect(result).toEqual(expected);
});
});

View file

@ -0,0 +1,47 @@
/*
* 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 { LOG_RATE_ANALYSIS_TYPE, type LogRateAnalysisType } from './log_rate_analysis_type';
/**
* Calculates the baseline and deviation rates for log rate analysis based on the specified analysis type.
*
* This function computes the rates by dividing the document count (docCount) and background count (bgCount)
* by the number of buckets allocated for baseline and deviation periods, respectively. The calculation
* method varies depending on whether the analysis type is a "spike" or a "dip". For a "spike", the baseline
* rate is derived from the background count and the deviation rate from the document count. For a "dip",
* the roles are reversed.
*
* @param analysisType The type of analysis to perform, can be either "spike" or "dip".
* @param baselineBuckets The number of buckets into which the baseline period is divided.
* @param deviationBuckets The number of buckets into which the deviation period is divided.
* @param docCount The total document count observed in the deviation period.
* @param bgCount The total background count observed in the baseline period.
* @returns An object containing the calculated baseline and deviation bucket rates.
*/
export function getBaselineAndDeviationRates(
analysisType: LogRateAnalysisType,
baselineBuckets: number,
deviationBuckets: number,
docCount: number,
bgCount: number
): { baselineBucketRate: number; deviationBucketRate: number } {
if (baselineBuckets === 0 || deviationBuckets === 0) {
return { baselineBucketRate: 0, deviationBucketRate: 0 };
} else if (analysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE) {
return {
baselineBucketRate: Math.round(bgCount / baselineBuckets),
deviationBucketRate: Math.round(docCount / deviationBuckets),
};
} else {
// For dip, the "doc count" refers to the amount of documents in the baseline time range so we set baselineBucketRate
return {
baselineBucketRate: Math.round(docCount / baselineBuckets),
deviationBucketRate: Math.round(bgCount / deviationBuckets),
};
}
}

View file

@ -0,0 +1,54 @@
/*
* 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 { LOG_RATE_ANALYSIS_TYPE } from './log_rate_analysis_type';
import { getLogRateAnalysisTypeForCounts } from './get_log_rate_analysis_type_for_counts';
const windowParameters = {
baselineMin: 1654579807500,
baselineMax: 1654586107500,
deviationMin: 1654586400000,
deviationMax: 1654587007500,
};
describe('getLogRateAnalysisTypeForCounts', () => {
it('returns SPIKE when normalized deviation count is higher than baseline count', () => {
const baselineCount = 100;
const deviationCount = 200;
const result = getLogRateAnalysisTypeForCounts(baselineCount, deviationCount, windowParameters);
expect(result).toEqual(LOG_RATE_ANALYSIS_TYPE.SPIKE);
});
it('returns DIP when normalized deviation count is lower than baseline count', () => {
const baselineCount = 20000;
const deviationCount = 10;
const result = getLogRateAnalysisTypeForCounts(baselineCount, deviationCount, windowParameters);
expect(result).toEqual(LOG_RATE_ANALYSIS_TYPE.DIP);
});
it('handles zero baseline count without throwing error', () => {
const baselineCount = 0;
const deviationCount = 100;
const result = getLogRateAnalysisTypeForCounts(baselineCount, deviationCount, windowParameters);
expect(result).toBe(LOG_RATE_ANALYSIS_TYPE.SPIKE);
});
it('handles zero deviation count without throwing error', () => {
const baselineCount = 100;
const deviationCount = 0;
const result = getLogRateAnalysisTypeForCounts(baselineCount, deviationCount, windowParameters);
expect(result).toBe(LOG_RATE_ANALYSIS_TYPE.DIP);
});
});

View file

@ -0,0 +1,35 @@
/*
* 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 { LOG_RATE_ANALYSIS_TYPE, type LogRateAnalysisType } from './log_rate_analysis_type';
import type { WindowParameters } from './window_parameters';
/**
* Identify the log rate analysis type based on the baseline/deviation doc counts.
*
* @param baselineCount The baseline doc count.
* @param deviationCount The deviation doc count.
* @param windowParameters The window parameters with baseline and deviation time range.
* @returns The log rate analysis type.
*/
export function getLogRateAnalysisTypeForCounts(
baselineCount: number,
deviationCount: number,
windowParameters: WindowParameters
): LogRateAnalysisType {
const { baselineMin, baselineMax, deviationMin, deviationMax } = windowParameters;
const deviationDuration = deviationMax - deviationMin;
const deviationPerBucket = deviationCount;
const baselineNormalizedDuration = (baselineMax - baselineMin) / deviationDuration;
const baselinePerBucket = baselineCount / baselineNormalizedDuration;
return deviationPerBucket >= baselinePerBucket
? LOG_RATE_ANALYSIS_TYPE.SPIKE
: LOG_RATE_ANALYSIS_TYPE.DIP;
}

View file

@ -6,9 +6,9 @@
*/
import type { LogRateHistogramItem } from './log_rate_histogram_item';
import { getLogRateAnalysisType } from './get_log_rate_analysis_type';
import { getLogRateAnalysisTypeForHistogram } from './get_log_rate_analysis_type_for_histogram';
describe('getLogRateAnalysisType', () => {
describe('getLogRateAnalysisTypeForHistogram', () => {
const LogRateHistogramMock: LogRateHistogramItem[] = [
{ time: 0, value: 10 },
{ time: 1, value: 10 },
@ -24,7 +24,7 @@ describe('getLogRateAnalysisType', () => {
test('returns "spike" for the given parameters', () => {
expect(
getLogRateAnalysisType(LogRateHistogramMock, {
getLogRateAnalysisTypeForHistogram(LogRateHistogramMock, {
baselineMin: 4,
baselineMax: 6,
deviationMin: 7,
@ -35,7 +35,7 @@ describe('getLogRateAnalysisType', () => {
test('returns "dip" for the given parameters', () => {
expect(
getLogRateAnalysisType(LogRateHistogramMock, {
getLogRateAnalysisTypeForHistogram(LogRateHistogramMock, {
baselineMin: 0,
baselineMax: 2,
deviationMin: 3,
@ -46,7 +46,7 @@ describe('getLogRateAnalysisType', () => {
test('falls back to "spike" if both time range have the same median', () => {
expect(
getLogRateAnalysisType(LogRateHistogramMock, {
getLogRateAnalysisTypeForHistogram(LogRateHistogramMock, {
baselineMin: 0,
baselineMax: 2,
deviationMin: 4,

View file

@ -19,7 +19,7 @@ import type { WindowParameters } from './window_parameters';
* @param windowParameters The window parameters with baseline and deviation time range.
* @returns The log rate analysis type.
*/
export function getLogRateAnalysisType(
export function getLogRateAnalysisTypeForHistogram(
logRateHistogram: LogRateHistogramItem[],
windowParameters: WindowParameters
): LogRateAnalysisType {

View file

@ -0,0 +1,112 @@
/*
* 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 { LOG_RATE_ANALYSIS_TYPE } from './log_rate_analysis_type';
import { getLogRateChange } from './get_log_rate_change';
describe('getLogRateChange', () => {
it('calculates the factor and message for a SPIKE analysis with factor < 10', () => {
const analysisType = LOG_RATE_ANALYSIS_TYPE.SPIKE;
const baselineBucketRate = 5;
const deviationBucketRate = 44;
const expectedFactor = 8.8;
const expectedMessage = '8.8x higher';
const { message, factor } = getLogRateChange(
analysisType,
baselineBucketRate,
deviationBucketRate
);
expect(factor).toBe(expectedFactor);
expect(message).toBe(expectedMessage);
});
it('calculates the factor and message for a SPIKE analysis with factor >= 10', () => {
const analysisType = LOG_RATE_ANALYSIS_TYPE.SPIKE;
const baselineBucketRate = 5;
const deviationBucketRate = 51;
const expectedFactor = 10;
const expectedMessage = '10x higher';
const { message, factor } = getLogRateChange(
analysisType,
baselineBucketRate,
deviationBucketRate
);
expect(factor).toEqual(expectedFactor);
expect(message).toContain(expectedMessage);
});
it('calculates the factor and message for a DIP analysis with factor < 10', () => {
const analysisType = LOG_RATE_ANALYSIS_TYPE.DIP;
const baselineBucketRate = 256;
const deviationBucketRate = 44;
const expectedFactor = 5.8;
const expectedMessage = '5.8x lower';
const { message, factor } = getLogRateChange(
analysisType,
baselineBucketRate,
deviationBucketRate
);
expect(factor).toBe(expectedFactor);
expect(message).toBe(expectedMessage);
});
it('calculates the factor and message for a DIP analysis with factor >= 10', () => {
const analysisType = LOG_RATE_ANALYSIS_TYPE.DIP;
const baselineBucketRate = 1024;
const deviationBucketRate = 51;
const expectedFactor = 20;
const expectedMessage = '20x lower';
const { message, factor } = getLogRateChange(
analysisType,
baselineBucketRate,
deviationBucketRate
);
expect(factor).toEqual(expectedFactor);
expect(message).toContain(expectedMessage);
});
it('handles a baseline rate of 0 without throwing an error', () => {
const analysisType = LOG_RATE_ANALYSIS_TYPE.SPIKE;
const baselineBucketRate = 0;
const deviationBucketRate = 10;
const expectedMessage = 'up to 10 from 0 in baseline';
const { message, factor } = getLogRateChange(
analysisType,
baselineBucketRate,
deviationBucketRate
);
// Factor is undefined if baseline rate is 0
expect(factor).toBe(undefined);
expect(message).toContain(expectedMessage);
});
it('handles a deviation rate of 0 without throwing an error', () => {
const analysisType = LOG_RATE_ANALYSIS_TYPE.DIP;
const baselineBucketRate = 500;
const deviationBucketRate = 0;
const expectedMessage = 'down to 0 from 500 in baseline';
const { message, factor } = getLogRateChange(
analysisType,
baselineBucketRate,
deviationBucketRate
);
// Factor is undefined if deviation rate is 0
expect(factor).toBe(undefined);
expect(message).toContain(expectedMessage);
});
});

View file

@ -0,0 +1,82 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { LOG_RATE_ANALYSIS_TYPE } from './log_rate_analysis_type';
import type { LogRateAnalysisType } from './log_rate_analysis_type';
/**
* Calculates the change in log rate between two time periods and generates a descriptive message.
* It return the factor as a number as well as a human readable message.
*
* @param analysisType The type of log rate analysis (spike or dip).
* @param baselineBucketRate The log rate (document count per unit time) during the baseline period.
* @param deviationBucketRate The log rate (document count per unit time) during the deviation period.
* @returns An object containing the message describing the rate change and the factor of change if applicable.
*/
export function getLogRateChange(
analysisType: LogRateAnalysisType,
baselineBucketRate: number,
deviationBucketRate: number
): { message: string; factor?: number } {
if (analysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE) {
if (baselineBucketRate > 0) {
const factor = deviationBucketRate / baselineBucketRate;
const roundedFactor = factor < 10 ? Math.round(factor * 10) / 10 : Math.round(factor);
const message = i18n.translate(
'xpack.aiops.logRateAnalysis.resultsTableGroups.logRateFactorIncreaseLabel',
{
defaultMessage: '{roundedFactor}x higher',
values: {
roundedFactor,
},
}
);
return { message, factor: roundedFactor };
} else {
return {
message: i18n.translate(
'xpack.aiops.logRateAnalysis.resultsTableGroups.logRateDocIncreaseLabel',
{
defaultMessage: 'up to {deviationBucketRate} from 0 in baseline',
values: { deviationBucketRate },
}
),
};
}
} else {
if (deviationBucketRate > 0) {
// For dip, "doc count" refers to the amount of documents in the baseline time range so we use baselineBucketRate
const factor = baselineBucketRate / deviationBucketRate;
const roundedFactor = factor < 10 ? Math.round(factor * 10) / 10 : Math.round(factor);
const message = i18n.translate(
'xpack.aiops.logRateAnalysis.resultsTableGroups.logRateFactorDecreaseLabel',
{
defaultMessage: '{roundedFactor}x lower',
values: {
roundedFactor,
},
}
);
return { message, factor: roundedFactor };
} else {
return {
message: i18n.translate(
'xpack.aiops.logRateAnalysis.resultsTableGroups.logRateDocDecreaseLabel',
{
defaultMessage: 'down to 0 from {baselineBucketRate} in baseline',
values: { baselineBucketRate },
}
),
};
}
}
}

View file

@ -0,0 +1,29 @@
/*
* 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 { getSwappedWindowParameters } from './get_swapped_window_parameters';
describe('getSwappedWindowParameters', () => {
it('swaps baseline and deviation parameters', () => {
const windowParameters = {
baselineMin: 1,
baselineMax: 2,
deviationMin: 3,
deviationMax: 4,
};
const expected = {
baselineMin: 3,
baselineMax: 4,
deviationMin: 1,
deviationMax: 2,
};
const result = getSwappedWindowParameters(windowParameters);
expect(result).toEqual(expected);
});
});

View file

@ -0,0 +1,23 @@
/*
* 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 { WindowParameters } from './window_parameters';
/**
* Swaps the baseline and deviation window parameters. To be used when we identify the type of analysis to be 'dip'.
*
* @param windowParameters An object containing the window parameters for baseline and deviation periods.
* @returns A new `WindowParameters` object with the baseline and deviation parameters swapped.
*/
export const getSwappedWindowParameters = (
windowParameters: WindowParameters
): WindowParameters => ({
baselineMin: windowParameters.deviationMin,
baselineMax: windowParameters.deviationMax,
deviationMin: windowParameters.baselineMin,
deviationMax: windowParameters.baselineMax,
});

View file

@ -6,7 +6,7 @@
*/
export { LOG_RATE_ANALYSIS_HIGHLIGHT_COLOR } from './constants';
export { getLogRateAnalysisType } from './get_log_rate_analysis_type';
export { getLogRateAnalysisTypeForHistogram } from './get_log_rate_analysis_type_for_histogram';
export { LOG_RATE_ANALYSIS_TYPE, type LogRateAnalysisType } from './log_rate_analysis_type';
export type { LogRateHistogramItem } from './log_rate_histogram_item';
export type { DocumentCountStats, DocumentStats, DocumentCountStatsChangePoint } from './types';
@ -16,3 +16,6 @@ export { getSnappedWindowParameters } from './get_snapped_window_parameters';
export { getWindowParameters } from './get_window_parameters';
export { getWindowParametersForTrigger } from './get_window_parameters_for_trigger';
export { getExtendedChangePoint } from './get_extended_change_point';
export { getSwappedWindowParameters } from './get_swapped_window_parameters';
export { getBaselineAndDeviationRates } from './get_baseline_and_deviation_rates';
export { getLogRateChange } from './get_log_rate_change';

View file

@ -14,7 +14,7 @@ export {
setInitialAnalysisStart,
setIsBrushCleared,
setStickyHistogram,
setWindowParameters,
setChartWindowParameters,
type BrushSelectionUpdatePayload,
} from './log_rate_analysis_slice';
export {

View file

@ -35,7 +35,7 @@ export interface LogRateAnalysisState {
initialAnalysisStart: InitialAnalysisStart;
isBrushCleared: boolean;
stickyHistogram: boolean;
windowParameters?: WindowParameters;
chartWindowParameters?: WindowParameters;
earliest?: number;
latest?: number;
intervalMs?: number;
@ -66,7 +66,7 @@ export const logRateAnalysisSlice = createSlice({
action: PayloadAction<BrushSelectionUpdatePayload>
) => {
if (!state.isBrushCleared || action.payload.force) {
state.windowParameters = action.payload.windowParameters;
state.chartWindowParameters = action.payload.windowParameters;
}
if (action.payload.force) {
state.isBrushCleared = false;
@ -74,7 +74,7 @@ export const logRateAnalysisSlice = createSlice({
state.analysisType = action.payload.analysisType;
},
clearSelection: (state: LogRateAnalysisState) => {
state.windowParameters = undefined;
state.chartWindowParameters = undefined;
state.isBrushCleared = true;
state.initialAnalysisStart = undefined;
},
@ -110,11 +110,11 @@ export const logRateAnalysisSlice = createSlice({
setStickyHistogram: (state: LogRateAnalysisState, action: PayloadAction<boolean>) => {
state.stickyHistogram = action.payload;
},
setWindowParameters: (
setChartWindowParameters: (
state: LogRateAnalysisState,
action: PayloadAction<WindowParameters | undefined>
) => {
state.windowParameters = action.payload;
state.chartWindowParameters = action.payload;
state.isBrushCleared = action.payload === undefined;
},
},
@ -130,5 +130,5 @@ export const {
setInitialAnalysisStart,
setIsBrushCleared,
setStickyHistogram,
setWindowParameters,
setChartWindowParameters,
} = logRateAnalysisSlice.actions;

View file

@ -30,5 +30,6 @@
"@kbn/ml-chi2test",
"@kbn/ml-string-hash",
"@kbn/ml-response-stream",
"@kbn/i18n",
]
}

View file

@ -81,7 +81,7 @@ export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
);
const loaded = useAppSelector((s) => s.logRateAnalysisResults.loaded);
const analysisType = useAppSelector((s) => s.logRateAnalysis.analysisType);
const windowParameters = useAppSelector((s) => s.logRateAnalysis.windowParameters);
const windowParameters = useAppSelector((s) => s.logRateAnalysis.chartWindowParameters);
// Window parameters stored in the url state use this components
// `initialAnalysisStart` prop to set the initial params restore from url state.

View file

@ -31,9 +31,9 @@ import {
useAppSelector,
} from '@kbn/aiops-log-rate-analysis/state';
import {
getSwappedWindowParameters,
LOG_RATE_ANALYSIS_TYPE,
type LogRateAnalysisType,
type WindowParameters,
} from '@kbn/aiops-log-rate-analysis';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
@ -41,6 +41,10 @@ import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils';
import { AIOPS_TELEMETRY_ID } from '@kbn/aiops-common/constants';
import type { AiopsLogRateAnalysisSchema } from '@kbn/aiops-log-rate-analysis/api/schema';
import type { AiopsLogRateAnalysisSchemaSignificantItem } from '@kbn/aiops-log-rate-analysis/api/schema_v2';
import {
setCurrentAnalysisType,
setCurrentAnalysisWindowParameters,
} from '@kbn/aiops-log-rate-analysis/api/stream_reducer';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
import { useDataSource } from '../../hooks/use_data_source';
@ -161,23 +165,20 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
analysisType,
earliest,
latest,
windowParameters,
chartWindowParameters,
documentStats: { sampleProbability },
stickyHistogram,
isBrushCleared,
} = useAppSelector((s) => s.logRateAnalysis);
const { isRunning, errors: streamErrors } = useAppSelector((s) => s.logRateAnalysisStream);
const data = useAppSelector((s) => s.logRateAnalysisResults);
const { currentAnalysisType, currentAnalysisWindowParameters } = data;
// Store the performance metric's start time using a ref
// to be able to track it across rerenders.
const analysisStartTime = useRef<number | undefined>(window.performance.now());
const abortCtrl = useRef(new AbortController());
const [currentAnalysisType, setCurrentAnalysisType] = useState<LogRateAnalysisType | undefined>();
const [currentAnalysisWindowParameters, setCurrentAnalysisWindowParameters] = useState<
WindowParameters | undefined
>();
const [groupResults, setGroupResults] = useState<boolean>(false);
const [groupSkipFields, setGroupSkipFields] = useState<string[]>([]);
const [uniqueFieldNames, setUniqueFieldNames] = useState<string[]>([]);
@ -281,8 +282,8 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
dispatch(clearAllRowState());
}
setCurrentAnalysisType(analysisType);
setCurrentAnalysisWindowParameters(windowParameters);
dispatch(setCurrentAnalysisType(analysisType));
dispatch(setCurrentAnalysisWindowParameters(chartWindowParameters));
// We trigger hooks updates above so we cannot directly call `start()` here
// because it would be run with stale arguments.
@ -290,7 +291,7 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
}
const startParams = useMemo(() => {
if (!windowParameters) {
if (!chartWindowParameters) {
return undefined;
}
@ -311,13 +312,8 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
// If analysis type is `spike`, pass on window parameters as is,
// if it's `dip`, swap baseline and deviation.
...(analysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE
? windowParameters
: {
baselineMin: windowParameters.deviationMin,
baselineMax: windowParameters.deviationMax,
deviationMin: windowParameters.baselineMin,
deviationMax: windowParameters.baselineMax,
}),
? chartWindowParameters
: getSwappedWindowParameters(chartWindowParameters)),
overrides,
sampleProbability,
},
@ -330,7 +326,7 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
http,
searchQuery,
dataView,
windowParameters,
chartWindowParameters,
sampleProbability,
overrides,
embeddingOrigin,
@ -346,8 +342,8 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
useEffect(() => {
if (startParams) {
setCurrentAnalysisType(analysisType);
setCurrentAnalysisWindowParameters(windowParameters);
dispatch(setCurrentAnalysisType(analysisType));
dispatch(setCurrentAnalysisWindowParameters(chartWindowParameters));
dispatch(startStream(startParams));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -361,8 +357,8 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
const shouldRerunAnalysis = useMemo(
() =>
currentAnalysisWindowParameters !== undefined &&
!isEqual(currentAnalysisWindowParameters, windowParameters),
[currentAnalysisWindowParameters, windowParameters]
!isEqual(currentAnalysisWindowParameters, chartWindowParameters),
[currentAnalysisWindowParameters, chartWindowParameters]
);
const showLogRateAnalysisResultsTable = data?.significantItems.length > 0;

View file

@ -1,97 +0,0 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { LOG_RATE_ANALYSIS_TYPE } from '@kbn/aiops-log-rate-analysis';
export function getLogRateChange(
analysisType: typeof LOG_RATE_ANALYSIS_TYPE[keyof typeof LOG_RATE_ANALYSIS_TYPE],
baselineBucketRate: number,
deviationBucketRate: number
) {
let message;
let factor;
if (analysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE) {
if (baselineBucketRate > 0) {
factor = Math.round(((deviationBucketRate / baselineBucketRate) * 100) / 100);
message = i18n.translate(
'xpack.aiops.logRateAnalysis.resultsTableGroups.logRateFactorIncreaseLabel',
{
defaultMessage: '{factor}x higher',
values: {
factor,
},
}
);
} else {
message = i18n.translate(
'xpack.aiops.logRateAnalysis.resultsTableGroups.logRateDocIncreaseLabel',
{
defaultMessage:
'{deviationBucketRate} {deviationBucketRate, plural, one {doc} other {docs}} rate up from 0 in baseline',
values: { deviationBucketRate },
}
);
}
} else {
if (deviationBucketRate > 0) {
// For dip, "doc count" refers to the amount of documents in the baseline time range so we use baselineBucketRate
factor = Math.round(((baselineBucketRate / deviationBucketRate) * 100) / 100);
message = i18n.translate(
'xpack.aiops.logRateAnalysis.resultsTableGroups.logRateFactorDecreaseLabel',
{
defaultMessage: '{factor}x lower',
values: {
factor,
},
}
);
} else {
message = i18n.translate(
'xpack.aiops.logRateAnalysis.resultsTableGroups.logRateDocDecreaseLabel',
{
defaultMessage: 'docs rate down to 0 from {baselineBucketRate} in baseline',
values: { baselineBucketRate },
}
);
}
}
return { message, factor };
}
export function getBaselineAndDeviationRates(
analysisType: typeof LOG_RATE_ANALYSIS_TYPE[keyof typeof LOG_RATE_ANALYSIS_TYPE],
baselineBuckets: number,
deviationBuckets: number,
docCount: number | undefined,
bgCount: number | undefined
) {
let baselineBucketRate;
let deviationBucketRate;
if (analysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE) {
if (bgCount !== undefined) {
baselineBucketRate = Math.round(bgCount / baselineBuckets);
}
if (docCount !== undefined) {
deviationBucketRate = Math.round(docCount / deviationBuckets);
}
} else {
// For dip, the "doc count" refers to the amount of documents in the baseline time range so we set baselineBucketRate
if (docCount !== undefined) {
baselineBucketRate = Math.round(docCount / baselineBuckets);
}
if (bgCount !== undefined) {
deviationBucketRate = Math.round(bgCount / deviationBuckets);
}
}
return { baselineBucketRate, deviationBucketRate };
}

View file

@ -21,7 +21,11 @@ import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils';
import { getCategoryQuery } from '@kbn/aiops-log-pattern-analysis/get_category_query';
import type { FieldStatsServices } from '@kbn/unified-field-list/src/components/field_stats';
import { useAppSelector } from '@kbn/aiops-log-rate-analysis/state';
import { LOG_RATE_ANALYSIS_TYPE } from '@kbn/aiops-log-rate-analysis';
import {
getBaselineAndDeviationRates,
getLogRateChange,
LOG_RATE_ANALYSIS_TYPE,
} from '@kbn/aiops-log-rate-analysis';
import { getFailedTransactionsCorrelationImpactLabel } from './get_failed_transactions_correlation_impact_label';
import { FieldStatsPopover } from '../field_stats_popover';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
@ -31,7 +35,6 @@ import { useViewInDiscoverAction } from './use_view_in_discover_action';
import { useViewInLogPatternAnalysisAction } from './use_view_in_log_pattern_analysis_action';
import { useCopyToClipboardAction } from './use_copy_to_clipboard_action';
import { MiniHistogram } from '../mini_histogram';
import { getBaselineAndDeviationRates, getLogRateChange } from './get_baseline_and_deviation_rates';
const TRUNCATE_TEXT_LINES = 3;
const UNIQUE_COLUMN_WIDTH = '40px';
@ -162,10 +165,11 @@ export const useColumns = (
const loading = useAppSelector((s) => s.logRateAnalysisStream.isRunning);
const zeroDocsFallback = useAppSelector((s) => s.logRateAnalysisResults.zeroDocsFallback);
const {
analysisType,
windowParameters,
documentStats: { documentCountStats },
} = useAppSelector((s) => s.logRateAnalysis);
const { currentAnalysisType, currentAnalysisWindowParameters } = useAppSelector(
(s) => s.logRateAnalysisResults
);
const isGroupsTable = tableType === LOG_RATE_ANALYSIS_RESULTS_TABLE_TYPE.GROUPS;
const interval = documentCountStats?.interval ?? 0;
@ -181,14 +185,15 @@ export const useColumns = (
}, [uiSettings, data, fieldFormats, charts]);
const buckets = useMemo(() => {
if (windowParameters === undefined) return;
if (currentAnalysisWindowParameters === undefined) return;
const { baselineMin, baselineMax, deviationMin, deviationMax } = windowParameters;
const { baselineMin, baselineMax, deviationMin, deviationMax } =
currentAnalysisWindowParameters;
const baselineBuckets = (baselineMax - baselineMin) / interval;
const deviationBuckets = (deviationMax - deviationMin) / interval;
return { baselineBuckets, deviationBuckets };
}, [windowParameters, interval]);
}, [currentAnalysisWindowParameters, interval]);
const columnsMap: Record<ColumnNames, EuiBasicTableColumn<SignificantItem>> = useMemo(
() => ({
@ -365,14 +370,15 @@ export const useColumns = (
render: (_, { bg_count: bgCount, doc_count: docCount }) => {
if (
interval === 0 ||
windowParameters === undefined ||
currentAnalysisType === undefined ||
currentAnalysisWindowParameters === undefined ||
buckets === undefined ||
isGroupsTable
)
return NOT_AVAILABLE;
const { baselineBucketRate } = getBaselineAndDeviationRates(
analysisType,
currentAnalysisType,
buckets.baselineBuckets,
buckets.deviationBuckets,
docCount,
@ -407,14 +413,15 @@ export const useColumns = (
render: (_, { doc_count: docCount, bg_count: bgCount }) => {
if (
interval === 0 ||
windowParameters === undefined ||
currentAnalysisType === undefined ||
currentAnalysisWindowParameters === undefined ||
buckets === undefined ||
isGroupsTable
)
return NOT_AVAILABLE;
const { deviationBucketRate } = getBaselineAndDeviationRates(
analysisType,
currentAnalysisType,
buckets.baselineBuckets,
buckets.deviationBuckets,
docCount,
@ -448,14 +455,15 @@ export const useColumns = (
render: ({ doc_count: docCount, bg_count: bgCount }: SignificantItem) => {
if (
interval === 0 ||
windowParameters === undefined ||
currentAnalysisType === undefined ||
currentAnalysisWindowParameters === undefined ||
buckets === undefined ||
isGroupsTable
)
return NOT_AVAILABLE;
const { baselineBucketRate, deviationBucketRate } = getBaselineAndDeviationRates(
analysisType,
currentAnalysisType,
buckets.baselineBuckets,
buckets.deviationBuckets,
docCount,
@ -463,9 +471,9 @@ export const useColumns = (
);
const logRateChange = getLogRateChange(
analysisType,
baselineBucketRate!,
deviationBucketRate!
currentAnalysisType,
baselineBucketRate,
deviationBucketRate
);
return (
@ -473,7 +481,7 @@ export const useColumns = (
<EuiIcon
size="s"
color="subdued"
type={analysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE ? 'sortUp' : 'sortDown'}
type={currentAnalysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE ? 'sortUp' : 'sortDown'}
className="eui-alignTop"
/>
&nbsp;

View file

@ -6,7 +6,7 @@
*/
import {
resetAll,
resetResults,
resetErrors,
resetGroups,
} from '@kbn/aiops-log-rate-analysis/api/stream_reducer';
@ -24,7 +24,7 @@ export const overridesHandlerFactory =
() => {
if (!requestBody.overrides) {
logDebugMessage('Full Reset.');
responseStream.push(resetAll());
responseStream.push(resetResults());
} else {
logDebugMessage('Reset Errors.');
responseStream.push(resetErrors());