Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix agent config indicator when applied through fleet integration #131820

Merged
merged 29 commits into from
May 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c92ba74
Fix agent config indicator when applied through fleet integration
gbamparop May 9, 2022
bab0ced
Add synthrace scenario
gbamparop May 11, 2022
7329fe0
Update docs in apmAgentConfigurationIndex with applied_by_agent
gbamparop May 11, 2022
d50d06c
Add API tests
gbamparop May 11, 2022
abae232
Move tests to agent_configuration subfolder
gbamparop May 11, 2022
29efe60
Index agent_config metrics to metrics-apm.internal-default
gbamparop May 11, 2022
44abd38
Revert Update docs in apmAgentConfigurationIndex with applied_by_agent
gbamparop May 12, 2022
e62bbc2
Apply logic to both listConfigurations and findExactConfiguration
gbamparop May 12, 2022
74bd08c
Revert omitTimestampAndId
gbamparop May 12, 2022
8b76c61
Merge branch 'main' into update-agent-config-indicator
kibanamachine May 12, 2022
57a3627
Mark etag as required in AgentConfiguration type
gbamparop May 16, 2022
560c644
Merge branch 'update-agent-config-indicator' of https://github.com/gb…
gbamparop May 16, 2022
0aa86ff
Pick fields for AgentConfigFields from ApmFields
gbamparop May 16, 2022
1cea1ea
Make requests for agent configs and agent_config metrics in parallel
gbamparop May 16, 2022
4f5b9de
Use reduce instead of mapValues / keyBy to construct the map
gbamparop May 17, 2022
e547c6f
Set size for the aggregation
gbamparop May 17, 2022
4757af5
Conver convertConfigSettingsToString to be immutable
gbamparop May 17, 2022
29205d6
Change AgentConfigMetrics to extend Metricset
gbamparop May 17, 2022
b1f8d4e
Merge branch 'main' into update-agent-config-indicator
kibanamachine May 17, 2022
5737fc0
Change timerange to the last minute
gbamparop May 17, 2022
e19ec2a
Clean synthrace data
gbamparop May 17, 2022
06171e3
Merge branch 'main' into update-agent-config-indicator
kibanamachine May 18, 2022
d9b00bf
Merge branch 'main' of https://github.com/elastic/kibana into update-…
gbamparop May 19, 2022
758d67a
Skip agent config metrics tests
gbamparop May 19, 2022
e918b75
clean synthrace data
gbamparop May 20, 2022
fc5fa66
Create a registry for agent config tests
gbamparop May 20, 2022
9445e65
Change timerange back to 15m
gbamparop May 20, 2022
98f4ee4
Add observer entity to synthrace
gbamparop May 20, 2022
f2cebe3
Merge branch 'main' into update-agent-config-indicator
kibanamachine May 23, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/elastic-apm-synthtrace/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
export { timerange } from './lib/timerange';
export { apm } from './lib/apm';
export { stackMonitoring } from './lib/stack_monitoring';
export { observer } from './lib/agent_config';
export { cleanWriteTargets } from './lib/utils/clean_write_targets';
export { createLogger, LogLevel } from './lib/utils/create_logger';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { AgentConfigFields } from './agent_config_fields';
import { Metricset } from '../apm/metricset';

export class AgentConfig extends Metricset<AgentConfigFields> {
constructor() {
super({
'metricset.name': 'agent_config',
agent_config_applied: 1,
});
}

etag(etag: string) {
this.fields['labels.etag'] = etag;
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { ApmFields } from '../apm/apm_fields';

export type AgentConfigFields = Pick<
ApmFields,
| '@timestamp'
| 'processor.event'
| 'processor.name'
| 'metricset.name'
| 'observer'
| 'ecs.version'
| 'event.ingested'
> &
Partial<{
'labels.etag': string;
agent_config_applied: number;
'event.agent_id_status': string;
}>;
9 changes: 9 additions & 0 deletions packages/elastic-apm-synthtrace/src/lib/agent_config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export { observer } from './observer';
21 changes: 21 additions & 0 deletions packages/elastic-apm-synthtrace/src/lib/agent_config/observer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { AgentConfigFields } from './agent_config_fields';
import { AgentConfig } from './agent_config';
import { Entity } from '../entity';

export class Observer extends Entity<AgentConfigFields> {
agentConfig() {
return new AgentConfig();
}
}

export function observer() {
return new Observer({});
}
2 changes: 1 addition & 1 deletion packages/elastic-apm-synthtrace/src/lib/apm/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class Instance extends Entity<ApmFields> {
}

appMetrics(metrics: ApmApplicationMetricFields) {
return new Metricset({
return new Metricset<ApmFields>({
...this.fields,
'metricset.name': 'app',
...metrics,
Expand Down
6 changes: 3 additions & 3 deletions packages/elastic-apm-synthtrace/src/lib/apm/metricset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
*/

import { Serializable } from '../serializable';
import { ApmFields } from './apm_fields';
import { Fields } from '../entity';

export class Metricset extends Serializable<ApmFields> {
constructor(fields: ApmFields) {
export class Metricset<TFields extends Fields> extends Serializable<TFields> {
constructor(fields: TFields) {
super({
'processor.event': 'metric',
'processor.name': 'metric',
Expand Down
4 changes: 3 additions & 1 deletion packages/elastic-apm-synthtrace/src/lib/stream_processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,9 @@ export class StreamProcessor<TFields extends Fields = ApmFields> {
const eventType = d.processor.event as keyof ApmElasticsearchOutputWriteTargets;
let dataStream = writeTargets[eventType];
if (eventType === 'metric') {
if (!d.service?.name) {
if (d.metricset?.name === 'agent_config') {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there another place preferred to set the data stream for these metrics? cc @Mpdreamz

dataStream = 'metrics-apm.internal-default';
} else if (!d.service?.name) {
dataStream = 'metrics-apm.app-default';
} else {
if (!d.transaction && !d.span) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { observer, timerange } from '../..';
import { Scenario } from '../scenario';
import { getLogger } from '../utils/get_common_services';
import { RunOptions } from '../utils/parse_run_cli_flags';
import { AgentConfigFields } from '../../lib/agent_config/agent_config_fields';

const scenario: Scenario<AgentConfigFields> = async (runOptions: RunOptions) => {
const logger = getLogger(runOptions);

return {
generate: ({ from, to }) => {
const agentConfig = observer().agentConfig();

const range = timerange(from, to);
return range
.interval('30s')
.rate(1)
.generator((timestamp) => {
const events = logger.perf('generating_agent_config_events', () => {
return agentConfig.etag('test-etag').timestamp(timestamp);
});
return events;
});
},
};
};

export default scenario;
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ export type AgentConfigurationIntake = t.TypeOf<
export type AgentConfiguration = {
'@timestamp': number;
applied_by_agent?: boolean;
etag?: string;
etag: string;
agent_name?: string;
} & AgentConfigurationIntake;
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,27 @@ import { AgentConfiguration } from '../../../../common/agent_configuration/confi
export function convertConfigSettingsToString(
hit: SearchHit<AgentConfiguration>
) {
const config = hit._source;
const { settings } = hit._source;

if (config.settings?.transaction_sample_rate) {
config.settings.transaction_sample_rate =
config.settings.transaction_sample_rate.toString();
}
const convertedConfigSettings = {
...settings,
...(settings?.transaction_sample_rate
? {
transaction_sample_rate: settings.transaction_sample_rate.toString(),
}
: {}),
...(settings?.transaction_max_spans
? {
transaction_max_spans: settings.transaction_max_spans.toString(),
}
: {}),
};

if (config.settings?.transaction_max_spans) {
config.settings.transaction_max_spans =
config.settings.transaction_max_spans.toString();
}

return hit;
return {
...hit,
_source: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it matters much, but it does feel kind of gross that we're transforming _source here. Is not returning _source, but just the data in _source an option, or is that a breaking change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel the same, looked to simplify it at first but it's used in the public API.

...hit._source,
settings: convertedConfigSettings,
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from '../../../../common/elasticsearch_fieldnames';
import { Setup } from '../../../lib/helpers/setup_request';
import { convertConfigSettingsToString } from './convert_settings_to_string';
import { getConfigsAppliedToAgentsThroughFleet } from './get_config_applied_to_agent_through_fleet';

export async function findExactConfiguration({
service,
Expand Down Expand Up @@ -40,16 +41,27 @@ export async function findExactConfiguration({
},
};

const resp = await internalClient.search<AgentConfiguration, typeof params>(
'find_exact_agent_configuration',
params
);
const [agentConfig, configsAppliedToAgentsThroughFleet] = await Promise.all([
internalClient.search<AgentConfiguration, typeof params>(
'find_exact_agent_configuration',
params
),
getConfigsAppliedToAgentsThroughFleet({ setup }),
]);

const hit = resp.hits.hits[0] as SearchHit<AgentConfiguration> | undefined;
const hit = agentConfig.hits.hits[0] as
| SearchHit<AgentConfiguration>
| undefined;

if (!hit) {
return;
}

return convertConfigSettingsToString(hit);
return {
id: hit._id,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is id new? is it a breaking change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is new but it's not a breaking change as this function is only used in private APIs and in the function body of some public ones without changing the return type

...convertConfigSettingsToString(hit)._source,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, can you convert this function to be immutable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in 4757af5

applied_by_agent:
hit._source.applied_by_agent ||
configsAppliedToAgentsThroughFleet.hasOwnProperty(hit._source.etag),
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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 { termQuery, rangeQuery } from '@kbn/observability-plugin/server';
import datemath from '@kbn/datemath';
import { METRICSET_NAME } from '../../../../common/elasticsearch_fieldnames';
import { Setup } from '../../../lib/helpers/setup_request';

export async function getConfigsAppliedToAgentsThroughFleet({
setup,
}: {
setup: Setup;
}) {
const { internalClient, indices } = setup;

const params = {
index: indices.metric,
size: 0,
body: {
query: {
bool: {
filter: [
...termQuery(METRICSET_NAME, 'agent_config'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious if this is the best way. Given it's a single etag value that we're interested in, would it make sense to pass the etag explicitly, and set terminate_after:1 here, rather than trying to "join" this data on the Kibana server?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah we also use it for a list of agent configurations. That means we cannot set terminate_after: 1. In that case, I suggest we parallelise the requests in both scenarios, I think that should work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, will do them in parallel

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 1cea1ea

...rangeQuery(
datemath.parse('now-15m')!.valueOf(),
datemath.parse('now')!.valueOf()
),
],
},
},
aggs: {
config_by_etag: {
terms: {
field: 'labels.etag',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and should size: 200 be set here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in e547c6f

size: 200,
},
},
},
},
};

const response = await internalClient.search(
'get_config_applied_to_agent_through_fleet',
params
);

return (
response.aggregations?.config_by_etag.buckets.reduce(
(configsAppliedToAgentsThroughFleet, bucket) => {
configsAppliedToAgentsThroughFleet[bucket.key as string] = true;
return configsAppliedToAgentsThroughFleet;
},
{} as Record<string, true>
) ?? {}
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { Setup } from '../../../lib/helpers/setup_request';
import { AgentConfiguration } from '../../../../common/agent_configuration/configuration_types';
import { convertConfigSettingsToString } from './convert_settings_to_string';
import { getConfigsAppliedToAgentsThroughFleet } from './get_config_applied_to_agent_through_fleet';

export async function listConfigurations({ setup }: { setup: Setup }) {
const { internalClient, indices } = setup;
Expand All @@ -17,12 +18,22 @@ export async function listConfigurations({ setup }: { setup: Setup }) {
size: 200,
};

const resp = await internalClient.search<AgentConfiguration>(
'list_agent_configuration',
params
);
const [agentConfigs, configsAppliedToAgentsThroughFleet] = await Promise.all([
internalClient.search<AgentConfiguration>(
'list_agent_configuration',
params
),
getConfigsAppliedToAgentsThroughFleet({ setup }),
]);

return resp.hits.hits
return agentConfigs.hits.hits
.map(convertConfigSettingsToString)
.map((hit) => hit._source);
.map((hit) => {
return {
...hit._source,
applied_by_agent:
hit._source.applied_by_agent ||
configsAppliedToAgentsThroughFleet.hasOwnProperty(hit._source.etag),
};
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ const agentConfigurationRoute = createApmServerRoute({
>;
}> => {
const setup = await setupRequest(resources);

const configurations = await listConfigurations({ setup });

return { configurations };
},
});
Expand Down Expand Up @@ -71,7 +73,7 @@ const getSingleAgentConfigurationRoute = createApmServerRoute({
throw Boom.notFound();
}

return config._source;
return config;
},
});

Expand Down Expand Up @@ -102,11 +104,11 @@ const deleteAgentConfigurationRoute = createApmServerRoute({
}

logger.info(
`Deleting config ${service.name}/${service.environment} (${config._id})`
`Deleting config ${service.name}/${service.environment} (${config.id})`
);

const deleteConfigurationResult = await deleteConfiguration({
configurationId: config._id,
configurationId: config.id,
setup,
});

Expand Down Expand Up @@ -162,7 +164,7 @@ const createOrUpdateAgentConfigurationRoute = createApmServerRoute({
);

await createOrUpdateConfiguration({
configurationId: config?._id,
configurationId: config?.id,
configurationIntake: body,
setup,
});
Expand Down
Loading