Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Discover] Cancel long running requests in Discover alert #130077

Merged
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5a4335f
[Discover] improve long running requests for search source within ale…
dimaanj Apr 13, 2022
311a66f
Merge branch 'main' of https://github.com/elastic/kibana into improve…
dimaanj Apr 20, 2022
30492e7
[Discover] add tests
dimaanj Apr 21, 2022
4ff15e2
Merge branch 'main' into improve-long-running-requests-for-alert-rule
kibanamachine Apr 21, 2022
9e717c2
[Discover] fix linting
dimaanj Apr 22, 2022
98f90fd
[Discover] fix unit test
dimaanj Apr 22, 2022
239b90e
[Discover] add getMetrics test
dimaanj Apr 22, 2022
12d6312
[Discover] fix unit test
dimaanj Apr 25, 2022
a3e0ff7
Merge branch 'main' of https://github.com/elastic/kibana into improve…
dimaanj Apr 25, 2022
1fc32ca
Merge branch 'main' into improve-long-running-requests-for-alert-rule
kibanamachine Apr 25, 2022
7c86fe8
Merge branch 'main' into improve-long-running-requests-for-alert-rule
kibanamachine Apr 25, 2022
4f1a917
Merge branch 'main' of https://github.com/elastic/kibana into improve…
dimaanj Apr 26, 2022
7d03945
[Discover] merge search clients metrics
dimaanj Apr 26, 2022
fae9f28
Merge branch 'main' of https://github.com/elastic/kibana into improve…
dimaanj Apr 28, 2022
bcb52ae
[Discover] wrap searchSourceClient
dimaanj Apr 30, 2022
157362d
Merge branch 'main' into improve-long-running-requests-for-alert-rule
kibanamachine Apr 30, 2022
5b03b8a
Merge branch 'main' of https://github.com/elastic/kibana into improve…
dimaanj May 11, 2022
80cf064
[Discover] add unit tests
dimaanj May 14, 2022
63df64c
Merge branch 'main' into improve-long-running-requests-for-alert-rule
kibanamachine May 14, 2022
7ab49d7
[Discover] replace searchSourceUtils with searchSourceClient in tests
dimaanj May 14, 2022
b5e1cc9
Merge branch 'main' into improve-long-running-requests-for-alert-rule
kibanamachine May 15, 2022
efb5d59
[Discover] apply suggestions
dimaanj May 17, 2022
59a5ebf
Merge branch 'main' into improve-long-running-requests-for-alert-rule
kibanamachine May 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions x-pack/plugins/alerting/common/rule_task_instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const ruleStateSchema = t.partial({

const ruleExecutionMetricsSchema = t.partial({
numSearches: t.number,
numSearchSourceSearches: t.number,
dimaanj marked this conversation as resolved.
Show resolved Hide resolved
totalSearchDurationMs: t.number,
esSearchDurationMs: t.number,
});
Expand Down
24 changes: 24 additions & 0 deletions x-pack/plugins/alerting/server/lib/merge_search_metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 { RuleExecutionMetrics } from '../types';
import type { ScopedClusterClientMetrics } from './wrap_scoped_cluster_client';
import type { SearchSourceClientMetrics } from './wrap_search_source_fetch';

export const mergeSearchMetrics = (
scopedClusterClientMetrics: ScopedClusterClientMetrics,
searchSourceMetrics: SearchSourceClientMetrics
): RuleExecutionMetrics => {
return {
numSearches: scopedClusterClientMetrics.numSearches,
numSearchSourceSearches: searchSourceMetrics.numSearchSourceSearches,
totalSearchDurationMs:
scopedClusterClientMetrics.totalSearchDurationMs + searchSourceMetrics.totalSearchDurationMs,
esSearchDurationMs:
scopedClusterClientMetrics.esSearchDurationMs + searchSourceMetrics.esSearchDurationMs,
};
};
9 changes: 9 additions & 0 deletions x-pack/plugins/alerting/server/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import * as t from 'io-ts';
import { either } from 'fp-ts/lib/Either';
import { Rule } from '../types';

// represents a Date from an ISO string
export const DateFromString = new t.Type<Date, string, unknown>(
'DateFromString',
Expand All @@ -24,3 +26,10 @@ export const DateFromString = new t.Type<Date, string, unknown>(
),
(valueToEncode) => valueToEncode.toISOString()
);

export type RuleInfo = Pick<Rule, 'name' | 'alertTypeId' | 'id'> & { spaceId: string };

export interface LogSearchMetricsOpts {
esSearchDuration: number;
totalSearchDuration: number;
}
24 changes: 12 additions & 12 deletions x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ import type {
AggregationsAggregate,
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { IScopedClusterClient, ElasticsearchClient, Logger } from '@kbn/core/server';
import { RuleExecutionMetrics } from '../types';
import { Rule } from '../types';
import { LogSearchMetricsOpts, RuleInfo } from './types';

export type LogSearchMetricsFn = (metrics: LogSearchMetricsOpts) => void;

type WrapEsClientOpts = Omit<WrapScopedClusterClientOpts, 'scopedClusterClient'> & {
esClient: ElasticsearchClient;
};

type RuleInfo = Pick<Rule, 'name' | 'alertTypeId' | 'id'> & { spaceId: string };
interface WrapScopedClusterClientFactoryOpts {
scopedClusterClient: IScopedClusterClient;
rule: RuleInfo;
Expand All @@ -36,15 +40,11 @@ type WrapScopedClusterClientOpts = WrapScopedClusterClientFactoryOpts & {
logMetricsFn: LogSearchMetricsFn;
};

type WrapEsClientOpts = Omit<WrapScopedClusterClientOpts, 'scopedClusterClient'> & {
esClient: ElasticsearchClient;
};

interface LogSearchMetricsOpts {
esSearchDuration: number;
totalSearchDuration: number;
export interface ScopedClusterClientMetrics {
esSearchDurationMs: number;
totalSearchDurationMs: number;
numSearches: number;
}
type LogSearchMetricsFn = (metrics: LogSearchMetricsOpts) => void;

export function createWrappedScopedClusterClientFactory(opts: WrapScopedClusterClientFactoryOpts) {
let numSearches: number = 0;
Expand All @@ -61,7 +61,7 @@ export function createWrappedScopedClusterClientFactory(opts: WrapScopedClusterC

return {
client: () => wrappedClient,
getMetrics: (): RuleExecutionMetrics => {
getMetrics: (): ScopedClusterClientMetrics => {
return {
esSearchDurationMs,
totalSearchDurationMs,
Expand Down
108 changes: 108 additions & 0 deletions x-pack/plugins/alerting/server/lib/wrap_search_source_fetch.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* 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.
*/

/*
* 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 { loggingSystemMock } from '@kbn/core/server/mocks';
import { searchSourceInstanceMock } from '@kbn/data-plugin/common/search/search_source/mocks';
import { wrapSearchSourceFetch } from './wrap_search_source_fetch';

const logger = loggingSystemMock.create().get();

const rule = {
name: 'test-rule',
alertTypeId: '.test-rule-type',
id: 'abcdefg',
spaceId: 'my-space',
};

describe('wrapSearchSourceFetch', () => {
beforeAll(() => {
jest.useFakeTimers();
});

afterAll(() => {
jest.useRealTimers();
});

afterEach(() => {
jest.resetAllMocks();
});

test('searches properly', async () => {
const abortController = new AbortController();

const { fetch: wrappedFetch } = wrapSearchSourceFetch({ logger, rule, abortController });
wrappedFetch(searchSourceInstanceMock);

expect(searchSourceInstanceMock.fetch).toHaveBeenCalledWith({
abortSignal: abortController.signal,
});
});

test('re-throws error when search throws error', async () => {
const abortController = new AbortController();

(searchSourceInstanceMock.fetch as jest.Mock).mockRejectedValueOnce(
new Error('something went wrong!')
);
const { fetch: wrappedFetch } = wrapSearchSourceFetch({ logger, rule, abortController });

await expect(
wrappedFetch.bind({}, searchSourceInstanceMock)
).rejects.toThrowErrorMatchingInlineSnapshot(`"something went wrong!"`);
});

test('throws error when search throws abort error', async () => {
const abortController = new AbortController();
abortController.abort();

(searchSourceInstanceMock.fetch as jest.Mock).mockRejectedValueOnce(
new Error('Request has been aborted by the user')
);

const { fetch: wrappedFetch } = wrapSearchSourceFetch({ logger, rule, abortController });

await expect(
wrappedFetch.bind({}, searchSourceInstanceMock)
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Search has been aborted due to cancelled execution"`
);
});

test('keeps track of number of queries', async () => {
const abortController = new AbortController();
abortController.abort();

(searchSourceInstanceMock.fetch as jest.Mock).mockResolvedValue({ took: 333 });

const { fetch: wrappedFetch, getMetrics } = wrapSearchSourceFetch({
logger,
rule,
abortController,
});

await wrappedFetch(searchSourceInstanceMock);
await wrappedFetch(searchSourceInstanceMock);
await wrappedFetch(searchSourceInstanceMock);

expect(searchSourceInstanceMock.fetch).toHaveBeenCalledTimes(3);

const stats = getMetrics();
expect(stats.numSearchSourceSearches).toEqual(3);
expect(stats.esSearchDurationMs).toEqual(999);

expect(logger.debug).toHaveBeenCalledWith(
'executing query for rule .test-rule-type:abcdefg in space my-space'
);
});
});
62 changes: 62 additions & 0 deletions x-pack/plugins/alerting/server/lib/wrap_search_source_fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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 { Logger } from '@kbn/core/server';
import { ISearchSource } from '@kbn/data-plugin/common';
import { LogSearchMetricsOpts, RuleInfo } from './types';

export interface SearchSourceClientMetrics {
esSearchDurationMs: number;
totalSearchDurationMs: number;
numSearchSourceSearches: number;
}

export function wrapSearchSourceFetch({
logger,
rule,
abortController,
}: {
logger: Logger;
rule: RuleInfo;
abortController: AbortController;
}) {
let numSearchSourceSearches: number = 0;
let esSearchDurationMs: number = 0;
let totalSearchDurationMs: number = 0;

function logMetrics(metrics: LogSearchMetricsOpts) {
numSearchSourceSearches++;
esSearchDurationMs += metrics.esSearchDuration;
totalSearchDurationMs += metrics.totalSearchDuration;
}

return {
fetch: async (searchSource: ISearchSource) => {
try {
const start = Date.now();
logger.debug(
`executing query for rule ${rule.alertTypeId}:${rule.id} in space ${rule.spaceId}`
);
const result = await searchSource.fetch({ abortSignal: abortController.signal });

const durationMs = Date.now() - start;
logMetrics({ esSearchDuration: result.took ?? 0, totalSearchDuration: durationMs });
return result;
} catch (e) {
if (abortController.signal.aborted) {
throw new Error('Search has been aborted due to cancelled execution');
}
throw e;
}
},
getMetrics: (): SearchSourceClientMetrics => ({
esSearchDurationMs,
totalSearchDurationMs,
numSearchSourceSearches,
}),
};
}
15 changes: 8 additions & 7 deletions x-pack/plugins/alerting/server/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import {
elasticsearchServiceMock,
savedObjectsClientMock,
uiSettingsServiceMock,
httpServerMock,
} from '@kbn/core/server/mocks';
import { dataPluginMock } from '@kbn/data-plugin/server/mocks';
import {
searchSourceCommonMock,
searchSourceInstanceMock,
} from '@kbn/data-plugin/common/search/search_source/mocks';
import { rulesClientMock } from './rules_client.mock';
import { PluginSetupContract, PluginStartContract } from './plugin';
import { Alert, AlertFactoryDoneUtils } from './alert';
Expand Down Expand Up @@ -113,11 +115,10 @@ const createRuleExecutorServicesMock = <
shouldWriteAlerts: () => true,
shouldStopExecution: () => true,
search: createAbortableSearchServiceMock(),
searchSourceClient: Promise.resolve(
dataPluginMock
.createStartContract()
.search.searchSource.asScoped(httpServerMock.createKibanaRequest())
),
searchSourceUtils: {
searchSourceClient: searchSourceCommonMock,
wrappedFetch: () => searchSourceInstanceMock.fetch(),
},
};
};
export type RuleExecutorServicesMock = ReturnType<typeof createRuleExecutorServicesMock>;
Expand Down
14 changes: 7 additions & 7 deletions x-pack/plugins/alerting/server/task_runner/task_runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ describe('Task Runner', () => {
expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z');
expect(logger.debug).nthCalledWith(
2,
'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"ok"}'
'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"numSearchSourceSearches":0,"totalSearchDurationMs":23423,"esSearchDurationMs":33},"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"ok"}'
);

const eventLogger = taskRunnerFactoryInitializerParams.eventLogger;
Expand Down Expand Up @@ -320,7 +320,7 @@ describe('Task Runner', () => {
);
expect(logger.debug).nthCalledWith(
3,
'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}'
'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"numSearchSourceSearches":0,"totalSearchDurationMs":23423,"esSearchDurationMs":33},"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}'
);

const eventLogger = customTaskRunnerFactoryInitializerParams.eventLogger;
Expand Down Expand Up @@ -428,7 +428,7 @@ describe('Task Runner', () => {
);
expect(logger.debug).nthCalledWith(
4,
'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}'
'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"numSearchSourceSearches":0,"totalSearchDurationMs":23423,"esSearchDurationMs":33},"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}'
);

const eventLogger = taskRunnerFactoryInitializerParams.eventLogger;
Expand Down Expand Up @@ -593,7 +593,7 @@ describe('Task Runner', () => {
);
expect(logger.debug).nthCalledWith(
4,
'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}'
'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"numSearchSourceSearches":0,"totalSearchDurationMs":23423,"esSearchDurationMs":33},"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}'
);
expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled();
}
Expand Down Expand Up @@ -1121,7 +1121,7 @@ describe('Task Runner', () => {
);
expect(logger.debug).nthCalledWith(
4,
'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}'
'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"numSearchSourceSearches":0,"totalSearchDurationMs":23423,"esSearchDurationMs":33},"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}'
);

const eventLogger = customTaskRunnerFactoryInitializerParams.eventLogger;
Expand Down Expand Up @@ -1262,7 +1262,7 @@ describe('Task Runner', () => {
);
expect(logger.debug).nthCalledWith(
4,
`ruleExecutionStatus for test:${alertId}: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}`
'ruleExecutionStatus for test:e558aaad-fd81-46d2-96fc-3bd8fc3dc03f: {"metrics":{"numSearches":3,"numSearchSourceSearches":0,"totalSearchDurationMs":23423,"esSearchDurationMs":33},"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}'
);

const eventLogger = customTaskRunnerFactoryInitializerParams.eventLogger;
Expand Down Expand Up @@ -2489,7 +2489,7 @@ describe('Task Runner', () => {
expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z');
expect(logger.debug).nthCalledWith(
2,
'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"ok"}'
'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"numSearchSourceSearches":0,"totalSearchDurationMs":23423,"esSearchDurationMs":33},"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"ok"}'
);

const eventLogger = taskRunnerFactoryInitializerParams.eventLogger;
Expand Down
Loading