Skip to content

Commit

Permalink
[Fleet] Define new telemetry hourly job for reusable policies (#188536)
Browse files Browse the repository at this point in the history
Part of #75867

Depends on merging first elastic/telemetry#3759

## Summary

Define new telemetry hourly job for reusable policies; Example of data:
```
   {
        total_integration_policies: 3,
        shared_integration_policies: 2,
        shared_integrations: {
          agents: 10,
          name: 'aws-1',
          pkg_name: 'aws',
          pkg_version: '1.0.0',
          shared_by_agent_policies: 3,
        }
```


### Checklist
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
criamico and elasticmachine authored Jul 19, 2024
1 parent 39a5515 commit 76c19c6
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 1 deletion.
111 changes: 111 additions & 0 deletions x-pack/plugins/fleet/server/collectors/integrations_collector.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* 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 { savedObjectsClientMock } from '@kbn/core/server/mocks';

import { packagePolicyService } from '../services';

import { getIntegrationsDetails } from './integrations_collector';

describe('getIntegrationsDetails', () => {
const soClientMock = savedObjectsClientMock.create();

it('should return empty array if there are no package policies', async () => {
packagePolicyService.list = jest.fn().mockResolvedValue({
items: [],
});
expect(await getIntegrationsDetails(soClientMock)).toEqual([]);
});

it('should return data about shared integration policies', async () => {
packagePolicyService.list = jest.fn().mockResolvedValue({
items: [
{
name: 'apache-1',
package: { name: 'apache', version: '1.0.0' },
policy_ids: ['agent-1', 'agent-2'],
},
{
name: 'aws-11',
package: { name: 'aws', version: '1.0.0' },
policy_ids: ['agent-1'],
agents: 10,
},
{ name: 'nginx-1', package: { name: 'aws', version: '1.0.0' } },
],
});
expect(await getIntegrationsDetails(soClientMock)).toEqual([
{
total_integration_policies: 3,
shared_integration_policies: 1,
shared_integrations: {
agents: undefined,
name: 'apache-1',
pkg_name: 'apache',
pkg_version: '1.0.0',
shared_by_agent_policies: 2,
},
},
]);
});

it('should return data about shared integration policies when there are multiple of them', async () => {
packagePolicyService.list = jest.fn().mockResolvedValue({
items: [
{
name: 'apache-1',
package: { name: 'apache', version: '1.0.0' },
policy_ids: ['agent-1', 'agent-2'],
},
{
name: 'aws-1',
package: { name: 'aws', version: '1.0.0' },
policy_ids: ['agent-1', 'agent-3', 'agent-4'],
agents: 10,
},
{ name: 'nginx-1', package: { name: 'aws', version: '1.0.0' } },
],
});
expect(await getIntegrationsDetails(soClientMock)).toEqual([
{
total_integration_policies: 3,
shared_integration_policies: 2,
shared_integrations: {
agents: undefined,
name: 'apache-1',
pkg_name: 'apache',
pkg_version: '1.0.0',
shared_by_agent_policies: 2,
},
},
{
total_integration_policies: 3,
shared_integration_policies: 2,
shared_integrations: {
agents: 10,
name: 'aws-1',
pkg_name: 'aws',
pkg_version: '1.0.0',
shared_by_agent_policies: 3,
},
},
]);
});

it('should return empty array if there are no shared integrations', async () => {
packagePolicyService.list = jest.fn().mockResolvedValue({
items: [
{
name: 'apache-1',
package: { name: 'apache', version: '1.0.0' },
},
{ name: 'nginx-1', package: { name: 'aws', version: '1.0.0' } },
],
});
expect(await getIntegrationsDetails(soClientMock)).toEqual([]);
});
});
56 changes: 56 additions & 0 deletions x-pack/plugins/fleet/server/collectors/integrations_collector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 type { SavedObjectsClientContract } from '@kbn/core/server';

import { SO_SEARCH_LIMIT } from '../constants';
import { packagePolicyService } from '../services';

export interface IntegrationsDetails {
total_integration_policies: number;
shared_integration_policies: number;
shared_integrations: SharedIntegration;
}

interface SharedIntegration {
name: string;
shared_by_agent_policies: number;
pkg_name?: string;
pkg_version?: string;
agents?: number;
}

export const getIntegrationsDetails = async (
soClient?: SavedObjectsClientContract
): Promise<IntegrationsDetails[]> => {
if (!soClient) {
return [];
}
const allPackagePolicies = await packagePolicyService.list(soClient, {
perPage: SO_SEARCH_LIMIT,
});
const sharedPackagePolicies = allPackagePolicies.items.filter((packagePolicy) => {
if (packagePolicy?.policy_ids?.length > 1) return packagePolicy;
});

const integrationsDetails: IntegrationsDetails[] = (sharedPackagePolicies || []).map(
(packagePolicy) => {
return {
total_integration_policies: allPackagePolicies.items.length,
shared_integration_policies: sharedPackagePolicies.length,
shared_integrations: {
name: packagePolicy.name,
pkg_name: packagePolicy.package?.name,
pkg_version: packagePolicy.package?.version,
shared_by_agent_policies: packagePolicy?.policy_ids.length,
agents: packagePolicy?.agents,
},
};
}
);
return integrationsDetails;
};
4 changes: 4 additions & 0 deletions x-pack/plugins/fleet/server/collectors/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { getPanicLogsLastHour } from './agent_logs_panics';
import { getAgentLogsTopErrors } from './agent_logs_top_errors';
import type { AgentsPerOutputType } from './agents_per_output';
import { getAgentsPerOutput } from './agents_per_output';
import type { IntegrationsDetails } from './integrations_collector';
import { getIntegrationsDetails } from './integrations_collector';

export interface Usage {
agents_enabled: boolean;
Expand All @@ -41,6 +43,7 @@ export interface FleetUsage extends Usage, AgentData {
agent_logs_top_errors?: string[];
fleet_server_logs_top_errors?: string[];
agents_per_output_type: AgentsPerOutputType[];
integrations_details: IntegrationsDetails[];
}

export const fetchFleetUsage = async (
Expand All @@ -65,6 +68,7 @@ export const fetchFleetUsage = async (
agents_per_output_type: await getAgentsPerOutput(soClient, esClient),
license_issued_to: (await esClient.license.get()).license.issued_to,
deployment_id: appContextService.getCloud()?.deploymentId,
integrations_details: await getIntegrationsDetails(soClient),
};
return usage;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,20 @@ describe('fleet usage telemetry', () => {
],
});

await soClient.create('ingest-package-policies', {
name: 'nginx-1',
namespace: 'default',
package: {
name: 'nginx',
title: 'Nginx',
version: '1.0.0',
},
enabled: true,
policy_id: 'policy2',
policy_ids: ['policy2', 'policy3'],
inputs: [],
});

await soClient.create(
'ingest-outputs',
{
Expand Down Expand Up @@ -593,6 +607,19 @@ describe('fleet usage telemetry', () => {
'this should not be included in metrics',
],
fleet_server_logs_top_errors: ['failed to unenroll offline agents'],
integrations_details: [
{
total_integration_policies: 2,
shared_integration_policies: 1,
shared_integrations: {
agents: undefined,
name: 'nginx-1',
pkg_name: 'nginx',
pkg_version: '1.0.0',
shared_by_agent_policies: 2,
},
},
],
})
);
expect(usage?.upgrade_details.length).toBe(3);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@ import type { FleetUsage } from '../../collectors/register';

import { appContextService } from '../app_context';

import { fleetAgentsSchema, fleetUsagesSchema } from './fleet_usages_schema';
import {
fleetAgentsSchema,
fleetUsagesSchema,
fleetIntegrationsSchema,
} from './fleet_usages_schema';

const FLEET_USAGES_EVENT_TYPE = 'fleet_usage';
const FLEET_AGENTS_EVENT_TYPE = 'fleet_agents';
const FLEET_INTEGRATIONS_EVENT_TYPE = 'fleet_integrations';

export class FleetUsageSender {
private taskManager?: TaskManagerStartContract;
Expand Down Expand Up @@ -89,6 +94,7 @@ export class FleetUsageSender {
agents_per_output_type: agentsPerOutputType,
agents_per_privileges: agentsPerPrivileges,
upgrade_details: upgradeDetails,
integrations_details: integrationsDetails,
...fleetUsageData
} = usageData;
appContextService
Expand Down Expand Up @@ -126,6 +132,15 @@ export class FleetUsageSender {
upgradeDetails.forEach((upgradeDetailsObj) => {
core.analytics.reportEvent(FLEET_AGENTS_EVENT_TYPE, { upgrade_details: upgradeDetailsObj });
});

appContextService
.getLogger()
.debug(() => 'Integrations details telemetry: ' + JSON.stringify(integrationsDetails));
integrationsDetails.forEach((integrationDetailsObj) => {
core.analytics.reportEvent(FLEET_INTEGRATIONS_EVENT_TYPE, {
integrations_details: integrationDetailsObj,
});
});
} catch (error) {
appContextService
.getLogger()
Expand Down Expand Up @@ -180,5 +195,10 @@ export class FleetUsageSender {
eventType: FLEET_AGENTS_EVENT_TYPE,
schema: fleetAgentsSchema,
});

core.analytics.registerEventType({
eventType: FLEET_INTEGRATIONS_EVENT_TYPE,
schema: fleetIntegrationsSchema,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -448,3 +448,50 @@ export const fleetUsagesSchema: RootSchema<any> = {
_meta: { description: 'id of the deployment', optional: true },
},
};

export const fleetIntegrationsSchema: RootSchema<any> = {
total_integration_policies: {
type: 'long',
_meta: {
description: 'Count of total integration policies in this kibana',
},
},
shared_integration_policies: {
type: 'long',
_meta: {
description: 'Count of integration policies shared across agent policies in this kibana',
},
},
shared_integrations: {
properties: {
name: {
type: 'keyword',
_meta: { description: 'Name of the integration policy' },
},
pkg_name: {
type: 'keyword',
_meta: {
description: 'Name of the integration package installed on the integration policy',
},
},
pkg_version: {
type: 'keyword',
_meta: {
description: 'Version of the integration package installed on the integration policy',
},
},
shared_by_agent_policies: {
type: 'long',
_meta: {
description: 'Count of agent policies sharing the integration policy',
},
},
agents: {
type: 'long',
_meta: {
description: 'Number of agents installed on the integration policy',
},
},
},
},
};

0 comments on commit 76c19c6

Please sign in to comment.