mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[ML] AIOps: Auto-detect if spike or dip selected in log rate analysis. (#163100)
This updates log rate analysis to be able to auto-detect whether the selected deviation is a spike or dip compared to the baseline time range. To achieve this, we compare the median bucket size of the two selections. If a dip gets detected, the analysis will then switch the window parameters sent to the API endpoint to run the analysis. An info callout points out the auto-selected analysis type and explains to which time range the analysis results refer to. We need to do this to make it clear that for dip analysis the significant terms and their doc counts refer to the baseline time range and vice versa for spike analysis.
This commit is contained in:
parent
0b95987782
commit
da0fb1d987
31 changed files with 401 additions and 237 deletions
|
@ -7,5 +7,9 @@
|
|||
|
||||
export { DualBrush, DualBrushAnnotation } from './src/dual_brush';
|
||||
export { ProgressControls } from './src/progress_controls';
|
||||
export { DocumentCountChart } from './src/document_count_chart';
|
||||
export type { DocumentCountChartPoint, DocumentCountChartProps } from './src/document_count_chart';
|
||||
export {
|
||||
DocumentCountChart,
|
||||
type BrushSettings,
|
||||
type BrushSelectionUpdateHandler,
|
||||
} from './src/document_count_chart';
|
||||
export type { DocumentCountChartProps } from './src/document_count_chart';
|
||||
|
|
|
@ -27,14 +27,21 @@ import {
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { IUiSettingsClient } from '@kbn/core/public';
|
||||
import { getSnappedWindowParameters, getWindowParameters } from '@kbn/aiops-utils';
|
||||
import type { WindowParameters } from '@kbn/aiops-utils';
|
||||
import {
|
||||
getLogRateAnalysisType,
|
||||
getSnappedWindowParameters,
|
||||
getWindowParameters,
|
||||
type LogRateAnalysisType,
|
||||
type LogRateHistogramItem,
|
||||
type WindowParameters,
|
||||
} from '@kbn/aiops-utils';
|
||||
import { MULTILAYER_TIME_AXIS_STYLE } from '@kbn/charts-plugin/common';
|
||||
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
||||
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
||||
|
||||
import { DualBrush, DualBrushAnnotation } from '../..';
|
||||
|
||||
import { BrushBadge } from './brush_badge';
|
||||
|
||||
declare global {
|
||||
|
@ -51,20 +58,6 @@ interface TimeFilterRange {
|
|||
to: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Datum for the bar chart
|
||||
*/
|
||||
export interface DocumentCountChartPoint {
|
||||
/**
|
||||
* Time of bucket
|
||||
*/
|
||||
time: number | string;
|
||||
/**
|
||||
* Number of doc count for that time bucket
|
||||
*/
|
||||
value: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Brush settings
|
||||
*/
|
||||
|
@ -83,6 +76,19 @@ export interface BrushSettings {
|
|||
badgeWidth?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function which gets called when the brush selection has changed
|
||||
*
|
||||
* @param windowParameters Baseline and deviation time ranges.
|
||||
* @param force Force update
|
||||
* @param logRateAnalysisType `spike` or `dip` based on median log rate bucket size
|
||||
*/
|
||||
export type BrushSelectionUpdateHandler = (
|
||||
windowParameters: WindowParameters,
|
||||
force: boolean,
|
||||
logRateAnalysisType: LogRateAnalysisType
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Props for document count chart
|
||||
*/
|
||||
|
@ -94,14 +100,14 @@ export interface DocumentCountChartProps {
|
|||
fieldFormats: FieldFormatsStart;
|
||||
uiSettings: IUiSettingsClient;
|
||||
};
|
||||
/** Optional callback function which gets called the brush selection has changed */
|
||||
brushSelectionUpdateHandler?: (windowParameters: WindowParameters, force: boolean) => void;
|
||||
/** Optional callback for handling brush selection updates */
|
||||
brushSelectionUpdateHandler?: BrushSelectionUpdateHandler;
|
||||
/** Optional width */
|
||||
width?: number;
|
||||
/** Data chart points */
|
||||
chartPoints: DocumentCountChartPoint[];
|
||||
chartPoints: LogRateHistogramItem[];
|
||||
/** Data chart points split */
|
||||
chartPointsSplit?: DocumentCountChartPoint[];
|
||||
chartPointsSplit?: LogRateHistogramItem[];
|
||||
/** Start time range for the chart */
|
||||
timeRangeEarliest: number;
|
||||
/** Ending time range for the chart */
|
||||
|
@ -162,42 +168,30 @@ function getBaselineBadgeOverflow(
|
|||
/**
|
||||
* Document count chart with draggable brushes to select time ranges
|
||||
* by default use `Baseline` and `Deviation` for the badge names
|
||||
* @param dependencies - List of Kibana services that are required as dependencies
|
||||
* @param brushSelectionUpdateHandler - Optional callback function which gets called the brush selection has changed
|
||||
* @param width - Optional width
|
||||
* @param chartPoints - Data chart points
|
||||
* @param chartPointsSplit - Data chart points split
|
||||
* @param timeRangeEarliest - Start time range for the chart
|
||||
* @param timeRangeLatest - Ending time range for the chart
|
||||
* @param interval - Time interval for the document count buckets
|
||||
* @param chartPointsSplitLabel - Label to name the adjustedChartPointsSplit histogram
|
||||
* @param isBrushCleared - Whether or not brush has been reset
|
||||
* @param autoAnalysisStart - Timestamp for start of initial analysis
|
||||
* @param barColorOverride - Optional color override for the default bar color for charts
|
||||
* @param barStyleAccessor - Optional style to override bar chart
|
||||
* @param barHighlightColorOverride - Optional color override for the highlighted bar color for charts
|
||||
* @param deviationBrush - Optional settings override for the 'deviation' brush
|
||||
* @param baselineBrush - Optional settings override for the 'baseline' brush
|
||||
* @constructor
|
||||
*
|
||||
* @param props DocumentCountChart component props
|
||||
* @returns The DocumentCountChart component.
|
||||
*/
|
||||
export const DocumentCountChart: FC<DocumentCountChartProps> = ({
|
||||
dependencies,
|
||||
brushSelectionUpdateHandler,
|
||||
width,
|
||||
chartPoints,
|
||||
chartPointsSplit,
|
||||
timeRangeEarliest,
|
||||
timeRangeLatest,
|
||||
interval,
|
||||
chartPointsSplitLabel,
|
||||
isBrushCleared,
|
||||
autoAnalysisStart,
|
||||
barColorOverride,
|
||||
barStyleAccessor,
|
||||
barHighlightColorOverride,
|
||||
deviationBrush = {},
|
||||
baselineBrush = {},
|
||||
}) => {
|
||||
export const DocumentCountChart: FC<DocumentCountChartProps> = (props) => {
|
||||
const {
|
||||
dependencies,
|
||||
brushSelectionUpdateHandler,
|
||||
width,
|
||||
chartPoints,
|
||||
chartPointsSplit,
|
||||
timeRangeEarliest,
|
||||
timeRangeLatest,
|
||||
interval,
|
||||
chartPointsSplitLabel,
|
||||
isBrushCleared,
|
||||
autoAnalysisStart,
|
||||
barColorOverride,
|
||||
barStyleAccessor,
|
||||
barHighlightColorOverride,
|
||||
deviationBrush = {},
|
||||
baselineBrush = {},
|
||||
} = props;
|
||||
|
||||
const { data, uiSettings, fieldFormats, charts } = dependencies;
|
||||
|
||||
const chartTheme = charts.theme.useChartsTheme();
|
||||
|
@ -333,8 +327,13 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = ({
|
|||
const wpSnap = getSnappedWindowParameters(wp, snapTimestamps);
|
||||
setOriginalWindowParameters(wpSnap);
|
||||
setWindowParameters(wpSnap);
|
||||
|
||||
if (brushSelectionUpdateHandler !== undefined) {
|
||||
brushSelectionUpdateHandler(wpSnap, true);
|
||||
brushSelectionUpdateHandler(
|
||||
wpSnap,
|
||||
true,
|
||||
getLogRateAnalysisType(adjustedChartPoints, wpSnap)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -385,7 +384,7 @@ export const DocumentCountChart: FC<DocumentCountChartProps> = ({
|
|||
}
|
||||
setWindowParameters(wp);
|
||||
setWindowParametersAsPixels(wpPx);
|
||||
brushSelectionUpdateHandler(wp, false);
|
||||
brushSelectionUpdateHandler(wp, false, getLogRateAnalysisType(adjustedChartPoints, wp));
|
||||
}
|
||||
|
||||
const [mlBrushWidth, setMlBrushWidth] = useState<number>();
|
||||
|
|
|
@ -6,4 +6,8 @@
|
|||
*/
|
||||
|
||||
export { DocumentCountChart } from './document_count_chart';
|
||||
export type { DocumentCountChartPoint, DocumentCountChartProps } from './document_count_chart';
|
||||
export type {
|
||||
BrushSelectionUpdateHandler,
|
||||
BrushSettings,
|
||||
DocumentCountChartProps,
|
||||
} from './document_count_chart';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { isEqual } from 'lodash';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import React, { useEffect, useRef, type FC } from 'react';
|
||||
|
||||
import * as d3Brush from 'd3-brush';
|
||||
import * as d3Scale from 'd3-scale';
|
||||
|
@ -54,6 +54,9 @@ const BRUSH_MARGIN = 4;
|
|||
const BRUSH_HANDLE_SIZE = 4;
|
||||
const BRUSH_HANDLE_ROUNDED_CORNER = 2;
|
||||
|
||||
/**
|
||||
* Props for the DualBrush React Component
|
||||
*/
|
||||
interface DualBrushProps {
|
||||
/**
|
||||
* Min and max numeric timestamps for the two brushes
|
||||
|
@ -88,40 +91,12 @@ interface DualBrushProps {
|
|||
/**
|
||||
* DualBrush React Component
|
||||
* Dual brush component that overlays the document count chart
|
||||
* @type {FC<DualBrushProps>}
|
||||
* @param props - `DualBrushProps` component props
|
||||
* @returns {React.ReactElement} The DualBrush component.
|
||||
*
|
||||
* @param props DualBrushProps component props
|
||||
* @returns The DualBrush component.
|
||||
*/
|
||||
export function DualBrush({
|
||||
/**
|
||||
* Min and max numeric timestamps for the two brushes
|
||||
*/
|
||||
windowParameters,
|
||||
/**
|
||||
* Min timestamp for x domain
|
||||
*/
|
||||
min,
|
||||
/**
|
||||
* Max timestamp for x domain
|
||||
*/
|
||||
max,
|
||||
/**
|
||||
* Callback function whenever the brush changes
|
||||
*/
|
||||
onChange,
|
||||
/**
|
||||
* Margin left
|
||||
*/
|
||||
marginLeft,
|
||||
/**
|
||||
* Nearest timestamps to snap to the brushes to
|
||||
*/
|
||||
snapTimestamps,
|
||||
/**
|
||||
* Width
|
||||
*/
|
||||
width,
|
||||
}: DualBrushProps) {
|
||||
export const DualBrush: FC<DualBrushProps> = (props) => {
|
||||
const { windowParameters, min, max, onChange, marginLeft, snapTimestamps, width } = props;
|
||||
const d3BrushContainer = useRef(null);
|
||||
const brushes = useRef<DualBrush[]>([]);
|
||||
|
||||
|
@ -383,4 +358,4 @@ export function DualBrush({
|
|||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -21,11 +21,12 @@ interface BrushAnnotationProps {
|
|||
/**
|
||||
* DualBrushAnnotation React Component
|
||||
* Dual brush annotation component that overlays the document count chart
|
||||
* @type {FC<BrushAnnotationProps>}
|
||||
* @param props - `BrushAnnotationProps` component props
|
||||
* @returns {React.ReactElement} The DualBrushAnnotation component.
|
||||
*
|
||||
* @param props BrushAnnotationProps component props
|
||||
* @returns The DualBrushAnnotation component.
|
||||
*/
|
||||
export const DualBrushAnnotation: FC<BrushAnnotationProps> = ({ id, min, max, style }) => {
|
||||
export const DualBrushAnnotation: FC<BrushAnnotationProps> = (props) => {
|
||||
const { id, min, max, style } = props;
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { colors } = euiTheme;
|
||||
|
||||
|
|
|
@ -44,38 +44,25 @@ interface ProgressControlProps {
|
|||
/**
|
||||
* ProgressControls React Component
|
||||
* Component with ability to Run & cancel analysis
|
||||
* by default use `Baseline` and `Deviation` for the badge name
|
||||
* @type {FC<ProgressControlProps>}
|
||||
* @param children - List of Kibana services that are required as dependencies
|
||||
* @param brushSelectionUpdateHandler - Optional callback function which gets called the brush selection has changed
|
||||
* @param width - Optional width
|
||||
* @param chartPoints - Data chart points
|
||||
* @param chartPointsSplit - Data chart points split
|
||||
* @param timeRangeEarliest - Start time range for the chart
|
||||
* @param timeRangeLatest - Ending time range for the chart
|
||||
* @param interval - Time interval for the document count buckets
|
||||
* @param chartPointsSplitLabel - Label to name the adjustedChartPointsSplit histogram
|
||||
* @param isBrushCleared - Whether or not brush has been reset
|
||||
* @param autoAnalysisStart - Timestamp for start of initial analysis
|
||||
* @param barColorOverride - Optional color override for the default bar color for charts
|
||||
* @param barStyleAccessor - Optional style to override bar chart
|
||||
* @param barHighlightColorOverride - Optional color override for the highlighted bar color for charts
|
||||
* @param deviationBrush - Optional settings override for the 'deviation' brush
|
||||
* @param baselineBrush - Optional settings override for the 'baseline' brush
|
||||
* @returns {React.ReactElement} The ProgressControls component.
|
||||
* by default uses `Baseline` and `Deviation` for the badge name
|
||||
*
|
||||
* @param props ProgressControls component props
|
||||
* @returns The ProgressControls component.
|
||||
*/
|
||||
export const ProgressControls: FC<ProgressControlProps> = ({
|
||||
children,
|
||||
isBrushCleared,
|
||||
progress,
|
||||
progressMessage,
|
||||
onRefresh,
|
||||
onCancel,
|
||||
onReset,
|
||||
isRunning,
|
||||
shouldRerunAnalysis,
|
||||
runAnalysisDisabled = false,
|
||||
}) => {
|
||||
export const ProgressControls: FC<ProgressControlProps> = (props) => {
|
||||
const {
|
||||
children,
|
||||
isBrushCleared,
|
||||
progress,
|
||||
progressMessage,
|
||||
onRefresh,
|
||||
onCancel,
|
||||
onReset,
|
||||
isRunning,
|
||||
shouldRerunAnalysis,
|
||||
runAnalysisDisabled = false,
|
||||
} = props;
|
||||
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const runningProgressBarStyles = useAnimatedProgressBarBackground(euiTheme.colors.success);
|
||||
const analysisCompleteStyle = { display: 'none' };
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 { LogRateHistogramItem } from './log_rate_histogram_item';
|
||||
import { getLogRateAnalysisType } from './get_log_rate_analysis_type';
|
||||
|
||||
describe('getLogRateAnalysisType', () => {
|
||||
const LogRateHistogramMock: LogRateHistogramItem[] = [
|
||||
{ time: 0, value: 10 },
|
||||
{ time: 1, value: 10 },
|
||||
{ time: 2, value: 10 },
|
||||
{ time: 3, value: 5 },
|
||||
{ time: 4, value: 10 },
|
||||
{ time: 5, value: 10 },
|
||||
{ time: 6, value: 10 },
|
||||
{ time: 7, value: 20 },
|
||||
{ time: 8, value: 10 },
|
||||
{ time: 9, value: 10 },
|
||||
];
|
||||
|
||||
test('returns "spike" for the given parameters', () => {
|
||||
expect(
|
||||
getLogRateAnalysisType(LogRateHistogramMock, {
|
||||
baselineMin: 4,
|
||||
baselineMax: 6,
|
||||
deviationMin: 7,
|
||||
deviationMax: 8,
|
||||
})
|
||||
).toBe('spike');
|
||||
});
|
||||
|
||||
test('returns "dip" for the given parameters', () => {
|
||||
expect(
|
||||
getLogRateAnalysisType(LogRateHistogramMock, {
|
||||
baselineMin: 0,
|
||||
baselineMax: 2,
|
||||
deviationMin: 3,
|
||||
deviationMax: 4,
|
||||
})
|
||||
).toBe('dip');
|
||||
});
|
||||
|
||||
test('falls back to "spike" if both time range have the same median', () => {
|
||||
expect(
|
||||
getLogRateAnalysisType(LogRateHistogramMock, {
|
||||
baselineMin: 0,
|
||||
baselineMax: 2,
|
||||
deviationMin: 4,
|
||||
deviationMax: 6,
|
||||
})
|
||||
).toBe('spike');
|
||||
});
|
||||
});
|
40
x-pack/packages/ml/aiops_utils/get_log_rate_analysis_type.ts
Normal file
40
x-pack/packages/ml/aiops_utils/get_log_rate_analysis_type.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 { median } from 'd3-array';
|
||||
|
||||
import { LOG_RATE_ANALYSIS_TYPE, type LogRateAnalysisType } from './log_rate_analysis_type';
|
||||
import type { LogRateHistogramItem } from './log_rate_histogram_item';
|
||||
import type { WindowParameters } from './window_parameters';
|
||||
|
||||
/**
|
||||
* Identify the log rate analysis type based on the baseline/deviation
|
||||
* time ranges on a given log rate histogram.
|
||||
*
|
||||
* @param logRateHistogram The log rate histogram.
|
||||
* @param windowParameters The window parameters with baseline and deviation time range.
|
||||
* @returns The log rate analysis type.
|
||||
*/
|
||||
export function getLogRateAnalysisType(
|
||||
logRateHistogram: LogRateHistogramItem[],
|
||||
windowParameters: WindowParameters
|
||||
): LogRateAnalysisType {
|
||||
const { baselineMin, baselineMax, deviationMin, deviationMax } = windowParameters;
|
||||
const baselineItems = logRateHistogram.filter(
|
||||
(d) => d.time >= baselineMin && d.time < baselineMax
|
||||
);
|
||||
const baselineMedian = median(baselineItems.map((d) => d.value)) ?? 0;
|
||||
|
||||
const deviationItems = logRateHistogram.filter(
|
||||
(d) => d.time >= deviationMin && d.time < deviationMax
|
||||
);
|
||||
const deviationMedian = median(deviationItems.map((d) => d.value)) ?? 0;
|
||||
|
||||
return deviationMedian >= baselineMedian
|
||||
? LOG_RATE_ANALYSIS_TYPE.SPIKE
|
||||
: LOG_RATE_ANALYSIS_TYPE.DIP;
|
||||
}
|
|
@ -5,5 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { getSnappedWindowParameters, getWindowParameters } from './src/get_window_parameters';
|
||||
export type { WindowParameters } from './src/get_window_parameters';
|
||||
export { getLogRateAnalysisType } from './get_log_rate_analysis_type';
|
||||
export { LOG_RATE_ANALYSIS_TYPE, type LogRateAnalysisType } from './log_rate_analysis_type';
|
||||
export { type LogRateHistogramItem } from './log_rate_histogram_item';
|
||||
export {
|
||||
getSnappedWindowParameters,
|
||||
getWindowParameters,
|
||||
type WindowParameters,
|
||||
} from './window_parameters';
|
||||
|
|
21
x-pack/packages/ml/aiops_utils/log_rate_analysis_type.ts
Normal file
21
x-pack/packages/ml/aiops_utils/log_rate_analysis_type.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The type of log rate analysis (spike or dip) will affect how parameters are
|
||||
* passed to the analysis API endpoint.
|
||||
*/
|
||||
export const LOG_RATE_ANALYSIS_TYPE = {
|
||||
SPIKE: 'spike',
|
||||
DIP: 'dip',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Union type of log rate analysis types.
|
||||
*/
|
||||
export type LogRateAnalysisType =
|
||||
typeof LOG_RATE_ANALYSIS_TYPE[keyof typeof LOG_RATE_ANALYSIS_TYPE];
|
20
x-pack/packages/ml/aiops_utils/log_rate_histogram_item.ts
Normal file
20
x-pack/packages/ml/aiops_utils/log_rate_histogram_item.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Log rate histogram item
|
||||
*/
|
||||
export interface LogRateHistogramItem {
|
||||
/**
|
||||
* Time of bucket
|
||||
*/
|
||||
time: number | string;
|
||||
/**
|
||||
* Number of doc count for that time bucket
|
||||
*/
|
||||
value: number;
|
||||
}
|
|
@ -10,20 +10,6 @@
|
|||
*/
|
||||
export const LOG_RATE_ANALYSIS_P_VALUE_THRESHOLD = 0.02;
|
||||
|
||||
/**
|
||||
* The type of log rate analysis (spike or dip) will affect how parameters are
|
||||
* passed to the analysis API endpoint.
|
||||
*/
|
||||
export const LOG_RATE_ANALYSIS_TYPE = {
|
||||
SPIKE: 'spike',
|
||||
DIP: 'dip',
|
||||
} as const;
|
||||
/**
|
||||
* Union type of log rate analysis types.
|
||||
*/
|
||||
export type LogRateAnalysisType =
|
||||
typeof LOG_RATE_ANALYSIS_TYPE[keyof typeof LOG_RATE_ANALYSIS_TYPE];
|
||||
|
||||
/**
|
||||
* For the technical preview of Log Rate Analysis we use a hard coded seed.
|
||||
* In future versions we might use a user specific seed or let the user customise it.
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export type { LogRateAnalysisType } from './constants';
|
||||
|
||||
/**
|
||||
* PLUGIN_ID is used as a unique identifier for the aiops plugin
|
||||
*/
|
||||
|
|
|
@ -13,8 +13,8 @@ import {
|
|||
RectAnnotationSpec,
|
||||
} from '@elastic/charts/dist/chart_types/xy_chart/utils/specs';
|
||||
|
||||
import type { WindowParameters } from '@kbn/aiops-utils';
|
||||
import { DocumentCountChart, type DocumentCountChartPoint } from '@kbn/aiops-components';
|
||||
import type { LogRateHistogramItem, WindowParameters } from '@kbn/aiops-utils';
|
||||
import { DocumentCountChart, type BrushSelectionUpdateHandler } from '@kbn/aiops-components';
|
||||
|
||||
import { useAiopsAppContext } from '../../../hooks/use_aiops_app_context';
|
||||
import { DocumentCountStats } from '../../../get_document_stats';
|
||||
|
@ -22,7 +22,7 @@ import { DocumentCountStats } from '../../../get_document_stats';
|
|||
import { TotalCountHeader } from '../total_count_header';
|
||||
|
||||
export interface DocumentCountContentProps {
|
||||
brushSelectionUpdateHandler: (d: WindowParameters, force: boolean) => void;
|
||||
brushSelectionUpdateHandler: BrushSelectionUpdateHandler;
|
||||
documentCountStats?: DocumentCountStats;
|
||||
documentCountStatsSplit?: DocumentCountStats;
|
||||
documentCountStatsSplitLabel?: string;
|
||||
|
@ -78,14 +78,14 @@ export const DocumentCountContent: FC<DocumentCountContentProps> = ({
|
|||
) : null;
|
||||
}
|
||||
|
||||
const chartPoints: DocumentCountChartPoint[] = Object.entries(documentCountStats.buckets).map(
|
||||
const chartPoints: LogRateHistogramItem[] = Object.entries(documentCountStats.buckets).map(
|
||||
([time, value]) => ({
|
||||
time: +time,
|
||||
value,
|
||||
})
|
||||
);
|
||||
|
||||
let chartPointsSplit: DocumentCountChartPoint[] | undefined;
|
||||
let chartPointsSplit: LogRateHistogramItem[] | undefined;
|
||||
if (documentCountStatsSplit?.buckets !== undefined) {
|
||||
chartPointsSplit = Object.entries(documentCountStatsSplit?.buckets).map(([time, value]) => ({
|
||||
time: +time,
|
||||
|
|
|
@ -15,11 +15,13 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import type { Dictionary } from '@kbn/ml-url-state';
|
||||
import type { WindowParameters } from '@kbn/aiops-utils';
|
||||
import {
|
||||
LOG_RATE_ANALYSIS_TYPE,
|
||||
type LogRateAnalysisType,
|
||||
type WindowParameters,
|
||||
} from '@kbn/aiops-utils';
|
||||
import type { SignificantTerm } from '@kbn/ml-agg-utils';
|
||||
|
||||
import { LOG_RATE_ANALYSIS_TYPE, type LogRateAnalysisType } from '../../../../common/constants';
|
||||
|
||||
import { useData } from '../../../hooks/use_data';
|
||||
|
||||
import { DocumentCountContent } from '../../document_count_content/document_count_content';
|
||||
|
@ -48,8 +50,6 @@ export function getDocumentCountStatsSplitLabel(
|
|||
export interface LogRateAnalysisContentProps {
|
||||
/** The data view to analyze. */
|
||||
dataView: DataView;
|
||||
/** The type of analysis, whether it's a spike or dip */
|
||||
analysisType?: LogRateAnalysisType;
|
||||
setGlobalState?: (params: Dictionary<unknown>) => void;
|
||||
/** Timestamp for the start of the range for initial analysis */
|
||||
initialAnalysisStart?: number | WindowParameters;
|
||||
|
@ -68,7 +68,6 @@ export interface LogRateAnalysisContentProps {
|
|||
|
||||
export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
|
||||
dataView,
|
||||
analysisType = LOG_RATE_ANALYSIS_TYPE.SPIKE,
|
||||
setGlobalState,
|
||||
initialAnalysisStart: incomingInitialAnalysisStart,
|
||||
timeRange,
|
||||
|
@ -83,6 +82,9 @@ export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
|
|||
number | WindowParameters | undefined
|
||||
>(incomingInitialAnalysisStart);
|
||||
const [isBrushCleared, setIsBrushCleared] = useState(true);
|
||||
const [logRateAnalysisType, setLogRateAnalysisType] = useState<LogRateAnalysisType>(
|
||||
LOG_RATE_ANALYSIS_TYPE.SPIKE
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setIsBrushCleared(windowParameters === undefined);
|
||||
|
@ -111,13 +113,18 @@ export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
|
|||
const { sampleProbability, totalCount, documentCountStats, documentCountStatsCompare } =
|
||||
documentStats;
|
||||
|
||||
function brushSelectionUpdate(d: WindowParameters, force: boolean) {
|
||||
function brushSelectionUpdate(
|
||||
windowParametersUpdate: WindowParameters,
|
||||
force: boolean,
|
||||
logRateAnalysisTypeUpdate: LogRateAnalysisType
|
||||
) {
|
||||
if (!isBrushCleared || force) {
|
||||
setWindowParameters(d);
|
||||
setWindowParameters(windowParametersUpdate);
|
||||
}
|
||||
if (force) {
|
||||
setIsBrushCleared(false);
|
||||
}
|
||||
setLogRateAnalysisType(logRateAnalysisTypeUpdate);
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
|
@ -153,7 +160,7 @@ export const LogRateAnalysisContent: FC<LogRateAnalysisContentProps> = ({
|
|||
{earliest !== undefined && latest !== undefined && windowParameters !== undefined && (
|
||||
<LogRateAnalysisResults
|
||||
dataView={dataView}
|
||||
analysisType={analysisType}
|
||||
analysisType={logRateAnalysisType}
|
||||
earliest={earliest}
|
||||
isBrushCleared={isBrushCleared}
|
||||
latest={latest}
|
||||
|
|
|
@ -20,7 +20,6 @@ import { DatePickerContextProvider } from '@kbn/ml-date-picker';
|
|||
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||
import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
import { LOG_RATE_ANALYSIS_TYPE, type LogRateAnalysisType } from '../../../../common/constants';
|
||||
import { timeSeriesDataViewWarning } from '../../../application/utils/time_series_dataview_check';
|
||||
import { AiopsAppContext, type AiopsAppDependencies } from '../../../hooks/use_aiops_app_context';
|
||||
import { DataSourceContext } from '../../../hooks/use_data_source';
|
||||
|
@ -38,8 +37,6 @@ const localStorage = new Storage(window.localStorage);
|
|||
export interface LogRateAnalysisContentWrapperProps {
|
||||
/** The data view to analyze. */
|
||||
dataView: DataView;
|
||||
/** The type of analysis, whether it's a spike or dip */
|
||||
analysisType?: LogRateAnalysisType;
|
||||
/** Option to make main histogram sticky */
|
||||
stickyHistogram?: boolean;
|
||||
/** App dependencies */
|
||||
|
@ -65,7 +62,6 @@ export interface LogRateAnalysisContentWrapperProps {
|
|||
|
||||
export const LogRateAnalysisContentWrapper: FC<LogRateAnalysisContentWrapperProps> = ({
|
||||
dataView,
|
||||
analysisType = LOG_RATE_ANALYSIS_TYPE.SPIKE,
|
||||
appDependencies,
|
||||
setGlobalState,
|
||||
initialAnalysisStart,
|
||||
|
@ -100,7 +96,6 @@ export const LogRateAnalysisContentWrapper: FC<LogRateAnalysisContentWrapperProp
|
|||
<DatePickerContextProvider {...datePickerDeps}>
|
||||
<LogRateAnalysisContent
|
||||
dataView={dataView}
|
||||
analysisType={analysisType}
|
||||
setGlobalState={setGlobalState}
|
||||
initialAnalysisStart={initialAnalysisStart}
|
||||
timeRange={timeRange}
|
||||
|
|
|
@ -24,13 +24,16 @@ import {
|
|||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { ProgressControls } from '@kbn/aiops-components';
|
||||
import { useFetchStream } from '@kbn/ml-response-stream/client';
|
||||
import type { WindowParameters } from '@kbn/aiops-utils';
|
||||
import {
|
||||
LOG_RATE_ANALYSIS_TYPE,
|
||||
type LogRateAnalysisType,
|
||||
type WindowParameters,
|
||||
} from '@kbn/aiops-utils';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { SignificantTerm, SignificantTermGroup } from '@kbn/ml-agg-utils';
|
||||
|
||||
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
||||
import { LOG_RATE_ANALYSIS_TYPE, type LogRateAnalysisType } from '../../../common/constants';
|
||||
import { initialState, streamReducer } from '../../../common/api/stream_reducer';
|
||||
import type { AiopsApiLogRateAnalysis } from '../../../common/api';
|
||||
import {
|
||||
|
@ -73,6 +76,8 @@ const resultsGroupedOnId = 'aiopsLogRateAnalysisGroupingOn';
|
|||
* Interface for log rate analysis results data.
|
||||
*/
|
||||
export interface LogRateAnalysisResultsData {
|
||||
/** The type of analysis, whether it's a spike or dip */
|
||||
analysisType: LogRateAnalysisType;
|
||||
/** Statistically significant field/value items. */
|
||||
significantTerms: SignificantTerm[];
|
||||
/** Statistically significant groups of field/value items. */
|
||||
|
@ -129,6 +134,7 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
|
|||
|
||||
const { clearAllRowState } = useLogRateAnalysisResultsTableRowContext();
|
||||
|
||||
const [currentAnalysisType, setCurrentAnalysisType] = useState<LogRateAnalysisType | undefined>();
|
||||
const [currentAnalysisWindowParameters, setCurrentAnalysisWindowParameters] = useState<
|
||||
WindowParameters | undefined
|
||||
>();
|
||||
|
@ -215,6 +221,7 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
|
|||
setOverrides(undefined);
|
||||
if (onAnalysisCompleted) {
|
||||
onAnalysisCompleted({
|
||||
analysisType,
|
||||
significantTerms: data.significantTerms,
|
||||
significantTermsGroups: data.significantTermsGroups,
|
||||
});
|
||||
|
@ -241,6 +248,7 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
|
|||
clearAllRowState();
|
||||
}
|
||||
|
||||
setCurrentAnalysisType(analysisType);
|
||||
setCurrentAnalysisWindowParameters(windowParameters);
|
||||
|
||||
// We trigger hooks updates above so we cannot directly call `start()` here
|
||||
|
@ -257,6 +265,7 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
|
|||
}, [shouldStart]);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentAnalysisType(analysisType);
|
||||
setCurrentAnalysisWindowParameters(windowParameters);
|
||||
start();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
@ -341,6 +350,40 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
|
|||
/>
|
||||
</EuiFlexItem>
|
||||
</ProgressControls>
|
||||
{showLogRateAnalysisResultsTable && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCallOut
|
||||
title={
|
||||
<span data-test-subj="aiopsAnalysisTypeCalloutTitle">
|
||||
{currentAnalysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE
|
||||
? i18n.translate('xpack.aiops.analysis.analysisTypeSpikeCallOutTitle', {
|
||||
defaultMessage: 'Analysis type: Log rate spike',
|
||||
})
|
||||
: i18n.translate('xpack.aiops.analysis.analysisTypeDipCallOutTitle', {
|
||||
defaultMessage: 'Analysis type: Log rate dip',
|
||||
})}
|
||||
</span>
|
||||
}
|
||||
color="primary"
|
||||
iconType="pin"
|
||||
size="s"
|
||||
>
|
||||
<EuiText size="s">
|
||||
{currentAnalysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE
|
||||
? i18n.translate('xpack.aiops.analysis.analysisTypeSpikeCallOutContent', {
|
||||
defaultMessage:
|
||||
'The median log rate in the selected deviation time range is higher than the baseline. Therefore, the analysis results table shows statistically significant items within the deviation time range that are contributors to the spike. The "doc count" column refers to the amount of documents in the deviation time range.',
|
||||
})
|
||||
: i18n.translate('xpack.aiops.analysis.analysisTypeDipCallOutContent', {
|
||||
defaultMessage:
|
||||
'The median log rate in the selected deviation time range is lower than the baseline. Therefore, the analysis results table shows statistically significant items within the baseline time range that are less in number or missing within the deviation time range. The "doc count" column refers to the amount of documents in the baseline time range.',
|
||||
})}
|
||||
</EuiText>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="xs" />
|
||||
</>
|
||||
)}
|
||||
{errors.length > 0 ? (
|
||||
<>
|
||||
<EuiSpacer size="xs" />
|
||||
|
|
|
@ -13,8 +13,6 @@ export function plugin() {
|
|||
return new AiopsPlugin();
|
||||
}
|
||||
|
||||
export { LOG_RATE_ANALYSIS_TYPE, type LogRateAnalysisType } from '../common/constants';
|
||||
|
||||
export type { AiopsAppDependencies } from './hooks/use_aiops_app_context';
|
||||
export type { LogRateAnalysisAppStateProps } from './components/log_rate_analysis';
|
||||
export type { LogRateAnalysisContentWrapperProps } from './components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content_wrapper';
|
||||
|
|
|
@ -22,17 +22,14 @@ import {
|
|||
import moment from 'moment';
|
||||
import { IUiSettingsClient } from '@kbn/core/public';
|
||||
import { MULTILAYER_TIME_AXIS_STYLE } from '@kbn/charts-plugin/common';
|
||||
import type { LogRateHistogramItem } from '@kbn/aiops-utils';
|
||||
|
||||
import { EuiFlexGroup, EuiLoadingSpinner, EuiFlexItem } from '@elastic/eui';
|
||||
import { useDataVisualizerKibana } from '../../../../kibana_context';
|
||||
|
||||
export interface DocumentCountChartPoint {
|
||||
time: number | string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
width?: number;
|
||||
chartPoints: DocumentCountChartPoint[];
|
||||
chartPoints: LogRateHistogramItem[];
|
||||
timeRangeEarliest: number;
|
||||
timeRangeLatest: number;
|
||||
interval?: number;
|
||||
|
|
|
@ -5,5 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export type { DocumentCountChartPoint } from './document_count_chart';
|
||||
export { DocumentCountChart } from './document_count_chart';
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { DocumentCountChartPoint } from './document_count_chart';
|
||||
import type { LogRateHistogramItem } from '@kbn/aiops-utils';
|
||||
import {
|
||||
RandomSamplerOption,
|
||||
RANDOM_SAMPLER_SELECT_OPTIONS,
|
||||
|
@ -108,7 +108,7 @@ export const DocumentCountContent: FC<Props> = ({
|
|||
if (timeRangeEarliest === undefined || timeRangeLatest === undefined)
|
||||
return <TotalCountHeader totalCount={totalCount} />;
|
||||
|
||||
let chartPoints: DocumentCountChartPoint[] = [];
|
||||
let chartPoints: LogRateHistogramItem[] = [];
|
||||
if (documentCountStats.buckets !== undefined) {
|
||||
const buckets: Record<string, number> = documentCountStats?.buckets;
|
||||
chartPoints = Object.entries(buckets).map(([time, value]) => ({ time: +time, value }));
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { WindowParameters } from '@kbn/aiops-utils';
|
||||
import type { WindowParameters, LogRateHistogramItem } from '@kbn/aiops-utils';
|
||||
import React, { FC } from 'react';
|
||||
import { DocumentCountChart, type DocumentCountChartPoint } from '@kbn/aiops-components';
|
||||
import { DocumentCountChart } from '@kbn/aiops-components';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import type { DocumentCountChartProps } from '@kbn/aiops-components';
|
||||
import type { BrushSelectionUpdateHandler, DocumentCountChartProps } from '@kbn/aiops-components';
|
||||
import { RandomSampler } from '@kbn/ml-random-sampler-utils';
|
||||
import { useDataVisualizerKibana } from '../kibana_context';
|
||||
import { DocumentCountStats } from '../../../common/types/field_stats';
|
||||
|
@ -26,7 +26,7 @@ export interface DocumentCountContentProps
|
|||
| 'interval'
|
||||
| 'chartPointsSplitLabel'
|
||||
> {
|
||||
brushSelectionUpdateHandler: (d: WindowParameters, force: boolean) => void;
|
||||
brushSelectionUpdateHandler: BrushSelectionUpdateHandler;
|
||||
documentCountStats?: DocumentCountStats;
|
||||
documentCountStatsSplit?: DocumentCountStats;
|
||||
documentCountStatsSplitLabel?: string;
|
||||
|
@ -83,14 +83,14 @@ export const DocumentCountWithDualBrush: FC<DocumentCountContentProps> = ({
|
|||
return totalCount !== undefined ? <TotalCountHeader totalCount={totalCount} /> : null;
|
||||
}
|
||||
|
||||
const chartPoints: DocumentCountChartPoint[] = Object.entries(documentCountStats.buckets).map(
|
||||
const chartPoints: LogRateHistogramItem[] = Object.entries(documentCountStats.buckets).map(
|
||||
([time, value]) => ({
|
||||
time: +time,
|
||||
value,
|
||||
})
|
||||
);
|
||||
|
||||
let chartPointsSplit: DocumentCountChartPoint[] | undefined;
|
||||
let chartPointsSplit: LogRateHistogramItem[] | undefined;
|
||||
if (documentCountStatsSplit?.buckets !== undefined) {
|
||||
chartPointsSplit = Object.entries(documentCountStatsSplit?.buckets).map(([time, value]) => ({
|
||||
time: +time,
|
||||
|
|
|
@ -14,11 +14,10 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import {
|
||||
LogRateAnalysisContent,
|
||||
LOG_RATE_ANALYSIS_TYPE,
|
||||
type LogRateAnalysisResultsData,
|
||||
type LogRateAnalysisType,
|
||||
} from '@kbn/aiops-plugin/public';
|
||||
} from '@kbn/aiops-utils/log_rate_analysis_type';
|
||||
import { LogRateAnalysisContent, type LogRateAnalysisResultsData } from '@kbn/aiops-plugin/public';
|
||||
import { Rule } from '@kbn/alerting-plugin/common';
|
||||
import { TopAlert } from '@kbn/observability-plugin/public';
|
||||
import {
|
||||
|
@ -33,7 +32,6 @@ import { ALERT_END } from '@kbn/rule-data-utils';
|
|||
import { Color, colorTransformer } from '../../../../../../common/color_palette';
|
||||
import { useKibanaContextForPlugin } from '../../../../../hooks/use_kibana';
|
||||
import {
|
||||
Comparator,
|
||||
CountRuleParams,
|
||||
isRatioRuleParams,
|
||||
PartialRuleParams,
|
||||
|
@ -60,11 +58,9 @@ export const LogRateAnalysis: FC<AlertDetailsLogRateAnalysisSectionProps> = ({ r
|
|||
const [dataView, setDataView] = useState<DataView | undefined>();
|
||||
const [esSearchQuery, setEsSearchQuery] = useState<QueryDslQueryContainer | undefined>();
|
||||
const [logRateAnalysisParams, setLogRateAnalysisParams] = useState<
|
||||
{ significantFieldValues: SignificantFieldValue[] } | undefined
|
||||
| { logRateAnalysisType: LogRateAnalysisType; significantFieldValues: SignificantFieldValue[] }
|
||||
| undefined
|
||||
>();
|
||||
const [logRateAnalysisType, setLogRateAnalysisType] = useState<LogRateAnalysisType | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const validatedParams = useMemo(() => decodeOrThrow(ruleParamsRT)(rule.params), [rule]);
|
||||
|
||||
|
@ -95,19 +91,6 @@ export const LogRateAnalysis: FC<AlertDetailsLogRateAnalysisSectionProps> = ({ r
|
|||
|
||||
if (!isRatioRuleParams(validatedParams)) {
|
||||
getDataView();
|
||||
|
||||
switch (validatedParams.count.comparator) {
|
||||
case Comparator.GT:
|
||||
case Comparator.GT_OR_EQ:
|
||||
setLogRateAnalysisType(LOG_RATE_ANALYSIS_TYPE.SPIKE);
|
||||
break;
|
||||
case Comparator.LT:
|
||||
case Comparator.LT_OR_EQ:
|
||||
setLogRateAnalysisType(LOG_RATE_ANALYSIS_TYPE.DIP);
|
||||
break;
|
||||
default:
|
||||
setLogRateAnalysisType(undefined);
|
||||
}
|
||||
}
|
||||
}, [validatedParams, alert, dataViews, logsShared]);
|
||||
|
||||
|
@ -188,7 +171,13 @@ export const LogRateAnalysis: FC<AlertDetailsLogRateAnalysisSectionProps> = ({ r
|
|||
['pValue', 'docCount'],
|
||||
['asc', 'asc']
|
||||
).slice(0, 50);
|
||||
setLogRateAnalysisParams(significantFieldValues ? { significantFieldValues } : undefined);
|
||||
|
||||
const logRateAnalysisType = analysisResults?.analysisType;
|
||||
setLogRateAnalysisParams(
|
||||
significantFieldValues && logRateAnalysisType
|
||||
? { logRateAnalysisType, significantFieldValues }
|
||||
: undefined
|
||||
);
|
||||
};
|
||||
|
||||
const aiAssistant = useObservabilityAIAssistant();
|
||||
|
@ -201,6 +190,8 @@ export const LogRateAnalysis: FC<AlertDetailsLogRateAnalysisSectionProps> = ({ r
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const { logRateAnalysisType } = logRateAnalysisParams;
|
||||
|
||||
const header = 'Field name,Field value,Doc count,p-value';
|
||||
const rows = logRateAnalysisParams.significantFieldValues
|
||||
.map((item) => Object.values(item).join(','))
|
||||
|
@ -210,27 +201,34 @@ export const LogRateAnalysis: FC<AlertDetailsLogRateAnalysisSectionProps> = ({ r
|
|||
"Log Rate Analysis" is an AIOps feature that uses advanced statistical methods to identify reasons for increases and decreases in log rates. It makes it easy to find and investigate causes of unusual spikes or dips by using the analysis workflow view.
|
||||
You are using "Log Rate Analysis" and ran the statistical analysis on the log messages which occured during the alert.
|
||||
You received the following analysis results from "Log Rate Analysis" which list statistically significant co-occuring field/value combinations sorted from most significant (lower p-values) to least significant (higher p-values) that ${
|
||||
logRateAnalysisType === 'spike'
|
||||
logRateAnalysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE
|
||||
? 'contribute to the log rate spike'
|
||||
: 'are less or not present in the log rate dip'
|
||||
}:
|
||||
|
||||
${
|
||||
logRateAnalysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE
|
||||
? 'The median log rate in the selected deviation time range is higher than the baseline. Therefore, the results shows statistically significant items within the deviation time range that are contributors to the spike. The "doc count" column refers to the amount of documents in the deviation time range.'
|
||||
: 'The median log rate in the selected deviation time range is lower than the baseline. Therefore, the analysis results table shows statistically significant items within the baseline time range that are less in number or missing within the deviation time range. The "doc count" column refers to the amount of documents in the baseline time range.'
|
||||
}
|
||||
|
||||
${header}
|
||||
${rows}
|
||||
|
||||
Based on the above analysis results and your observability expert knowledge, output the following:
|
||||
Analyse the type of these logs and explain their usual purpose (1 paragraph).
|
||||
${
|
||||
logRateAnalysisType === 'spike'
|
||||
logRateAnalysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE
|
||||
? 'Based on the type of these logs do a root cause analysis on why the field and value combinations from the analysis results are causing this log rate spike (2 parapraphs)'
|
||||
: 'Based on the type of these logs do a concise analysis why the statistically significant field and value combinations are less present or missing from the log rate dip with concrete examples based on the analysis results data. Do not guess, just output what you are sure of (2 paragraphs)'
|
||||
: 'Based on the type of these logs explain why the statistically significant field and value combinations are less in number or missing from the log rate dip with concrete examples based on the analysis results data which contains items that are present in the baseline time range and are missing or less in number in the deviation time range (2 paragraphs)'
|
||||
}.
|
||||
${
|
||||
logRateAnalysisType === 'spike'
|
||||
logRateAnalysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE
|
||||
? 'Recommend concrete remediations to resolve the root cause (3 bullet points).'
|
||||
: ''
|
||||
}
|
||||
Do not repeat the given instructions in your output.`;
|
||||
|
||||
Do not mention indidivual p-values from the analysis results. Do not guess, just say what you are sure of. Do not repeat the given instructions in your output.`;
|
||||
|
||||
const now = new Date().toString();
|
||||
|
||||
|
@ -251,7 +249,7 @@ export const LogRateAnalysis: FC<AlertDetailsLogRateAnalysisSectionProps> = ({ r
|
|||
},
|
||||
},
|
||||
];
|
||||
}, [logRateAnalysisParams, logRateAnalysisType]);
|
||||
}, [logRateAnalysisParams]);
|
||||
|
||||
if (!dataView || !esSearchQuery) return null;
|
||||
|
||||
|
@ -271,7 +269,6 @@ export const LogRateAnalysis: FC<AlertDetailsLogRateAnalysisSectionProps> = ({ r
|
|||
<EuiFlexItem>
|
||||
<LogRateAnalysisContent
|
||||
dataView={dataView}
|
||||
analysisType={logRateAnalysisType}
|
||||
timeRange={timeRange}
|
||||
esSearchQuery={esSearchQuery}
|
||||
initialAnalysisStart={initialAnalysisStart}
|
||||
|
|
|
@ -68,6 +68,7 @@
|
|||
"@kbn/core-http-server",
|
||||
"@kbn/logs-shared-plugin",
|
||||
"@kbn/licensing-plugin",
|
||||
"@kbn/aiops-utils",
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -147,7 +147,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await aiops.logRateAnalysisPage.clickRerunAnalysisButton(true);
|
||||
}
|
||||
|
||||
await aiops.logRateAnalysisPage.assertAnalysisComplete();
|
||||
await aiops.logRateAnalysisPage.assertAnalysisComplete(testData.analysisType);
|
||||
|
||||
// The group switch should be disabled by default
|
||||
await aiops.logRateAnalysisPage.assertLogRateAnalysisResultsGroupSwitchExists(false);
|
||||
|
|
|
@ -5,10 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { LOG_RATE_ANALYSIS_TYPE, type LogRateAnalysisType } from '@kbn/aiops-utils';
|
||||
|
||||
import type { TestData } from './types';
|
||||
|
||||
export const kibanaLogsDataViewTestData: TestData = {
|
||||
suiteTitle: 'kibana sample data logs',
|
||||
analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE,
|
||||
dataGenerator: 'kibana_sample_data_logs',
|
||||
isSavedSearch: false,
|
||||
sourceIndexOrSavedSearch: 'kibana_sample_data_logs',
|
||||
|
@ -114,6 +117,7 @@ export const kibanaLogsDataViewTestData: TestData = {
|
|||
|
||||
export const farequoteDataViewTestData: TestData = {
|
||||
suiteTitle: 'farequote with spike',
|
||||
analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE,
|
||||
dataGenerator: 'farequote_with_spike',
|
||||
isSavedSearch: false,
|
||||
sourceIndexOrSavedSearch: 'ft_farequote',
|
||||
|
@ -131,6 +135,7 @@ export const farequoteDataViewTestData: TestData = {
|
|||
|
||||
export const farequoteDataViewTestDataWithQuery: TestData = {
|
||||
suiteTitle: 'farequote with spike',
|
||||
analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE,
|
||||
dataGenerator: 'farequote_with_spike',
|
||||
isSavedSearch: false,
|
||||
sourceIndexOrSavedSearch: 'ft_farequote',
|
||||
|
@ -171,11 +176,12 @@ const DAY_MS = 86400000;
|
|||
const DEVIATION_TS = REFERENCE_TS - DAY_MS * 2;
|
||||
const BASELINE_TS = DEVIATION_TS - DAY_MS * 1;
|
||||
|
||||
export const artificialLogDataViewTestData: TestData = {
|
||||
suiteTitle: 'artificial logs with spike',
|
||||
dataGenerator: 'artificial_logs_with_spike',
|
||||
const getArtificialLogDataViewTestData = (analysisType: LogRateAnalysisType): TestData => ({
|
||||
suiteTitle: `artificial logs with ${analysisType}`,
|
||||
analysisType,
|
||||
dataGenerator: `artificial_logs_with_${analysisType}`,
|
||||
isSavedSearch: false,
|
||||
sourceIndexOrSavedSearch: 'artificial_logs_with_spike',
|
||||
sourceIndexOrSavedSearch: `artificial_logs_with_${analysisType}`,
|
||||
brushBaselineTargetTimestamp: BASELINE_TS + DAY_MS / 2,
|
||||
brushDeviationTargetTimestamp: DEVIATION_TS + DAY_MS / 2,
|
||||
brushIntervalFactor: 10,
|
||||
|
@ -224,11 +230,12 @@ export const artificialLogDataViewTestData: TestData = {
|
|||
],
|
||||
fieldSelectorPopover: ['response_code', 'url', 'user'],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const logRateAnalysisTestData: TestData[] = [
|
||||
kibanaLogsDataViewTestData,
|
||||
farequoteDataViewTestData,
|
||||
farequoteDataViewTestDataWithQuery,
|
||||
artificialLogDataViewTestData,
|
||||
getArtificialLogDataViewTestData(LOG_RATE_ANALYSIS_TYPE.SPIKE),
|
||||
getArtificialLogDataViewTestData(LOG_RATE_ANALYSIS_TYPE.DIP),
|
||||
];
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { LogRateAnalysisType } from '@kbn/aiops-utils';
|
||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
||||
|
||||
interface TestDataTableActionLogPatternAnalysis {
|
||||
|
@ -44,6 +45,7 @@ interface TestDataExpectedWithoutSampleProbability {
|
|||
|
||||
export interface TestData {
|
||||
suiteTitle: string;
|
||||
analysisType: LogRateAnalysisType;
|
||||
dataGenerator: string;
|
||||
isSavedSearch?: boolean;
|
||||
sourceIndexOrSavedSearch: string;
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
|
||||
import { LOG_RATE_ANALYSIS_TYPE } from '@kbn/aiops-utils';
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export interface GeneratedDoc {
|
||||
|
@ -24,7 +26,7 @@ const DAY_MS = 86400000;
|
|||
const DEVIATION_TS = REFERENCE_TS - DAY_MS * 2;
|
||||
const BASELINE_TS = DEVIATION_TS - DAY_MS * 1;
|
||||
|
||||
function getArtificialLogsWithSpike(index: string) {
|
||||
function getArtificialLogsWithDeviation(index: string, deviationType: string) {
|
||||
const bulkBody: estypes.BulkRequest<GeneratedDoc, GeneratedDoc>['body'] = [];
|
||||
const action = { index: { _index: index } };
|
||||
let tsOffset = 0;
|
||||
|
@ -44,7 +46,7 @@ function getArtificialLogsWithSpike(index: string) {
|
|||
) {
|
||||
tsOffset = 0;
|
||||
[...Array(100)].forEach(() => {
|
||||
tsOffset += DAY_MS / 100;
|
||||
tsOffset += Math.round(DAY_MS / 100);
|
||||
const doc: GeneratedDoc = {
|
||||
user,
|
||||
response_code: responseCode,
|
||||
|
@ -74,14 +76,16 @@ function getArtificialLogsWithSpike(index: string) {
|
|||
['login.php', 'user.php', 'home.php'].forEach((url) => {
|
||||
tsOffset = 0;
|
||||
[...Array(docsPerUrl1[url])].forEach(() => {
|
||||
tsOffset += DAY_MS / docsPerUrl1[url];
|
||||
tsOffset += Math.round(DAY_MS / docsPerUrl1[url]);
|
||||
bulkBody.push(action);
|
||||
bulkBody.push({
|
||||
user: 'Peter',
|
||||
response_code: responseCode,
|
||||
url,
|
||||
version: 'v1.0.0',
|
||||
'@timestamp': DEVIATION_TS + tsOffset,
|
||||
'@timestamp':
|
||||
(deviationType === LOG_RATE_ANALYSIS_TYPE.SPIKE ? DEVIATION_TS : BASELINE_TS) +
|
||||
tsOffset,
|
||||
should_ignore_this_field: 'should_ignore_this_field',
|
||||
});
|
||||
});
|
||||
|
@ -97,14 +101,16 @@ function getArtificialLogsWithSpike(index: string) {
|
|||
['login.php', 'home.php'].forEach((url) => {
|
||||
tsOffset = 0;
|
||||
[...Array(docsPerUrl2[url] + userIndex)].forEach(() => {
|
||||
tsOffset += DAY_MS / docsPerUrl2[url];
|
||||
tsOffset += Math.round(DAY_MS / docsPerUrl2[url]);
|
||||
bulkBody.push(action);
|
||||
bulkBody.push({
|
||||
user,
|
||||
response_code: '500',
|
||||
url,
|
||||
version: 'v1.0.0',
|
||||
'@timestamp': DEVIATION_TS + tsOffset,
|
||||
'@timestamp':
|
||||
(deviationType === LOG_RATE_ANALYSIS_TYPE.SPIKE ? DEVIATION_TS : BASELINE_TS) +
|
||||
tsOffset,
|
||||
should_ignore_this_field: 'should_ignore_this_field',
|
||||
});
|
||||
});
|
||||
|
@ -159,17 +165,18 @@ export function LogRateAnalysisDataGeneratorProvider({ getService }: FtrProvider
|
|||
break;
|
||||
|
||||
case 'artificial_logs_with_spike':
|
||||
case 'artificial_logs_with_dip':
|
||||
try {
|
||||
await es.indices.delete({
|
||||
index: 'artificial_logs_with_spike',
|
||||
index: dataGenerator,
|
||||
});
|
||||
} catch (e) {
|
||||
log.info(`Could not delete index 'artificial_logs_with_spike' in before() callback`);
|
||||
log.info(`Could not delete index '${dataGenerator}' in before() callback`);
|
||||
}
|
||||
|
||||
// Create index with mapping
|
||||
await es.indices.create({
|
||||
index: 'artificial_logs_with_spike',
|
||||
index: dataGenerator,
|
||||
mappings: {
|
||||
properties: {
|
||||
user: { type: 'keyword' },
|
||||
|
@ -184,7 +191,10 @@ export function LogRateAnalysisDataGeneratorProvider({ getService }: FtrProvider
|
|||
|
||||
await es.bulk({
|
||||
refresh: 'wait_for',
|
||||
body: getArtificialLogsWithSpike('artificial_logs_with_spike'),
|
||||
body: getArtificialLogsWithDeviation(
|
||||
dataGenerator,
|
||||
dataGenerator.split('_').pop() ?? LOG_RATE_ANALYSIS_TYPE.SPIKE
|
||||
),
|
||||
});
|
||||
break;
|
||||
|
||||
|
@ -204,12 +214,13 @@ export function LogRateAnalysisDataGeneratorProvider({ getService }: FtrProvider
|
|||
break;
|
||||
|
||||
case 'artificial_logs_with_spike':
|
||||
case 'artificial_logs_with_dip':
|
||||
try {
|
||||
await es.indices.delete({
|
||||
index: 'artificial_logs_with_spike',
|
||||
index: dataGenerator,
|
||||
});
|
||||
} catch (e) {
|
||||
log.error(`Error deleting index 'artificial_logs_with_spike' in after() callback`);
|
||||
log.error(`Error deleting index '${dataGenerator}' in after() callback`);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import type { LogRateAnalysisType } from '@kbn/aiops-utils';
|
||||
|
||||
import type { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export function LogRateAnalysisPageProvider({ getService, getPageObject }: FtrProviderContext) {
|
||||
|
@ -239,11 +241,17 @@ export function LogRateAnalysisPageProvider({ getService, getPageObject }: FtrPr
|
|||
});
|
||||
},
|
||||
|
||||
async assertAnalysisComplete() {
|
||||
async assertAnalysisComplete(analisysType: LogRateAnalysisType) {
|
||||
await retry.tryForTime(30 * 1000, async () => {
|
||||
await testSubjects.existOrFail('aiopsAnalysisComplete');
|
||||
const currentProgressTitle = await testSubjects.getVisibleText('aiopsAnalysisComplete');
|
||||
expect(currentProgressTitle).to.be('Analysis complete');
|
||||
|
||||
await testSubjects.existOrFail('aiopsAnalysisTypeCalloutTitle');
|
||||
const currentAnalysisTypeCalloutTitle = await testSubjects.getVisibleText(
|
||||
'aiopsAnalysisTypeCalloutTitle'
|
||||
);
|
||||
expect(currentAnalysisTypeCalloutTitle).to.be(`Analysis type: Log rate ${analisysType}`);
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -137,6 +137,7 @@
|
|||
"@kbn/uptime-plugin",
|
||||
"@kbn/ml-category-validator",
|
||||
"@kbn/observability-ai-assistant-plugin",
|
||||
"@kbn/stack-connectors-plugin"
|
||||
"@kbn/stack-connectors-plugin",
|
||||
"@kbn/aiops-utils"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue