Skip to content

Commit

Permalink
Security Telemetry Usage Telemetry Counters (#108735) (#109142)
Browse files Browse the repository at this point in the history
* added UsageCounter to SecuritySolution app to be passed to telemetry and other plugins as needed

* Add counters for payloads and helpers for naming

* Fixed some typing issues

* Fixed eslint errors

* Still more eslint fixes

* Missed an eslint fix again

* Incorrect import order

* Addressed some review comments

* Added unit test for UsageCounter inside TaskSender

* Fixed some import checks

* incrementCounter unittest needs questionmark to handle undefined case

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Chris Donaher <christopher.donaher@elastic.co>
  • Loading branch information
kibanamachine and donaherc committed Aug 18, 2021
1 parent 47da737 commit 0101c8d
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,12 @@ export function isPackagePolicyList(

return (data as PackagePolicy[])[0].inputs !== undefined;
}

/**
* Convert counter label list to kebab case
* @params label_list the list of labels to create standardized UsageCounter from
* @returns a string label for usage in the UsageCounter
*/
export function createUsageCounterLabel(labelList: string[]): string {
return labelList.join('-');
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@
/* eslint-disable dot-notation */
import { TelemetryEventsSender, copyAllowlistedFields, getV3UrlFromV2 } from './sender';
import { loggingSystemMock } from 'src/core/server/mocks';
import { usageCountersServiceMock } from 'src/plugins/usage_collection/server/usage_counters/usage_counters_service.mock';
import { URL } from 'url';

describe('TelemetryEventsSender', () => {
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
const usageCountersServiceSetup = usageCountersServiceMock.createSetupContract();
const telemetryUsageCounter = usageCountersServiceSetup.createUsageCounter(
'testTelemetryUsageCounter'
);

beforeEach(() => {
logger = loggingSystemMock.createLogger();
Expand Down Expand Up @@ -163,19 +168,26 @@ describe('TelemetryEventsSender', () => {

it('empties the queue when sending', async () => {
const sender = new TelemetryEventsSender(logger);
sender['sendEvents'] = jest.fn();
sender['telemetryStart'] = {
getIsOptedIn: jest.fn(async () => true),
};
sender['telemetrySetup'] = {
getTelemetryUrl: jest.fn(async () => new URL('https://telemetry.elastic.co')),
};
sender['telemetryUsageCounter'] = telemetryUsageCounter;
sender['fetchClusterInfo'] = jest.fn(async () => {
return {
cluster_name: 'test',
cluster_uuid: 'test-uuid',
};
});
sender['sendEvents'] = jest.fn(async () => {
sender['telemetryUsageCounter']?.incrementCounter({
counterName: 'test_counter',
counterType: 'invoked',
incrementBy: 1,
});
});

sender.queueTelemetryEvents([{ 'event.kind': '1' }, { 'event.kind': '2' }]);
expect(sender['queue'].length).toBe(2);
Expand All @@ -188,6 +200,7 @@ describe('TelemetryEventsSender', () => {
await sender['sendIfDue']();
expect(sender['queue'].length).toBe(0);
expect(sender['sendEvents']).toBeCalledTimes(2);
expect(sender['telemetryUsageCounter'].incrementCounter).toBeCalledTimes(2);
});

it("shouldn't send when telemetry is disabled", async () => {
Expand Down
44 changes: 43 additions & 1 deletion x-pack/plugins/security_solution/server/lib/telemetry/sender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import { SearchRequest } from '@elastic/elasticsearch/api/types';
import { URL } from 'url';
import { CoreStart, ElasticsearchClient, Logger } from 'src/core/server';
import { TelemetryPluginStart, TelemetryPluginSetup } from 'src/plugins/telemetry/server';
import { UsageCounter } from 'src/plugins/usage_collection/server';
import { transformDataToNdjson } from '../../utils/read_stream/create_stream_from_ndjson';
import {
TaskManagerSetupContract,
TaskManagerStartContract,
} from '../../../../task_manager/server';
import { createUsageCounterLabel } from './helpers';
import { TelemetryDiagTask } from './diagnostic_task';
import { TelemetryEndpointTask } from './endpoint_task';
import { TelemetryTrustedAppsTask } from './trusted_apps_task';
Expand All @@ -27,6 +29,7 @@ import { getTrustedAppsList } from '../../endpoint/routes/trusted_apps/service';

type BaseSearchTypes = string | number | boolean | object;
export type SearchTypes = BaseSearchTypes | BaseSearchTypes[] | undefined;
const usageLabelPrefix: string[] = ['security_telemetry', 'sender'];

export interface TelemetryEvent {
[key: string]: SearchTypes;
Expand Down Expand Up @@ -66,13 +69,19 @@ export class TelemetryEventsSender {
private esClient?: ElasticsearchClient;
private savedObjectsClient?: SavedObjectsClientContract;
private exceptionListClient?: ExceptionListClient;
private telemetryUsageCounter?: UsageCounter;

constructor(logger: Logger) {
this.logger = logger.get('telemetry_events');
}

public setup(telemetrySetup?: TelemetryPluginSetup, taskManager?: TaskManagerSetupContract) {
public setup(
telemetrySetup?: TelemetryPluginSetup,
taskManager?: TaskManagerSetupContract,
telemetryUsageCounter?: UsageCounter
) {
this.telemetrySetup = telemetrySetup;
this.telemetryUsageCounter = telemetryUsageCounter;

if (taskManager) {
this.diagTask = new TelemetryDiagTask(this.logger, taskManager, this);
Expand Down Expand Up @@ -285,6 +294,16 @@ export class TelemetryEventsSender {
}

if (events.length > this.maxQueueSize - qlength) {
this.telemetryUsageCounter?.incrementCounter({
counterName: createUsageCounterLabel(usageLabelPrefix.concat(['queue_stats'])),
counterType: 'docs_lost',
incrementBy: events.length,
});
this.telemetryUsageCounter?.incrementCounter({
counterName: createUsageCounterLabel(usageLabelPrefix.concat(['queue_stats'])),
counterType: 'num_capacity_exceeded',
incrementBy: 1,
});
this.queue.push(...this.processEvents(events.slice(0, this.maxQueueSize - qlength)));
} else {
this.queue.push(...this.processEvents(events));
Expand Down Expand Up @@ -344,6 +363,7 @@ export class TelemetryEventsSender {
await this.sendEvents(
toSend,
telemetryUrl,
'alerts-endpoint',
clusterInfo.cluster_uuid,
clusterInfo.version?.number,
licenseInfo?.uid
Expand Down Expand Up @@ -379,6 +399,7 @@ export class TelemetryEventsSender {
await this.sendEvents(
toSend,
telemetryUrl,
channel,
clusterInfo.cluster_uuid,
clusterInfo.version?.number,
licenseInfo?.uid
Expand Down Expand Up @@ -429,6 +450,7 @@ export class TelemetryEventsSender {
private async sendEvents(
events: unknown[],
telemetryUrl: string,
channel: string,
clusterUuid: string,
clusterVersionNumber: string | undefined,
licenseId: string | undefined
Expand All @@ -445,11 +467,31 @@ export class TelemetryEventsSender {
...(licenseId ? { 'X-Elastic-License-ID': licenseId } : {}),
},
});
this.telemetryUsageCounter?.incrementCounter({
counterName: createUsageCounterLabel(usageLabelPrefix.concat(['payloads', channel])),
counterType: resp.status.toString(),
incrementBy: 1,
});
this.telemetryUsageCounter?.incrementCounter({
counterName: createUsageCounterLabel(usageLabelPrefix.concat(['payloads', channel])),
counterType: 'docs_sent',
incrementBy: events.length,
});
this.logger.debug(`Events sent!. Response: ${resp.status} ${JSON.stringify(resp.data)}`);
} catch (err) {
this.logger.warn(
`Error sending events: ${err.response.status} ${JSON.stringify(err.response.data)}`
);
this.telemetryUsageCounter?.incrementCounter({
counterName: createUsageCounterLabel(usageLabelPrefix.concat(['payloads', channel])),
counterType: 'docs_lost',
incrementBy: events.length,
});
this.telemetryUsageCounter?.incrementCounter({
counterName: createUsageCounterLabel(usageLabelPrefix.concat(['payloads', channel])),
counterType: 'num_exceptions',
incrementBy: 1,
});
}
}
}
Expand Down
14 changes: 12 additions & 2 deletions x-pack/plugins/security_solution/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import {
PluginSetup as DataPluginSetup,
PluginStart as DataPluginStart,
} from '../../../../src/plugins/data/server';
import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server';
import {
UsageCollectionSetup,
UsageCounter,
} from '../../../../src/plugins/usage_collection/server';
import {
PluginSetupContract as AlertingSetup,
PluginStartContract as AlertPluginStartContract,
Expand Down Expand Up @@ -143,6 +146,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S

private manifestTask: ManifestTask | undefined;
private artifactsCache: LRU<string, Buffer>;
private telemetryUsageCounter?: UsageCounter;

constructor(context: PluginInitializerContext) {
this.context = context;
Expand Down Expand Up @@ -181,6 +185,8 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
usageCollection: plugins.usageCollection,
});

this.telemetryUsageCounter = plugins.usageCollection?.createUsageCounter(APP_ID);

const router = core.http.createRouter<SecuritySolutionRequestHandlerContext>();
core.http.registerRouteHandlerContext<SecuritySolutionRequestHandlerContext, typeof APP_ID>(
APP_ID,
Expand Down Expand Up @@ -329,7 +335,11 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
);
});

this.telemetryEventsSender.setup(plugins.telemetry, plugins.taskManager);
this.telemetryEventsSender.setup(
plugins.telemetry,
plugins.taskManager,
this.telemetryUsageCounter
);

return {};
}
Expand Down

0 comments on commit 0101c8d

Please sign in to comment.