-
Notifications
You must be signed in to change notification settings - Fork 1
[SVLS-8757] Add instance enhanced metric in Azure Functions #114
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
base: main
Are you sure you want to change the base?
Changes from all commits
dd957b3
f5ef0b7
044e316
a57a342
fc24a42
c089617
3fd7bc4
ec977b2
345b000
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| [package] | ||
| name = "datadog-metrics-collector" | ||
| version = "0.1.0" | ||
| edition.workspace = true | ||
| license.workspace = true | ||
| description = "Collector to read, compute, and submit enhanced metrics in Serverless environments" | ||
|
|
||
| [dependencies] | ||
| dogstatsd = { path = "../dogstatsd", default-features = true } | ||
| tracing = { version = "0.1", default-features = false } | ||
| libdd-common = { git = "https://github.com/DataDog/libdatadog", rev = "8c88979985154d6d97c0fc2ca9039682981eacad", default-features = false } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| // Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| //! Instance identity metric collector for Azure Functions. | ||
| //! | ||
| //! Submits `azure.functions.enhanced.instance` with value 1.0 on each | ||
| //! collection tick, tagged with the instance identifier. | ||
|
|
||
| use dogstatsd::aggregator::AggregatorHandle; | ||
| use dogstatsd::metric::{Metric, MetricValue, SortedTags}; | ||
| use std::env; | ||
| use tracing::{error, warn}; | ||
|
|
||
| const INSTANCE_METRIC: &str = "azure.functions.enhanced.instance"; | ||
|
|
||
| /// Resolves the instance ID from explicit values (used by tests). | ||
| fn resolve_instance_id_from( | ||
| website_instance_id: Option<&str>, | ||
| website_pod_name: Option<&str>, | ||
| container_name: Option<&str>, | ||
| ) -> Option<String> { | ||
| website_instance_id | ||
| .or(website_pod_name) | ||
| .or(container_name) | ||
| .map(String::from) | ||
| } | ||
|
|
||
| /// Resolves the instance ID from environment variables. | ||
| /// | ||
| /// Checks in order: | ||
| /// 1. `WEBSITE_INSTANCE_ID` (Elastic Premium / Premium plans) | ||
| /// 2. `WEBSITE_POD_NAME` (Flex Consumption / Consumption plans) | ||
| /// 3. `CONTAINER_NAME` (Flex Consumption / Consumption plans) | ||
| fn resolve_instance_id() -> Option<String> { | ||
| resolve_instance_id_from( | ||
| env::var("WEBSITE_INSTANCE_ID").ok().as_deref(), | ||
| env::var("WEBSITE_POD_NAME").ok().as_deref(), | ||
| env::var("CONTAINER_NAME").ok().as_deref(), | ||
| ) | ||
| } | ||
|
|
||
| pub struct InstanceMetricsCollector { | ||
| aggregator: AggregatorHandle, | ||
| tags: Option<SortedTags>, | ||
| } | ||
|
|
||
| impl InstanceMetricsCollector { | ||
| /// Creates a new collector, returning `None` if no instance ID is found. | ||
| pub fn new(aggregator: AggregatorHandle, tags: Option<SortedTags>) -> Option<Self> { | ||
| let instance_id = resolve_instance_id(); | ||
| let Some(instance_id) = instance_id else { | ||
| warn!("No instance ID found, instance metric will not be submitted"); | ||
| return None; | ||
| }; | ||
|
|
||
| // Precompute tags: enhanced metrics tags + instance tag | ||
| let instance_tag = format!("instance:{}", instance_id); | ||
| let tags = match tags { | ||
| Some(mut existing) => { | ||
| if let Ok(id_tag) = SortedTags::parse(&instance_tag) { | ||
| existing.extend(&id_tag); | ||
| } | ||
| Some(existing) | ||
| } | ||
| None => SortedTags::parse(&instance_tag).ok(), | ||
| }; | ||
|
|
||
| Some(Self { aggregator, tags }) | ||
| } | ||
|
|
||
| pub fn collect_and_submit(&self) { | ||
| let metric = Metric::new( | ||
| INSTANCE_METRIC.into(), | ||
| MetricValue::gauge(1.0), | ||
| self.tags.clone(), | ||
| None, | ||
| ); | ||
|
|
||
| if let Err(e) = self.aggregator.insert_batch(vec![metric]) { | ||
| error!("Failed to insert instance metric: {}", e); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn test_resolve_instance_id_falls_back_to_pod_name() { | ||
| let id = resolve_instance_id_from(None, Some("pod-xyz"), Some("container-123")); | ||
| assert_eq!(id, Some("pod-xyz".to_string())); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_resolve_instance_id_falls_back_to_container_name() { | ||
| let id = resolve_instance_id_from(None, None, Some("container-123")); | ||
| assert_eq!(id, Some("container-123".to_string())); | ||
| } | ||
|
Comment on lines
+89
to
+99
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| #![cfg_attr(not(test), deny(clippy::panic))] | ||
| #![cfg_attr(not(test), deny(clippy::unwrap_used))] | ||
| #![cfg_attr(not(test), deny(clippy::expect_used))] | ||
| #![cfg_attr(not(test), deny(clippy::todo))] | ||
| #![cfg_attr(not(test), deny(clippy::unimplemented))] | ||
|
|
||
| pub mod instance; | ||
| pub mod tags; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| // Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| //! Shared tag builder for enhanced metrics. | ||
| //! | ||
| //! Tags are attached to all enhanced metrics submitted by the metrics collector. | ||
|
|
||
| use dogstatsd::metric::SortedTags; | ||
| use libdd_common::azure_app_services; | ||
| use std::env; | ||
|
|
||
| /// Builds the common tags for all enhanced metrics. | ||
| /// | ||
| /// Sources: | ||
| /// - Azure metadata (resource_group, subscription_id, name) from libdd_common | ||
| /// - Environment variables (region, plan_tier, service, env, version, serverless_compat_version) | ||
| /// | ||
| /// The DogStatsD origin tag (e.g. `origin:azurefunction`) is added by the metrics aggregator, | ||
| /// not here. | ||
| pub fn build_enhanced_metrics_tags() -> Option<SortedTags> { | ||
| let mut tag_parts = Vec::new(); | ||
|
|
||
| if let Some(aas_metadata) = &*azure_app_services::AAS_METADATA_FUNCTION { | ||
| let aas_tags = [ | ||
| ("resource_group", aas_metadata.get_resource_group()), | ||
| ("subscription_id", aas_metadata.get_subscription_id()), | ||
| ("name", aas_metadata.get_site_name()), | ||
| ]; | ||
| for (name, value) in aas_tags { | ||
| if value != "unknown" { | ||
| tag_parts.push(format!("{}:{}", name, value)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| for (tag_name, env_var) in [ | ||
| ("region", "REGION_NAME"), | ||
| ("plan_tier", "WEBSITE_SKU"), | ||
| ("service", "DD_SERVICE"), | ||
| ("env", "DD_ENV"), | ||
| ("version", "DD_VERSION"), | ||
| ("serverless_compat_version", "DD_SERVERLESS_COMPAT_VERSION"), | ||
| ] { | ||
| if let Ok(val) = env::var(env_var) | ||
| && !val.is_empty() | ||
| { | ||
| tag_parts.push(format!("{}:{}", tag_name, val)); | ||
| } | ||
|
Comment on lines
+36
to
+48
|
||
| } | ||
|
|
||
| if tag_parts.is_empty() { | ||
| return None; | ||
| } | ||
| SortedTags::parse(&tag_parts.join(",")).ok() | ||
| } | ||
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.
resolve_instance_id_fromtreats empty strings as valid instance IDs. If (for example)WEBSITE_INSTANCE_IDis set but empty, it will block fallbacks and produce aninstance:tag with an empty value. Consider filtering out empty/whitespace-only strings before applying the fallback chain.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.
These env vars should either be injected by Azure with a value or not set at all