Rule execution log performance optimizations (#118925)

This commit is contained in:
Dmitry Shevchenko 2021-11-24 18:44:27 +01:00 committed by GitHub
parent 9d662b77be
commit d6ac415fb8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 69 additions and 123 deletions

View file

@ -19,7 +19,6 @@ export const ruleExecutionLogClientMock = {
deleteCurrentStatus: jest.fn(),
logStatusChange: jest.fn(),
logExecutionMetrics: jest.fn(),
}),
};

View file

@ -79,9 +79,8 @@ export class EventLogAdapter implements IRuleExecutionLogClient {
// EventLog execution events are immutable, nothing to do here
}
public async logExecutionMetrics(args: LogExecutionMetricsArgs) {
private async logExecutionMetrics(args: LogExecutionMetricsArgs) {
const { ruleId, spaceId, ruleType, ruleName, metrics } = args;
await this.savedObjectsAdapter.logExecutionMetrics(args);
this.eventLogClient.logExecutionMetrics({
ruleId,

View file

@ -11,7 +11,6 @@ import { IRuleStatusSOAttributes } from '../rules/types';
import { EventLogAdapter } from './event_log_adapter/event_log_adapter';
import { SavedObjectsAdapter } from './saved_objects_adapter/saved_objects_adapter';
import {
LogExecutionMetricsArgs,
FindBulkExecutionLogArgs,
FindExecutionLogArgs,
IRuleExecutionLogClient,
@ -75,10 +74,6 @@ export class RuleExecutionLogClient implements IRuleExecutionLogClient {
return this.client.deleteCurrentStatus(ruleId);
}
public async logExecutionMetrics(args: LogExecutionMetricsArgs) {
return this.client.logExecutionMetrics(args);
}
public async logStatusChange(args: LogStatusChangeArgs) {
const message = args.message ? truncateMessage(args.message) : args.message;
return this.client.logStatusChange({

View file

@ -11,28 +11,26 @@ import { SavedObjectsClientContract } from '../../../../../../../../src/core/ser
import { RuleExecutionStatus } from '../../../../../common/detection_engine/schemas/common/schemas';
// eslint-disable-next-line no-restricted-imports
import { legacyGetRuleReference } from '../../rules/legacy_rule_status/legacy_utils';
import { IRuleStatusSOAttributes } from '../../rules/types';
import {
ExecutionMetrics,
FindBulkExecutionLogArgs,
FindExecutionLogArgs,
GetCurrentStatusArgs,
GetCurrentStatusBulkArgs,
GetCurrentStatusBulkResult,
GetLastFailuresArgs,
IRuleExecutionLogClient,
LogStatusChangeArgs,
} from '../types';
import {
RuleStatusSavedObjectsClient,
ruleStatusSavedObjectsClientFactory,
} from './rule_status_saved_objects_client';
import {
LogExecutionMetricsArgs,
FindBulkExecutionLogArgs,
FindExecutionLogArgs,
IRuleExecutionLogClient,
ExecutionMetrics,
LogStatusChangeArgs,
GetLastFailuresArgs,
GetCurrentStatusArgs,
GetCurrentStatusBulkArgs,
GetCurrentStatusBulkResult,
} from '../types';
import { assertUnreachable } from '../../../../../common';
const MAX_ERRORS = 5;
// 1st is mutable status, followed by 5 most recent failures
export const MAX_RULE_STATUSES = 6;
const MAX_RULE_STATUSES = 1 + MAX_ERRORS;
const convertMetricFields = (
metrics: ExecutionMetrics
@ -100,112 +98,73 @@ export class SavedObjectsAdapter implements IRuleExecutionLogClient {
await Promise.all(statusSavedObjects.map((so) => this.ruleStatusClient.delete(so.id)));
}
public async logExecutionMetrics({ ruleId, metrics }: LogExecutionMetricsArgs) {
const references: SavedObjectReference[] = [legacyGetRuleReference(ruleId)];
const [currentStatus] = await this.getOrCreateRuleStatuses(ruleId);
await this.ruleStatusClient.update(
currentStatus.id,
{
...currentStatus.attributes,
...convertMetricFields(metrics),
},
{ references }
);
}
private createNewRuleStatus = async (
private findRuleStatuses = async (
ruleId: string
): Promise<SavedObject<IRuleStatusSOAttributes>> => {
const references: SavedObjectReference[] = [legacyGetRuleReference(ruleId)];
const now = new Date().toISOString();
return this.ruleStatusClient.create(
{
statusDate: now,
status: RuleExecutionStatus['going to run'],
lastFailureAt: null,
lastSuccessAt: null,
lastFailureMessage: null,
lastSuccessMessage: null,
gap: null,
bulkCreateTimeDurations: [],
searchAfterTimeDurations: [],
lastLookBackDate: null,
},
{ references }
);
};
private getOrCreateRuleStatuses = async (
ruleId: string
): Promise<Array<SavedObject<IRuleStatusSOAttributes>>> => {
const existingStatuses = await this.findRuleStatusSavedObjects(ruleId, MAX_RULE_STATUSES);
if (existingStatuses.length > 0) {
return existingStatuses;
}
const newStatus = await this.createNewRuleStatus(ruleId);
return [newStatus];
};
): Promise<Array<SavedObject<IRuleStatusSOAttributes>>> =>
this.findRuleStatusSavedObjects(ruleId, MAX_RULE_STATUSES);
public async logStatusChange({ newStatus, ruleId, message, metrics }: LogStatusChangeArgs) {
const references: SavedObjectReference[] = [legacyGetRuleReference(ruleId)];
const ruleStatuses = await this.findRuleStatuses(ruleId);
const [currentStatus] = ruleStatuses;
const attributes = buildRuleStatusAttributes({
status: newStatus,
message,
metrics,
currentAttributes: currentStatus?.attributes,
});
// Create or update current status
if (currentStatus) {
await this.ruleStatusClient.update(currentStatus.id, attributes, { references });
} else {
await this.ruleStatusClient.create(attributes, { references });
}
switch (newStatus) {
case RuleExecutionStatus['going to run']:
case RuleExecutionStatus.succeeded:
case RuleExecutionStatus.warning:
case RuleExecutionStatus['partial failure']: {
const [currentStatus] = await this.getOrCreateRuleStatuses(ruleId);
await this.ruleStatusClient.update(
currentStatus.id,
{
...currentStatus.attributes,
...buildRuleStatusAttributes(newStatus, message, metrics),
},
{ references }
);
return;
}
case RuleExecutionStatus.failed: {
const ruleStatuses = await this.getOrCreateRuleStatuses(ruleId);
const [currentStatus] = ruleStatuses;
const failureAttributes = {
...currentStatus.attributes,
...buildRuleStatusAttributes(RuleExecutionStatus.failed, message, metrics),
};
// We always update the newest status, so to 'persist' a failure we push a copy to the head of the list
await this.ruleStatusClient.update(currentStatus.id, failureAttributes, { references });
const lastStatus = await this.ruleStatusClient.create(failureAttributes, { references });
// drop oldest failures
const oldStatuses = [lastStatus, ...ruleStatuses].slice(MAX_RULE_STATUSES);
await Promise.all(oldStatuses.map((status) => this.ruleStatusClient.delete(status.id)));
return;
}
default:
assertUnreachable(newStatus, 'Unknown rule execution status supplied to logStatusChange');
if (newStatus === RuleExecutionStatus.failed) {
await Promise.all([
// Persist the current failure in the last five errors list
this.ruleStatusClient.create(attributes, { references }),
// Drop oldest failures
...ruleStatuses
.slice(MAX_RULE_STATUSES - 1)
.map((status) => this.ruleStatusClient.delete(status.id)),
]);
}
}
}
const buildRuleStatusAttributes: (
status: RuleExecutionStatus,
message?: string,
metrics?: ExecutionMetrics
) => Partial<IRuleStatusSOAttributes> = (status, message, metrics = {}) => {
const defaultStatusAttributes: IRuleStatusSOAttributes = {
statusDate: '',
status: RuleExecutionStatus['going to run'],
lastFailureAt: null,
lastSuccessAt: null,
lastFailureMessage: null,
lastSuccessMessage: null,
gap: null,
bulkCreateTimeDurations: [],
searchAfterTimeDurations: [],
lastLookBackDate: null,
};
const buildRuleStatusAttributes = ({
status,
message,
metrics = {},
currentAttributes,
}: {
status: RuleExecutionStatus;
message?: string;
metrics?: ExecutionMetrics;
currentAttributes?: IRuleStatusSOAttributes;
}): IRuleStatusSOAttributes => {
const now = new Date().toISOString();
const baseAttributes: Partial<IRuleStatusSOAttributes> = {
...convertMetricFields(metrics),
const baseAttributes: IRuleStatusSOAttributes = {
...defaultStatusAttributes,
...currentAttributes,
statusDate: now,
status:
status === RuleExecutionStatus.warning ? RuleExecutionStatus['partial failure'] : status,
statusDate: now,
...convertMetricFields(metrics),
};
switch (status) {

View file

@ -28,7 +28,6 @@ export interface IRuleExecutionLogClient {
deleteCurrentStatus(ruleId: string): Promise<void>;
logStatusChange(args: LogStatusChangeArgs): Promise<void>;
logExecutionMetrics(args: LogExecutionMetricsArgs): Promise<void>;
}
/** @deprecated */

View file

@ -9,7 +9,6 @@ import { SavedObjectsFindResult } from 'kibana/server';
import {
IRuleExecutionLogClient,
LogStatusChangeArgs,
LogExecutionMetricsArgs,
FindBulkExecutionLogArgs,
FindBulkExecutionLogResponse,
FindExecutionLogArgs,
@ -65,10 +64,6 @@ export const createWarningsAndErrors = () => {
warningsAndErrorsStore.push(args);
return Promise.resolve();
},
logExecutionMetrics(args: LogExecutionMetricsArgs): Promise<void> {
return Promise.resolve();
},
};
return { previewRuleExecutionLogClient, warningsAndErrorsStore };