Skip to content

Commit

Permalink
[8.x] [Security Solution][Serverless] - Improve security solution per…
Browse files Browse the repository at this point in the history
…formance (#194241) (#194588)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Security Solution][Serverless] - Improve security solution
performance (#194241)](#194241)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Michael
Olorunnisola","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-09-27T18:45:45Z","message":"[Security
Solution][Serverless] - Improve security solution performance
(#194241)\n\n## Summary\r\n\r\nThe goal of this PR is to improve the
default performance of many of our\r\nsecurity solution views.\r\n\r\n1.
Upon scale testing, it was observed that the default events
histogram\r\naggregation was a source of application slowness, so to
improve the\r\nperformance of the default security experience, we've
made the default\r\nbreakdown for the events histogram `No Breakdown`
similar to what is\r\nseen in the default discover histogram
experience.\r\n\r\n2. After looking through some telemetry, it was
observed that the field\r\nlist query run in the background for timeline
can also take a\r\nsignificant amount of time based on the user's field
count, so we now\r\nonly run that query after timeline has been
opened.\r\n\r\n### Demos\r\n#### 1. By default the events visualizations
on the overview and explore\r\nevents pages will not have an
aggregation. The user will have to\r\nmanually select the breakdown they
desire:\r\nhttps://github.com/elastic/kibana/commit/d354d27962ebbd6d5fda19e912ec344ffe8a6c75\r\n\r\n\r\nhttps://github.com/user-attachments/assets/a6d6987b-73fc-4735-9c37-973917c2fa2d\r\n\r\n\r\n####
2. Timeline fields list will only load after the first
interaction\r\nwith
timeline:\r\nhttps://github.com/elastic/kibana/commit/ad557260d8f9c5dd0810a5a6aa51e5de0430000f\r\n\r\n**Before:**\r\n\r\n\r\nhttps://github.com/user-attachments/assets/0ad2e903-ac15-4daa-925b-da8ad05e80dd\r\n\r\n\r\n**After:**\r\n\r\n\r\nhttps://github.com/user-attachments/assets/27d5d3d5-02c8-49b5-b699-239ebc36b16c","sha":"e45d97b26c6d0e0798d620ad0b097cad9009c179","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement","backport:skip","v9.0.0","Team:Threat
Hunting:Investigations","Team:Threat
Hunting:Explore","backport:prev-minor","v8.16.0"],"number":194241,"url":"https://github.com/elastic/kibana/pull/194241","mergeCommit":{"message":"[Security
Solution][Serverless] - Improve security solution performance
(#194241)\n\n## Summary\r\n\r\nThe goal of this PR is to improve the
default performance of many of our\r\nsecurity solution views.\r\n\r\n1.
Upon scale testing, it was observed that the default events
histogram\r\naggregation was a source of application slowness, so to
improve the\r\nperformance of the default security experience, we've
made the default\r\nbreakdown for the events histogram `No Breakdown`
similar to what is\r\nseen in the default discover histogram
experience.\r\n\r\n2. After looking through some telemetry, it was
observed that the field\r\nlist query run in the background for timeline
can also take a\r\nsignificant amount of time based on the user's field
count, so we now\r\nonly run that query after timeline has been
opened.\r\n\r\n### Demos\r\n#### 1. By default the events visualizations
on the overview and explore\r\nevents pages will not have an
aggregation. The user will have to\r\nmanually select the breakdown they
desire:\r\nhttps://github.com/elastic/kibana/commit/d354d27962ebbd6d5fda19e912ec344ffe8a6c75\r\n\r\n\r\nhttps://github.com/user-attachments/assets/a6d6987b-73fc-4735-9c37-973917c2fa2d\r\n\r\n\r\n####
2. Timeline fields list will only load after the first
interaction\r\nwith
timeline:\r\nhttps://github.com/elastic/kibana/commit/ad557260d8f9c5dd0810a5a6aa51e5de0430000f\r\n\r\n**Before:**\r\n\r\n\r\nhttps://github.com/user-attachments/assets/0ad2e903-ac15-4daa-925b-da8ad05e80dd\r\n\r\n\r\n**After:**\r\n\r\n\r\nhttps://github.com/user-attachments/assets/27d5d3d5-02c8-49b5-b699-239ebc36b16c","sha":"e45d97b26c6d0e0798d620ad0b097cad9009c179"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/194241","number":194241,"mergeCommit":{"message":"[Security
Solution][Serverless] - Improve security solution performance
(#194241)\n\n## Summary\r\n\r\nThe goal of this PR is to improve the
default performance of many of our\r\nsecurity solution views.\r\n\r\n1.
Upon scale testing, it was observed that the default events
histogram\r\naggregation was a source of application slowness, so to
improve the\r\nperformance of the default security experience, we've
made the default\r\nbreakdown for the events histogram `No Breakdown`
similar to what is\r\nseen in the default discover histogram
experience.\r\n\r\n2. After looking through some telemetry, it was
observed that the field\r\nlist query run in the background for timeline
can also take a\r\nsignificant amount of time based on the user's field
count, so we now\r\nonly run that query after timeline has been
opened.\r\n\r\n### Demos\r\n#### 1. By default the events visualizations
on the overview and explore\r\nevents pages will not have an
aggregation. The user will have to\r\nmanually select the breakdown they
desire:\r\nhttps://github.com/elastic/kibana/commit/d354d27962ebbd6d5fda19e912ec344ffe8a6c75\r\n\r\n\r\nhttps://github.com/user-attachments/assets/a6d6987b-73fc-4735-9c37-973917c2fa2d\r\n\r\n\r\n####
2. Timeline fields list will only load after the first
interaction\r\nwith
timeline:\r\nhttps://github.com/elastic/kibana/commit/ad557260d8f9c5dd0810a5a6aa51e5de0430000f\r\n\r\n**Before:**\r\n\r\n\r\nhttps://github.com/user-attachments/assets/0ad2e903-ac15-4daa-925b-da8ad05e80dd\r\n\r\n\r\n**After:**\r\n\r\n\r\nhttps://github.com/user-attachments/assets/27d5d3d5-02c8-49b5-b699-239ebc36b16c","sha":"e45d97b26c6d0e0798d620ad0b097cad9009c179"}},{"branch":"8.x","label":"v8.16.0","labelRegex":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->
  • Loading branch information
michaelolo24 authored Oct 1, 2024
1 parent f45a06f commit ca251ff
Show file tree
Hide file tree
Showing 13 changed files with 206 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { EventsQueryTabBody, ALERTS_EVENTS_HISTOGRAM_ID } from './events_query_t
import { useGlobalFullScreen } from '../../containers/use_full_screen';
import { licenseService } from '../../hooks/use_license';
import { mockHistory } from '../../mock/router';
import { DEFAULT_EVENTS_STACK_BY_VALUE } from './histogram_configurations';

const mockGetDefaultControlColumn = jest.fn();
jest.mock('../../../timelines/components/timeline/body/control_columns', () => ({
Expand Down Expand Up @@ -144,7 +145,7 @@ describe('EventsQueryTabBody', () => {
);

expect(result.getByTestId('header-section-supplements').querySelector('select')?.value).toEqual(
'event.action'
DEFAULT_EVENTS_STACK_BY_VALUE
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import { getEventsHistogramLensAttributes } from '../visualization_actions/lens_
import type { MatrixHistogramConfigs, MatrixHistogramOption } from '../matrix_histogram/types';
import * as i18n from './translations';

const DEFAULT_EVENTS_STACK_BY = 'event.action';
export const NO_BREAKDOWN_STACK_BY_VALUE = 'no_breakdown';

export const DEFAULT_EVENTS_STACK_BY_VALUE = NO_BREAKDOWN_STACK_BY_VALUE;

export const getSubtitleFunction =
(defaultNumberFormat: string, isAlert: boolean) => (totalCount: number) =>
Expand All @@ -20,6 +22,10 @@ export const getSubtitleFunction =
}`;

export const eventsStackByOptions: MatrixHistogramOption[] = [
{
text: i18n.EVENTS_GRAPH_NO_BREAKDOWN_TITLE,
value: NO_BREAKDOWN_STACK_BY_VALUE,
},
{
text: 'event.action',
value: 'event.action',
Expand All @@ -36,7 +42,8 @@ export const eventsStackByOptions: MatrixHistogramOption[] = [

export const eventsHistogramConfig: MatrixHistogramConfigs = {
defaultStackByOption:
eventsStackByOptions.find((o) => o.text === DEFAULT_EVENTS_STACK_BY) ?? eventsStackByOptions[0],
eventsStackByOptions.find((o) => o.value === DEFAULT_EVENTS_STACK_BY_VALUE) ??
eventsStackByOptions[0],
stackByOptions: eventsStackByOptions,
subtitle: undefined,
title: i18n.EVENTS_GRAPH_TITLE,
Expand All @@ -58,7 +65,7 @@ const DEFAULT_STACK_BY = 'event.module';

export const alertsHistogramConfig: MatrixHistogramConfigs = {
defaultStackByOption:
alertsStackByOptions.find((o) => o.text === DEFAULT_STACK_BY) ?? alertsStackByOptions[0],
alertsStackByOptions.find((o) => o.value === DEFAULT_STACK_BY) ?? alertsStackByOptions[0],
stackByOptions: alertsStackByOptions,
title: i18n.ALERTS_GRAPH_TITLE,
getLensAttributes: getExternalAlertLensAttributes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,10 @@ export const SHOW_EXTERNAL_ALERTS = i18n.translate(
export const EVENTS_GRAPH_TITLE = i18n.translate('xpack.securitySolution.eventsGraphTitle', {
defaultMessage: 'Events',
});

export const EVENTS_GRAPH_NO_BREAKDOWN_TITLE = i18n.translate(
'xpack.securitySolution.eventsHistogram.selectOptions.noBreakDownLabel',
{
defaultMessage: 'No breakdown',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { VISUALIZATION_ACTIONS_BUTTON_CLASS } from '../visualization_actions/uti
import { VisualizationEmbeddable } from '../visualization_actions/visualization_embeddable';
import { useVisualizationResponse } from '../visualization_actions/use_visualization_response';
import type { SourcererScopeName } from '../../../sourcerer/store/model';
import { NO_BREAKDOWN_STACK_BY_VALUE } from '../events_tab/histogram_configurations';

export type MatrixHistogramComponentProps = MatrixHistogramQueryProps &
MatrixHistogramConfigs & {
Expand Down Expand Up @@ -165,6 +166,13 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
[isPtrIncluded, filterQuery]
);

// If the user selected the `No breakdown` option, we shouldn't perform the aggregation
const stackByField = useMemo(() => {
return selectedStackByOption.value === NO_BREAKDOWN_STACK_BY_VALUE
? undefined
: selectedStackByOption.value;
}, [selectedStackByOption.value]);

if (hideHistogram) {
return null;
}
Expand Down Expand Up @@ -216,7 +224,7 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
id={visualizationId}
inspectTitle={title as string}
lensAttributes={lensAttributes}
stackByField={selectedStackByOption.value}
stackByField={stackByField}
timerange={timerange}
/>
) : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { GetLensAttributes, LensAttributes } from '../visualization_actions

export interface MatrixHistogramOption {
text: string;
value: string;
value: string | undefined;
}

export type GetSubTitle = (count: number) => string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { wrapper } from '../../mocks';

import { useLensAttributes } from '../../use_lens_attributes';

import { getEventsHistogramLensAttributes } from './events';
import { getEventsHistogramLensAttributes, stackByFieldAccessorId } from './events';

jest.mock('uuid', () => ({
v4: jest.fn().mockReturnValue('0039eb0c-9a1a-4687-ae54-0f4e239bec75'),
Expand Down Expand Up @@ -497,4 +497,41 @@ describe('getEventsHistogramLensAttributes', () => {
})
);
});

it('should render the layer for the stackByField when provided', () => {
const { result } = renderHook(
() =>
useLensAttributes({
getLensAttributes: getEventsHistogramLensAttributes,
stackByField: 'event.dataset',
}),
{ wrapper }
);

expect(result?.current?.state?.visualization).toEqual(
expect.objectContaining({
layers: expect.arrayContaining([
expect.objectContaining({ splitAccessor: stackByFieldAccessorId }),
]),
})
);
});

it('should not render the layer for the stackByField is undefined', () => {
const { result } = renderHook(
() =>
useLensAttributes({
getLensAttributes: getEventsHistogramLensAttributes,
}),
{ wrapper }
);

expect(result?.current?.state?.visualization).toEqual(
expect.objectContaining({
layers: expect.arrayContaining([
expect.not.objectContaining({ splitAccessor: stackByFieldAccessorId }),
]),
})
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import { v4 as uuidv4 } from 'uuid';
import type { GetLensAttributes } from '../../types';

const layerId = uuidv4();
// Exported for testing purposes
export const stackByFieldAccessorId = '34919782-4546-43a5-b668-06ac934d3acd';

export const getEventsHistogramLensAttributes: GetLensAttributes = (
stackByField = 'event.action',
stackByField,
extraOptions = {}
) => {
return {
Expand All @@ -37,7 +39,7 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = (
showGridlines: false,
layerType: 'data',
xAccessor: 'aac9d7d0-13a3-480a-892b-08207a787926',
splitAccessor: '34919782-4546-43a5-b668-06ac934d3acd',
splitAccessor: stackByField ? stackByFieldAccessorId : undefined,
},
],
yRightExtent: {
Expand Down Expand Up @@ -83,30 +85,32 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = (
sourceField: '___records___',
params: { emptyAsNull: true },
},
'34919782-4546-43a5-b668-06ac934d3acd': {
label: `Top values of ${stackByField}`,
dataType: 'string',
operationType: 'terms',
scale: 'ordinal',
sourceField: `${stackByField}`,
isBucketed: true,
params: {
size: 10,
orderBy: {
type: 'column',
columnId: 'e09e0380-0740-4105-becc-0a4ca12e3944',
},
orderDirection: 'desc',
otherBucket: true,
missingBucket: false,
parentFormat: {
id: 'terms',
...(stackByField && {
[stackByFieldAccessorId]: {
label: `Top values of ${stackByField}`,
dataType: 'string',
operationType: 'terms',
scale: 'ordinal',
sourceField: `${stackByField}`,
isBucketed: true,
params: {
size: 10,
orderBy: {
type: 'column',
columnId: 'e09e0380-0740-4105-becc-0a4ca12e3944',
},
orderDirection: 'desc',
otherBucket: true,
missingBucket: false,
parentFormat: {
id: 'terms',
},
},
},
},
}),
},
columnOrder: [
'34919782-4546-43a5-b668-06ac934d3acd',
...(stackByField ? [stackByFieldAccessorId] : []),
'aac9d7d0-13a3-480a-892b-08207a787926',
'e09e0380-0740-4105-becc-0a4ca12e3944',
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { useSourcererDataView } from '../../../sourcerer/containers';
import { kpiHostMetricLensAttributes } from './lens_attributes/hosts/kpi_host_metric';
import { useRouteSpy } from '../../utils/route/use_route_spy';
import { SecurityPageName } from '../../../app/types';
import { getEventsHistogramLensAttributes } from './lens_attributes/common/events';

jest.mock('../../../sourcerer/containers');
jest.mock('../../utils/route/use_route_spy', () => ({
Expand Down Expand Up @@ -212,6 +213,25 @@ describe('useLensAttributes', () => {
]);
});

it('should not set splitAccessor if stackByField is undefined', () => {
const { result } = renderHook(
() =>
useLensAttributes({
getLensAttributes: getEventsHistogramLensAttributes,
stackByField: undefined,
}),
{ wrapper }
);

expect(result?.current?.state?.visualization).toEqual(
expect.objectContaining({
layers: expect.arrayContaining([
expect.objectContaining({ seriesType: 'bar_stacked', splitAccessor: undefined }),
]),
})
);
});

it('should return null if no indices exist', () => {
(useSourcererDataView as jest.Mock).mockReturnValue({
dataViewId: 'security-solution-default',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const useLensAttributes = ({
() =>
lensAttributes ??
((getLensAttributes &&
stackByField &&
stackByField !== null &&
getLensAttributes(stackByField, extraOptions)) as LensAttributes),
[extraOptions, getLensAttributes, lensAttributes, stackByField]
);
Expand All @@ -82,7 +82,7 @@ export const useLensAttributes = ({
const lensAttrsWithInjectedData = useMemo(() => {
if (
lensAttributes == null &&
(getLensAttributes == null || stackByField == null || stackByField?.length === 0)
(getLensAttributes == null || stackByField === null || stackByField?.length === 0)
) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { useKibana, useUiSetting$ } from '../../../common/lib/kibana';
import {
eventsStackByOptions,
eventsHistogramConfig,
NO_BREAKDOWN_STACK_BY_VALUE,
} from '../../../common/components/events_tab/histogram_configurations';
import { HostsTableType } from '../../../explore/hosts/store/model';
import type { GlobalTimeArgs } from '../../../common/containers/use_global_time';
Expand All @@ -36,7 +37,7 @@ import { useFormatUrl } from '../../../common/components/link_to';
import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query';
import type { SourcererScopeName } from '../../../sourcerer/store/model';

const DEFAULT_STACK_BY = 'event.dataset';
const DEFAULT_STACK_BY = NO_BREAKDOWN_STACK_BY_VALUE;

const ID = 'eventsByDatasetOverview';
const CHART_HEIGHT = 160;
Expand Down Expand Up @@ -156,7 +157,7 @@ const EventsByDatasetComponent: React.FC<Props> = ({
defaultStackByOption:
onlyField != null
? getHistogramOption(onlyField)
: eventsStackByOptions.find((o) => o.text === DEFAULT_STACK_BY) ??
: eventsStackByOptions.find((o) => o.value === DEFAULT_STACK_BY) ??
eventsStackByOptions[0],
legendPosition: Position.Right,
subtitle: (totalCount: number) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,11 @@ const TestComponent = (props: Partial<ComponentProps<typeof QueryTabContent>>) =

const dispatch = useDispatch();

// populating timeline so that it is not blank
useEffect(() => {
// Unified field list can be a culprit for long load times, so we wait for the timeline to be interacted with to load
dispatch(timelineActions.showTimeline({ id: TimelineId.test, show: true }));

// populating timeline so that it is not blank
dispatch(
timelineActions.applyKqlFilterQuery({
id: TimelineId.test,
Expand Down
Loading

0 comments on commit ca251ff

Please sign in to comment.