Skip to content

Commit

Permalink
[Alerting] More telemetry for 8.0 based on Event Log data (#115318)
Browse files Browse the repository at this point in the history
* [Alerting] More telemetry for 8.0 based on Event Log data

* fixed event log index mapping

* fixed typecheck

* fixed tests

* added avg aggs

* set size to 0

* fixed due to comments

* fixed telemetry schema

* fixed query

* removed test data

* added tests

* fixed test

* fixed query

* added exection detalization by day

* fixed test

* fixed for rules

* fixed schema

* fixed schema

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
YulNaumenko and kibanamachine authored Nov 2, 2021
1 parent 4e294e3 commit 6e14338
Show file tree
Hide file tree
Showing 16 changed files with 1,434 additions and 23 deletions.
3 changes: 2 additions & 1 deletion x-pack/plugins/actions/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,8 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
plugins.taskManager,
core,
this.kibanaIndex,
this.preconfiguredActions
this.preconfiguredActions,
this.eventLogService
);
}

Expand Down
100 changes: 99 additions & 1 deletion x-pack/plugins/actions/server/usage/actions_telemetry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { elasticsearchClientMock } from '../../../../../src/core/server/elasticsearch/client/mocks';
import { getInUseTotalCount, getTotalCount } from './actions_telemetry';
import { getExecutionsPerDayCount, getInUseTotalCount, getTotalCount } from './actions_telemetry';

describe('actions telemetry', () => {
test('getTotalCount should replace first symbol . to __ for action types names', async () => {
Expand Down Expand Up @@ -604,4 +604,102 @@ Object {
}
`);
});

test('getExecutionsTotalCount', async () => {
const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser;
mockEsClient.search.mockReturnValueOnce(
// @ts-expect-error not full search response
elasticsearchClientMock.createSuccessTransportRequestPromise({
aggregations: {
totalExecutions: {
byConnectorTypeId: {
value: {
connectorTypes: {
'.slack': 100,
'.server-log': 20,
},
total: 120,
},
},
},
failedExecutions: {
refs: {
byConnectorTypeId: {
value: {
connectorTypes: {
'.slack': 7,
},
total: 7,
},
},
},
},
avgDuration: { value: 10 },
avgDurationByType: {
doc_count: 216,
actionSavedObjects: {
doc_count: 108,
byTypeId: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [
{
key: '.server-log',
doc_count: 99,
refs: {
doc_count: 99,
avgDuration: {
value: 919191.9191919192,
},
},
},
{
key: '.email',
doc_count: 9,
refs: {
doc_count: 9,
avgDuration: {
value: 4.196666666666667e8,
},
},
},
],
},
},
},
},
})
);

// for .slack connectors
mockEsClient.search.mockReturnValueOnce(
// @ts-expect-error not full search response
elasticsearchClientMock.createSuccessTransportRequestPromise({
aggregations: {
avgDuration: { value: 10 },
},
})
);
const telemetry = await getExecutionsPerDayCount(mockEsClient, 'test');

expect(mockEsClient.search).toHaveBeenCalledTimes(1);
expect(telemetry).toStrictEqual({
avgExecutionTime: 0,
avgExecutionTimeByType: {
'__server-log': 919191.9191919192,
__email: 419666666.6666667,
},

countByType: {
__slack: 100,

'__server-log': 20,
},
countFailed: 7,
countFailedByType: {
__slack: 7,
},
countTotal: 120,
});
});
});
182 changes: 181 additions & 1 deletion x-pack/plugins/actions/server/usage/actions_telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,4 +379,184 @@ function replaceFirstAndLastDotSymbols(strToReplace: string) {
return hasLastSymbolDot ? `${appliedString.slice(0, -1)}__` : appliedString;
}

// TODO: Implement executions count telemetry with eventLog, when it will write to index
export async function getExecutionsPerDayCount(
esClient: ElasticsearchClient,
eventLogIndex: string
): Promise<{
countTotal: number;
countByType: Record<string, number>;
countFailed: number;
countFailedByType: Record<string, number>;
avgExecutionTime: number;
avgExecutionTimeByType: Record<string, number>;
}> {
const scriptedMetric = {
scripted_metric: {
init_script: 'state.connectorTypes = [:]; state.total = 0;',
map_script: `
if (doc['kibana.saved_objects.type'].value == 'action') {
String connectorType = doc['kibana.saved_objects.type_id'].value;
state.connectorTypes.put(connectorType, state.connectorTypes.containsKey(connectorType) ? state.connectorTypes.get(connectorType) + 1 : 1);
state.total++;
}
`,
// Combine script is executed per cluster, but we already have a key-value pair per cluster.
// Despite docs that say this is optional, this script can't be blank.
combine_script: 'return state',
// Reduce script is executed across all clusters, so we need to add up all the total from each cluster
// This also needs to account for having no data
reduce_script: `
Map connectorTypes = [:];
long total = 0;
for (state in states) {
if (state !== null) {
total += state.total;
for (String k : state.connectorTypes.keySet()) {
connectorTypes.put(k, connectorTypes.containsKey(k) ? connectorTypes.get(k) + state.connectorTypes.get(k) : state.connectorTypes.get(k));
}
}
}
Map result = new HashMap();
result.total = total;
result.connectorTypes = connectorTypes;
return result;
`,
},
};

const { body: actionResults } = await esClient.search({
index: eventLogIndex,
size: 0,
body: {
query: {
bool: {
filter: {
bool: {
must: [
{
term: { 'event.action': 'execute' },
},
{
term: { 'event.provider': 'actions' },
},
{
range: {
'@timestamp': {
gte: 'now-1d',
},
},
},
],
},
},
},
},
aggs: {
totalExecutions: {
nested: {
path: 'kibana.saved_objects',
},
aggs: {
byConnectorTypeId: scriptedMetric,
},
},
failedExecutions: {
filter: {
bool: {
filter: [
{
term: {
'event.outcome': 'failure',
},
},
],
},
},
aggs: {
refs: {
nested: {
path: 'kibana.saved_objects',
},
aggs: {
byConnectorTypeId: scriptedMetric,
},
},
},
},
avgDuration: { avg: { field: 'event.duration' } },
avgDurationByType: {
nested: {
path: 'kibana.saved_objects',
},
aggs: {
actionSavedObjects: {
filter: { term: { 'kibana.saved_objects.type': 'action' } },
aggs: {
byTypeId: {
terms: {
field: 'kibana.saved_objects.type_id',
},
aggs: {
refs: {
reverse_nested: {},
aggs: {
avgDuration: { avg: { field: 'event.duration' } },
},
},
},
},
},
},
},
},
},
},
});

// @ts-expect-error aggegation type is not specified
const aggsExecutions = actionResults.aggregations.totalExecutions?.byConnectorTypeId.value;
// convert nanoseconds to milliseconds
const aggsAvgExecutionTime = Math.round(
// @ts-expect-error aggegation type is not specified
actionResults.aggregations.avgDuration.value / (1000 * 1000)
);
const aggsFailedExecutions =
// @ts-expect-error aggegation type is not specified
actionResults.aggregations.failedExecutions?.refs?.byConnectorTypeId.value;

const avgDurationByType =
// @ts-expect-error aggegation type is not specified
actionResults.aggregations.avgDurationByType?.actionSavedObjects?.byTypeId?.buckets;

const avgExecutionTimeByType: Record<string, number> = avgDurationByType.reduce(
// @ts-expect-error aggegation type is not specified
(res: Record<string, number>, bucket) => {
res[replaceFirstAndLastDotSymbols(bucket.key)] = bucket?.refs.avgDuration.value;
return res;
},
{}
);

return {
countTotal: aggsExecutions.total,
countByType: Object.entries(aggsExecutions.connectorTypes).reduce(
(res: Record<string, number>, [key, value]) => {
// @ts-expect-error aggegation type is not specified
res[replaceFirstAndLastDotSymbols(key)] = value;
return res;
},
{}
),
countFailed: aggsFailedExecutions.total,
countFailedByType: Object.entries(aggsFailedExecutions.connectorTypes).reduce(
(res: Record<string, number>, [key, value]) => {
// @ts-expect-error aggegation type is not specified
res[replaceFirstAndLastDotSymbols(key)] = value;
return res;
},
{}
),
avgExecutionTime: aggsAvgExecutionTime,
avgExecutionTimeByType,
};
}
12 changes: 12 additions & 0 deletions x-pack/plugins/actions/server/usage/actions_usage_collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,14 @@ export function createActionsUsageCollector(
},
},
count_active_by_type: byTypeSchema,
count_actions_executions_per_day: { type: 'long' },
count_actions_executions_by_type_per_day: byTypeSchema,
count_active_email_connectors_by_service_type: byServiceProviderTypeSchema,
count_actions_namespaces: { type: 'long' },
count_actions_executions_failed_per_day: { type: 'long' },
count_actions_executions_failed_by_type_per_day: byTypeSchema,
avg_execution_time_per_day: { type: 'long' },
avg_execution_time_by_type_per_day: byTypeSchema,
},
fetch: async () => {
try {
Expand All @@ -60,6 +66,12 @@ export function createActionsUsageCollector(
count_active_by_type: {},
count_active_email_connectors_by_service_type: {},
count_actions_namespaces: 0,
count_actions_executions_per_day: 0,
count_actions_executions_by_type_per_day: {},
count_actions_executions_failed_per_day: 0,
count_actions_executions_failed_by_type_per_day: {},
avg_execution_time_per_day: 0,
avg_execution_time_by_type_per_day: {},
};
}
},
Expand Down
Loading

0 comments on commit 6e14338

Please sign in to comment.