Skip to content

Commit

Permalink
[Alerting] Show execution duration on Rule Details view (#114719) (#1…
Browse files Browse the repository at this point in the history
…15092)

* Adding execution duration to get alert instance summary

* Showing execution duration on rule details view

* Fixing unit tests

* Updating to match new mockup

* Fixing types

* Fixing functional test

* Removing unneeded max and min and adding tests

* Removing unneeded max and min and adding tests

* Fixing functional test

* Adding left axis

* PR feedback

* Reducing chart height

* PR feedback

* PR feedback

* PR feedback

Co-authored-by: ymao1 <ying.mao@elastic.co>
  • Loading branch information
kibanamachine and ymao1 authored Oct 14, 2021
1 parent 7464156 commit b7f384f
Show file tree
Hide file tree
Showing 20 changed files with 685 additions and 34 deletions.
4 changes: 4 additions & 0 deletions x-pack/plugins/alerting/common/alert_instance_summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export interface AlertInstanceSummary {
lastRun?: string;
errorMessages: Array<{ date: string; message: string }>;
instances: Record<string, AlertInstanceStatus>;
executionDuration: {
average: number;
values: number[];
};
}

export interface AlertInstanceStatus {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import { random, mean } from 'lodash';
import { SanitizedAlert, AlertInstanceSummary } from '../types';
import { IValidatedEvent } from '../../../event_log/server';
import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER, LEGACY_EVENT_LOG_ACTIONS } from '../plugin';
Expand All @@ -31,6 +32,10 @@ describe('alertInstanceSummaryFromEventLog', () => {
"consumer": "alert-consumer",
"enabled": false,
"errorMessages": Array [],
"executionDuration": Object {
"average": 0,
"values": Array [],
},
"id": "alert-123",
"instances": Object {},
"lastRun": undefined,
Expand Down Expand Up @@ -71,6 +76,10 @@ describe('alertInstanceSummaryFromEventLog', () => {
"consumer": "alert-consumer-2",
"enabled": true,
"errorMessages": Array [],
"executionDuration": Object {
"average": 0,
"values": Array [],
},
"id": "alert-456",
"instances": Object {},
"lastRun": undefined,
Expand Down Expand Up @@ -137,14 +146,16 @@ describe('alertInstanceSummaryFromEventLog', () => {
dateEnd,
});

const { lastRun, status, instances } = summary;
const { lastRun, status, instances, executionDuration } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {},
"lastRun": "2020-06-18T00:00:10.000Z",
"status": "OK",
}
`);

testExecutionDurations(eventsFactory.getExecutionDurations(), executionDuration);
});

test('active alert with no instances but has errors', async () => {
Expand All @@ -163,7 +174,7 @@ describe('alertInstanceSummaryFromEventLog', () => {
dateEnd,
});

const { lastRun, status, errorMessages, instances } = summary;
const { lastRun, status, errorMessages, instances, executionDuration } = summary;
expect({ lastRun, status, errorMessages, instances }).toMatchInlineSnapshot(`
Object {
"errorMessages": Array [
Expand All @@ -181,6 +192,8 @@ describe('alertInstanceSummaryFromEventLog', () => {
"status": "Error",
}
`);

testExecutionDurations(eventsFactory.getExecutionDurations(), executionDuration);
});

test('alert with currently inactive instance', async () => {
Expand All @@ -202,7 +215,7 @@ describe('alertInstanceSummaryFromEventLog', () => {
dateEnd,
});

const { lastRun, status, instances } = summary;
const { lastRun, status, instances, executionDuration } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
Expand All @@ -218,6 +231,8 @@ describe('alertInstanceSummaryFromEventLog', () => {
"status": "OK",
}
`);

testExecutionDurations(eventsFactory.getExecutionDurations(), executionDuration);
});

test('legacy alert with currently inactive instance', async () => {
Expand All @@ -239,7 +254,7 @@ describe('alertInstanceSummaryFromEventLog', () => {
dateEnd,
});

const { lastRun, status, instances } = summary;
const { lastRun, status, instances, executionDuration } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
Expand All @@ -255,6 +270,8 @@ describe('alertInstanceSummaryFromEventLog', () => {
"status": "OK",
}
`);

testExecutionDurations(eventsFactory.getExecutionDurations(), executionDuration);
});

test('alert with currently inactive instance, no new-instance', async () => {
Expand All @@ -275,7 +292,7 @@ describe('alertInstanceSummaryFromEventLog', () => {
dateEnd,
});

const { lastRun, status, instances } = summary;
const { lastRun, status, instances, executionDuration } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
Expand All @@ -291,6 +308,8 @@ describe('alertInstanceSummaryFromEventLog', () => {
"status": "OK",
}
`);

testExecutionDurations(eventsFactory.getExecutionDurations(), executionDuration);
});

test('alert with currently active instance', async () => {
Expand All @@ -312,7 +331,7 @@ describe('alertInstanceSummaryFromEventLog', () => {
dateEnd,
});

const { lastRun, status, instances } = summary;
const { lastRun, status, instances, executionDuration } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
Expand All @@ -328,6 +347,8 @@ describe('alertInstanceSummaryFromEventLog', () => {
"status": "Active",
}
`);

testExecutionDurations(eventsFactory.getExecutionDurations(), executionDuration);
});

test('alert with currently active instance with no action group in event log', async () => {
Expand All @@ -349,7 +370,7 @@ describe('alertInstanceSummaryFromEventLog', () => {
dateEnd,
});

const { lastRun, status, instances } = summary;
const { lastRun, status, instances, executionDuration } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
Expand All @@ -365,6 +386,8 @@ describe('alertInstanceSummaryFromEventLog', () => {
"status": "Active",
}
`);

testExecutionDurations(eventsFactory.getExecutionDurations(), executionDuration);
});

test('alert with currently active instance that switched action groups', async () => {
Expand All @@ -386,7 +409,7 @@ describe('alertInstanceSummaryFromEventLog', () => {
dateEnd,
});

const { lastRun, status, instances } = summary;
const { lastRun, status, instances, executionDuration } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
Expand All @@ -402,6 +425,8 @@ describe('alertInstanceSummaryFromEventLog', () => {
"status": "Active",
}
`);

testExecutionDurations(eventsFactory.getExecutionDurations(), executionDuration);
});

test('alert with currently active instance, no new-instance', async () => {
Expand All @@ -422,7 +447,7 @@ describe('alertInstanceSummaryFromEventLog', () => {
dateEnd,
});

const { lastRun, status, instances } = summary;
const { lastRun, status, instances, executionDuration } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
Expand All @@ -438,6 +463,8 @@ describe('alertInstanceSummaryFromEventLog', () => {
"status": "Active",
}
`);

testExecutionDurations(eventsFactory.getExecutionDurations(), executionDuration);
});

test('alert with active and inactive muted alerts', async () => {
Expand All @@ -462,7 +489,7 @@ describe('alertInstanceSummaryFromEventLog', () => {
dateEnd,
});

const { lastRun, status, instances } = summary;
const { lastRun, status, instances, executionDuration } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
Expand All @@ -485,6 +512,8 @@ describe('alertInstanceSummaryFromEventLog', () => {
"status": "Active",
}
`);

testExecutionDurations(eventsFactory.getExecutionDurations(), executionDuration);
});

test('alert with active and inactive alerts over many executes', async () => {
Expand Down Expand Up @@ -515,7 +544,7 @@ describe('alertInstanceSummaryFromEventLog', () => {
dateEnd,
});

const { lastRun, status, instances } = summary;
const { lastRun, status, instances, executionDuration } = summary;
expect({ lastRun, status, instances }).toMatchInlineSnapshot(`
Object {
"instances": Object {
Expand All @@ -538,7 +567,19 @@ describe('alertInstanceSummaryFromEventLog', () => {
"status": "Active",
}
`);

testExecutionDurations(eventsFactory.getExecutionDurations(), executionDuration);
});

const testExecutionDurations = (
actualDurations: number[],
executionDuration?: { average?: number; values?: number[] }
) => {
expect(executionDuration).toEqual({
average: Math.round(mean(actualDurations)),
values: actualDurations,
});
};
});

function dateString(isoBaseDate: string, offsetMillis = 0): string {
Expand Down Expand Up @@ -573,6 +614,7 @@ export class EventsFactory {
event: {
provider: EVENT_LOG_PROVIDER,
action: EVENT_LOG_ACTIONS.execute,
duration: random(2000, 180000) * 1000 * 1000,
},
};

Expand Down Expand Up @@ -634,6 +676,12 @@ export class EventsFactory {
});
return this;
}

getExecutionDurations(): number[] {
return this.events
.filter((ev) => ev?.event?.action === 'execute' && ev?.event?.duration !== undefined)
.map((ev) => ev?.event?.duration! / (1000 * 1000));
}
}

function createAlert(overrides: Partial<SanitizedAlert>): SanitizedAlert<{ bar: boolean }> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
* 2.0.
*/

import { mean } from 'lodash';
import { SanitizedAlert, AlertInstanceSummary, AlertInstanceStatus } from '../types';
import { IEvent } from '../../../event_log/server';
import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER, LEGACY_EVENT_LOG_ACTIONS } from '../plugin';

const Millis2Nanos = 1000 * 1000;

export interface AlertInstanceSummaryFromEventLogParams {
alert: SanitizedAlert<{ bar: boolean }>;
events: IEvent[];
Expand Down Expand Up @@ -36,9 +39,14 @@ export function alertInstanceSummaryFromEventLog(
lastRun: undefined,
errorMessages: [],
instances: {},
executionDuration: {
average: 0,
values: [],
},
};

const instances = new Map<string, AlertInstanceStatus>();
const eventDurations: number[] = [];

// loop through the events
// should be sorted newest to oldest, we want oldest to newest, so reverse
Expand Down Expand Up @@ -66,6 +74,10 @@ export function alertInstanceSummaryFromEventLog(
alertInstanceSummary.status = 'OK';
}

if (event?.event?.duration) {
eventDurations.push(event?.event?.duration / Millis2Nanos);
}

continue;
}

Expand Down Expand Up @@ -111,6 +123,13 @@ export function alertInstanceSummaryFromEventLog(

alertInstanceSummary.errorMessages.sort((a, b) => a.date.localeCompare(b.date));

if (eventDurations.length > 0) {
alertInstanceSummary.executionDuration = {
average: Math.round(mean(eventDurations)),
values: eventDurations,
};
}

return alertInstanceSummary;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ describe('getRuleAlertSummaryRoute', () => {
status: 'OK',
errorMessages: [],
instances: {},
executionDuration: {
average: 1,
values: [3, 5, 5],
},
};

it('gets rule alert summary', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const rewriteBodyRes: RewriteResponseCase<AlertInstanceSummary> = ({
errorMessages,
lastRun,
instances: alerts,
executionDuration,
...rest
}) => ({
...rest,
Expand All @@ -49,6 +50,7 @@ const rewriteBodyRes: RewriteResponseCase<AlertInstanceSummary> = ({
status_end_date: statusEndDate,
error_messages: errorMessages,
last_run: lastRun,
execution_duration: executionDuration,
});

export const getRuleAlertSummaryRoute = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ describe('getAlertInstanceSummaryRoute', () => {
status: 'OK',
errorMessages: [],
instances: {},
executionDuration: {
average: 0,
values: [],
},
};

it('gets alert instance summary', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import { omit, mean } from 'lodash';
import { RulesClient, ConstructorOptions } from '../rules_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
Expand Down Expand Up @@ -138,8 +139,11 @@ describe('getAlertInstanceSummary()', () => {

const dateStart = new Date(Date.now() - 60 * 1000).toISOString();

const durations: number[] = eventsFactory.getExecutionDurations();

const result = await rulesClient.getAlertInstanceSummary({ id: '1', dateStart });
expect(result).toMatchInlineSnapshot(`
const resultWithoutExecutionDuration = omit(result, 'executionDuration');
expect(resultWithoutExecutionDuration).toMatchInlineSnapshot(`
Object {
"alertTypeId": "123",
"consumer": "alert-consumer",
Expand Down Expand Up @@ -182,6 +186,11 @@ describe('getAlertInstanceSummary()', () => {
"throttle": null,
}
`);

expect(result.executionDuration).toEqual({
average: Math.round(mean(durations)),
values: durations,
});
});

// Further tests don't check the result of `getAlertInstanceSummary()`, as the result
Expand Down
Loading

0 comments on commit b7f384f

Please sign in to comment.