-
Notifications
You must be signed in to change notification settings - Fork 8.5k
Fix agent config indicator when applied through fleet integration #131820
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
Changes from 10 commits
c92ba74
bab0ced
7329fe0
d50d06c
abae232
29efe60
44abd38
e62bbc2
74bd08c
8b76c61
57a3627
560c644
0aa86ff
1cea1ea
4f5b9de
e547c6f
4757af5
29205d6
b1f8d4e
5737fc0
e19ec2a
06171e3
d9b00bf
758d67a
e918b75
fc5fa66
9445e65
98f4ee4
f2cebe3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| /* | ||
| * 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 { Entity } from '../entity'; | ||
| import { AgentConfigFields } from './agent_config_fields'; | ||
| import { AgentConfigMetrics } from './agent_config_metrics'; | ||
|
|
||
| export class AgentConfig extends Entity<AgentConfigFields> { | ||
| metrics() { | ||
| return new AgentConfigMetrics({ | ||
| ...this.fields, | ||
| 'processor.event': 'metric', | ||
| 'processor.name': 'metric', | ||
| 'metricset.name': 'agent_config', | ||
| agent_config_applied: 1, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| export function agentConfig(etag: string) { | ||
| return new AgentConfig({ | ||
| 'labels.etag': etag, | ||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| /* | ||
| * 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 { Fields } from '../entity'; | ||
|
|
||
| export interface Observer { | ||
| hostname: string; | ||
| id: string; | ||
| ephemeral_id: string; | ||
| type: string; | ||
| version: string; | ||
| } | ||
|
|
||
| export type AgentConfigFields = Fields & | ||
|
||
| Partial<{ | ||
| 'processor.event': string; | ||
| 'processor.name': string; | ||
| 'labels.etag': string; | ||
| 'metricset.name': string; | ||
| observer: Observer; | ||
| agent_config_applied: number; | ||
| 'ecs.version': string; | ||
| 'event.agent_id_status': string; | ||
| 'event.ingested': string; | ||
| }>; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| /* | ||
| * 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 { Serializable } from '../serializable'; | ||
| import { AgentConfigFields } from './agent_config_fields'; | ||
|
|
||
| export class AgentConfigMetrics extends Serializable<AgentConfigFields> { | ||
|
||
| timestamp(timestamp: number): this { | ||
| super.timestamp(timestamp); | ||
| this.fields['event.ingested'] = new Date(timestamp).toISOString(); | ||
|
||
| return this; | ||
| } | ||
| } | ||
| 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 { agentConfig } from './agent_config'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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') { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
|
|
||
| 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 { agentConfig, 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 agentConfigMetrics = agentConfig('test-etag').metrics(); | ||
|
|
||
| const range = timerange(from, to); | ||
| return range | ||
| .interval('30s') | ||
| .rate(1) | ||
| .generator((timestamp) => { | ||
| const events = logger.perf('generating_agent_config_events', () => { | ||
| return agentConfigMetrics.timestamp(timestamp); | ||
| }); | ||
| return events; | ||
| }); | ||
| }, | ||
| }; | ||
| }; | ||
|
|
||
| export default scenario; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
|
|
@@ -51,5 +52,15 @@ export async function findExactConfiguration({ | |
| return; | ||
| } | ||
|
|
||
| return convertConfigSettingsToString(hit); | ||
| const configsAppliedToAgentsThroughFleet = | ||
| await getConfigsAppliedToAgentsThroughFleet({ setup }); | ||
|
||
|
|
||
| return { | ||
| id: hit._id, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. btw, can you convert this function to be immutable?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed in 4757af5 |
||
| applied_by_agent: | ||
| hit._source.applied_by_agent || | ||
| (hit._source.etag !== undefined && | ||
| configsAppliedToAgentsThroughFleet.hasOwnProperty(hit._source.etag)), | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| /* | ||
| * 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 { keyBy, mapValues } from 'lodash'; | ||
| 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: 200, | ||
|
||
| body: { | ||
| query: { | ||
| bool: { | ||
| filter: [ | ||
| ...termQuery(METRICSET_NAME, 'agent_config'), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense, will do them in parallel
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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', | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and should
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated in e547c6f |
||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| const response = await internalClient.search( | ||
| 'get_config_applied_to_agent_through_fleet', | ||
| params | ||
| ); | ||
|
|
||
| return mapValues( | ||
| keyBy(response.aggregations?.config_by_etag.buckets, 'key'), | ||
| 'key' | ||
| ) as Record<string, string>; | ||
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -22,7 +23,16 @@ export async function listConfigurations({ setup }: { setup: Setup }) { | |
| params | ||
| ); | ||
|
|
||
| return resp.hits.hits | ||
| .map(convertConfigSettingsToString) | ||
| .map((hit) => hit._source); | ||
| const configsAppliedToAgentsThroughFleet = | ||
| await getConfigsAppliedToAgentsThroughFleet({ setup }); | ||
|
|
||
| return resp.hits.hits.map(convertConfigSettingsToString).map((hit) => { | ||
| return { | ||
| ...hit._source, | ||
| applied_by_agent: | ||
| hit._source.applied_by_agent || | ||
| (hit._source.etag !== undefined && | ||
|
||
| configsAppliedToAgentsThroughFleet.hasOwnProperty(hit._source.etag)), | ||
| }; | ||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| /* | ||
| * 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 { timerange, agentConfig } from '@elastic/apm-synthtrace'; | ||
| import type { ApmSynthtraceEsClient } from '@elastic/apm-synthtrace'; | ||
|
|
||
| export async function addAgentConfigMetrics({ | ||
| synthtraceEsClient, | ||
| start, | ||
| end, | ||
| etag, | ||
| }: { | ||
| synthtraceEsClient: ApmSynthtraceEsClient; | ||
| start: number; | ||
| end: number; | ||
| etag?: string; | ||
| }) { | ||
| const agentConfigMetrics = agentConfig(etag ?? 'test-etag').metrics(); | ||
|
|
||
| const agentConfigEvents = [ | ||
| timerange(start, end) | ||
| .interval('1m') | ||
| .rate(1) | ||
| .generator((timestamp) => agentConfigMetrics.timestamp(timestamp)), | ||
| ]; | ||
|
|
||
| await synthtraceEsClient.index(agentConfigEvents); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an interesting one, it doesn't really match with how we use entities, but I can see how it's the easiest option. Thinking out loud: these are internal APM Server metrics, so not from self-instrumentation. Maybe something like
Observer?and then automatically set
etagwhen serializing?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback, could you please explain a bit further / elaborate on how we intend to use entities in synthrace?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also it seems that etag is calculated in kibana from
AgentConfigurationIntakewhich included the settings.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gbamparop entities are things that collect and send data. not data by itself. does that make sense? and can you clarify what you mean with:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dgieselaar etag is a hash based on all the config settings, that's why I was thinking to pass the etag when creating an agent config with synthrace. Do you think it'll be better to pass service name / environment and just create a hash based on this?