-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
Changes from 9 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 & | ||
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. can you pick these from 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 0aa86ff |
||
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> { | ||
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. Does it make sense to extend this from 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 29205d6 |
||
timestamp(timestamp: number): this { | ||
super.timestamp(timestamp); | ||
this.fields['event.ingested'] = new Date(timestamp).toISOString(); | ||
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 field is set by an ingest pipeline. I wonder if there's a more appropriate place to set this. 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 can just remove this (although it's defined it doesn't seem to be set for |
||
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') { | ||
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 }); | ||
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. can this request happen in parallel? 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. Good point, I had them in parallel when they were made in the route, but missed it when moved to the helper function. 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 |
||
|
||
return { | ||
id: hit._id, | ||
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 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, | ||
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? 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, | ||
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. why 200? should it be configurable? 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. actually looks like size should be 0? 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. That was meant for the aggregation. What do you think the size should be? Config list returns 200. 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 |
||
body: { | ||
query: { | ||
bool: { | ||
filter: [ | ||
...termQuery(METRICSET_NAME, 'agent_config'), | ||
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 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 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 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', | ||
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 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>; | ||
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. Maybe a simple 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.
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 4f5b9de |
||
} |
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 && | ||
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. when would 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. It was defined as optional originally on the interface and ts was complaining. I'll change it to be required 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 57a3627 |
||
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
etag
when 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
AgentConfigurationIntake
which 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?