diff --git a/.github/workflows/build-datadog-serverless-compat.yml b/.github/workflows/build-datadog-serverless-compat.yml index 8c88500..070938f 100644 --- a/.github/workflows/build-datadog-serverless-compat.yml +++ b/.github/workflows/build-datadog-serverless-compat.yml @@ -56,7 +56,7 @@ jobs: retention-days: 3 - if: ${{ inputs.runner == 'windows-2022' }} shell: bash - run: cargo build --release -p datadog-serverless-compat --features windows-pipes + run: cargo build --release -p datadog-serverless-compat --features windows-pipes,windows-enhanced-metrics - if: ${{ inputs.runner == 'windows-2022' }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 4.6.2 with: @@ -69,7 +69,7 @@ jobs: rustup target add i686-pc-windows-msvc cargo build --release -p datadog-serverless-compat \ --target i686-pc-windows-msvc \ - --features windows-pipes + --features windows-pipes,windows-enhanced-metrics - if: ${{ inputs.runner == 'windows-2022' }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 4.6.2 with: diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml index ba363a9..db6553e 100644 --- a/.github/workflows/cargo.yml +++ b/.github/workflows/cargo.yml @@ -32,7 +32,12 @@ jobs: shell: bash run: chmod +x ./scripts/install-protoc.sh && ./scripts/install-protoc.sh $HOME - shell: bash - run: cargo check --workspace + run: | + if [[ "${{ inputs.runner }}" == "windows-2022" ]]; then + cargo check --workspace --features datadog-serverless-compat/windows-pipes,datadog-serverless-compat/windows-enhanced-metrics + else + cargo check --workspace + fi format: name: Format @@ -72,7 +77,12 @@ jobs: shell: bash run: chmod +x ./scripts/install-protoc.sh && ./scripts/install-protoc.sh $HOME - shell: bash - run: cargo build --all + run: | + if [[ "${{ inputs.runner }}" == "windows-2022" ]]; then + cargo build --all --features datadog-serverless-compat/windows-pipes,datadog-serverless-compat/windows-enhanced-metrics + else + cargo build --all + fi build-datadog-serverless-compat: name: Build Datadog Serverless Compat @@ -95,7 +105,7 @@ jobs: - shell: bash run: | if [[ "${{ inputs.runner }}" == "windows-2022" ]]; then - cargo nextest run --workspace --features datadog-serverless-compat/windows-pipes + cargo nextest run --workspace --features datadog-serverless-compat/windows-pipes,datadog-serverless-compat/windows-enhanced-metrics else cargo nextest run --workspace fi diff --git a/Cargo.lock b/Cargo.lock index 6038c63..9b0082d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -493,6 +493,7 @@ version = "0.1.0" dependencies = [ "dogstatsd", "libdd-common 4.0.0", + "tokio", "tracing", ] @@ -712,7 +713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1995,7 +1996,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2719,7 +2720,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3056,7 +3057,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3152,7 +3153,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/crates/datadog-metrics-collector/Cargo.toml b/crates/datadog-metrics-collector/Cargo.toml index 90b55e6..b6f5d4e 100644 --- a/crates/datadog-metrics-collector/Cargo.toml +++ b/crates/datadog-metrics-collector/Cargo.toml @@ -9,3 +9,9 @@ description = "Collector to read, compute, and submit enhanced metrics in Server dogstatsd = { path = "../dogstatsd", default-features = true } tracing = { version = "0.1", default-features = false } libdd-common = { git = "https://github.com/DataDog/libdatadog", rev = "4ae8ebe252451374c292efd159ce254c3f5a72e0", default-features = false } + +[dev-dependencies] +tokio = { version = "1", default-features = false, features = ["macros", "rt-multi-thread"] } + +[features] +windows-enhanced-metrics = [] diff --git a/crates/datadog-metrics-collector/src/azure_cpu.rs b/crates/datadog-metrics-collector/src/azure_cpu.rs new file mode 100644 index 0000000..157e242 --- /dev/null +++ b/crates/datadog-metrics-collector/src/azure_cpu.rs @@ -0,0 +1,277 @@ +// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! CPU metrics collector for Azure Functions +//! +//! This module provides OS-agnostic CPU stats collection, CPU usage +//! computation, and metrics submission to Datadog. +//! +//! All CPU metrics are reported in nanocores (1 core = 1,000,000,000 nanocores). + +use dogstatsd::aggregator::AggregatorHandle; +use dogstatsd::metric::{Metric, MetricValue, SortedTags}; +use tracing::{debug, error}; + +const CPU_USAGE_METRIC: &str = "azure.functions.enhanced.cpu.usage"; + +/// Computed CPU total usage metric +pub struct CpuStats { + pub total: u64, // Cumulative CPU usage in nanoseconds +} + +pub trait CpuStatsReader { + fn read(&self) -> Option; +} + +pub struct CpuMetricsCollector { + reader: Box, + aggregator: AggregatorHandle, + tags: Option, + last_usage_ns: Option, + last_collection_time: std::time::Instant, +} + +impl CpuMetricsCollector { + /// Creates a new CpuMetricsCollector + /// + /// # Arguments + /// + /// * `aggregator` - The aggregator handle to submit metrics to + /// * `tags` - Optional tags to attach to all metrics + pub fn new(aggregator: AggregatorHandle, tags: Option) -> Self { + #[cfg(all(windows, feature = "windows-enhanced-metrics"))] + let reader: Box = Box::new(crate::azure_windows::WindowsCpuStatsReader); + #[cfg(not(windows))] + let reader: Box = Box::new(crate::azure_linux::LinuxCpuStatsReader); + Self { + reader, + aggregator, + tags, + last_usage_ns: None, + last_collection_time: std::time::Instant::now(), + } + } + + pub fn collect_and_submit(&mut self) { + let Some(cpu_stats) = self.reader.read() else { + debug!( + "Skipping CPU enhanced metrics collection - could not find data to generate CPU usage metrics" + ); + return; + }; + + let current_usage_ns = cpu_stats.total; + let now_instant = std::time::Instant::now(); + let now = std::time::UNIX_EPOCH + .elapsed() + .map(|d| d.as_secs()) + .unwrap_or(0) + .try_into() + .unwrap_or(0); + + // Skip first collection + let Some(last_usage_ns) = self.last_usage_ns else { + debug!("First CPU collection, skipping interval"); + self.last_usage_ns = Some(current_usage_ns); + self.last_collection_time = now_instant; + return; + }; + + let elapsed_secs = now_instant + .duration_since(self.last_collection_time) + .as_secs_f64(); + + // Update state so the next collection always compares against the most recent reading, even if the interval is skipped + self.last_usage_ns = Some(current_usage_ns); + self.last_collection_time = now_instant; + + let Some(usage_rate_nc) = + compute_usage_rate_nc(current_usage_ns, last_usage_ns, elapsed_secs) + else { + return; + }; + + let usage_metric = Metric::new( + CPU_USAGE_METRIC.into(), + MetricValue::distribution(usage_rate_nc), + self.tags.clone(), + Some(now), + ); + + if let Err(e) = self.aggregator.insert_batch(vec![usage_metric]) { + error!("Failed to insert CPU usage metric: {}", e); + } + } +} + +fn compute_usage_rate_nc( + current_usage_ns: u64, + last_usage_ns: u64, + elapsed_secs: f64, +) -> Option { + if current_usage_ns < last_usage_ns { + debug!("Current CPU usage is less than last usage, skipping interval"); + return None; + } + if elapsed_secs <= 0.0 { + debug!("Elapsed time is less than or equal to 0, skipping interval"); + return None; + } + let delta_ns = (current_usage_ns - last_usage_ns) as f64; + Some(delta_ns / elapsed_secs) +} + +// For testing only since CpuMetricsCollector::new() hardcodes the reader based on OS +#[cfg(test)] +impl CpuMetricsCollector { + pub(crate) fn with_reader( + reader: Box, + aggregator: AggregatorHandle, + tags: Option, + ) -> Self { + Self { + reader, + aggregator, + tags, + last_usage_ns: None, + last_collection_time: std::time::Instant::now(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use dogstatsd::aggregator::AggregatorService; + use dogstatsd::metric::EMPTY_TAGS; + use std::cell::Cell; + + #[test] + fn test_normal_delta() { + assert_eq!( + compute_usage_rate_nc(2_000_000_000, 1_000_000_000, 1.0), + Some(1_000_000_000.0) + ); + } + + #[test] + fn test_current_usage_less_than_last_usage_returns_none() { + assert!(compute_usage_rate_nc(1_000_000_000, 2_000_000_000, 1.0).is_none()); + } + + #[test] + fn test_zero_elapsed_time_returns_none() { + assert!(compute_usage_rate_nc(2_000_000_000, 1_000_000_000, 0.0).is_none()); + } + + struct MockCpuStatsReader { + idx: Cell, + values: Vec>, + } + + impl MockCpuStatsReader { + fn new(values: Vec>) -> Self { + Self { + idx: Cell::new(0), + values, + } + } + } + + impl CpuStatsReader for MockCpuStatsReader { + fn read(&self) -> Option { + let i = self.idx.get(); + self.idx.set(i + 1); + self.values + .get(i) + .and_then(|v| v.map(|total| CpuStats { total })) + } + } + + #[tokio::test] + async fn test_first_collection_skipped() { + let (service, handle) = + AggregatorService::new(EMPTY_TAGS, 1000).expect("Aggregator creation failed"); + let task = tokio::spawn(service.run()); + + let mut collector = CpuMetricsCollector::with_reader( + Box::new(MockCpuStatsReader::new(vec![Some(1_000_000_000)])), + handle.clone(), + None, + ); + collector.collect_and_submit(); + + let response = handle.flush().await.expect("flush failed"); + assert!( + response.distributions.is_empty(), + "Expected no batches flushed on first collection, got {:?}", + response.distributions.len() + ); + + handle.shutdown().expect("Shutdown failed"); + task.await.expect("Service task panicked"); + } + + #[tokio::test] + async fn test_second_collection_submits_metric() { + let (service, handle) = + AggregatorService::new(EMPTY_TAGS, 1000).expect("Aggregator creation failed"); + let task = tokio::spawn(service.run()); + + let mut collector = CpuMetricsCollector::with_reader( + Box::new(MockCpuStatsReader::new(vec![ + Some(1_000_000_000), + Some(2_000_000_000), + ])), + handle.clone(), + None, + ); + collector.collect_and_submit(); + collector.collect_and_submit(); + + let response = handle.flush().await.expect("Flush failed"); + assert_eq!( + response.distributions.len(), + 1, + "Expected 1 batch to be flushed, got {:?}", + response.distributions.len() + ); + assert_eq!( + response.distributions[0].sketches.len(), + 1, + "Expected 1 metric, got {:?}", + response.distributions[0].sketches.len() + ); + + handle.shutdown().expect("Shutdown failed"); + task.await.expect("Service task panicked"); + } + + #[tokio::test] + async fn test_current_usage_less_than_last_usage_skips_interval() { + let (service, handle) = + AggregatorService::new(EMPTY_TAGS, 1000).expect("Aggregator creation failed"); + let task = tokio::spawn(service.run()); + + let mut collector = CpuMetricsCollector::with_reader( + Box::new(MockCpuStatsReader::new(vec![ + Some(2_000_000_000), + Some(1_000_000_000), + ])), + handle.clone(), + None, + ); + collector.collect_and_submit(); + collector.collect_and_submit(); + + let response = handle.flush().await.expect("Flush failed"); + assert!( + response.distributions.is_empty(), + "Expected no batches flushed when current usage is less than last usage, got {:?}", + response.distributions.len() + ); + + handle.shutdown().expect("Shutdown failed"); + task.await.expect("Service task panicked"); + } +} diff --git a/crates/datadog-metrics-collector/src/azure_linux.rs b/crates/datadog-metrics-collector/src/azure_linux.rs new file mode 100644 index 0000000..8c435e7 --- /dev/null +++ b/crates/datadog-metrics-collector/src/azure_linux.rs @@ -0,0 +1,73 @@ +// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! CPU metrics collector for Azure Functions (Linux) +//! +//! This module provides functionality to read raw CPU statistics from cgroup v1 files +//! and compute the CPU usage in Linux environments. +//! +//! All CPU metrics are reported in nanocores (1 core = 1,000,000,000 nanocores). + +use crate::azure_cpu::{CpuStats, CpuStatsReader}; +use std::fs; +use tracing::debug; + +const CGROUP_CPU_USAGE_PATH: &str = "/sys/fs/cgroup/cpu/cpuacct.usage"; // Reports the total CPU time, in nanoseconds, consumed by all tasks in this cgroup + +pub struct LinuxCpuStatsReader; + +impl CpuStatsReader for LinuxCpuStatsReader { + fn read(&self) -> Option { + read_cpuacct_usage_from_path(CGROUP_CPU_USAGE_PATH) + } +} + +fn read_cpuacct_usage_from_path(path: &str) -> Option { + match fs::read_to_string(path) + .ok() + .and_then(|contents| contents.trim().parse::().ok()) + { + Some(total) => Some(CpuStats { total }), + None => { + debug!("Could not read CPU usage from {:?}", path); + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + fn fixture_path(file: &str) -> String { + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push(file); + path.to_str().unwrap().to_string() + } + + #[test] + fn test_reads_valid_value() { + let result = + read_cpuacct_usage_from_path(&fixture_path("tests/cgroup/valid_cpuacct_usage")); + assert_eq!(result.map(|s| s.total), Some(12345678)); + } + + #[test] + fn test_returns_none_for_missing_file() { + let result = read_cpuacct_usage_from_path(&fixture_path("tests/cgroup/nonexistent_file")); + assert!(result.is_none()); + } + + #[test] + fn test_returns_none_for_invalid_content() { + let result = read_cpuacct_usage_from_path(&fixture_path("tests/cgroup/invalid_content")); + assert!(result.is_none()); + } + + #[test] + fn test_trims_whitespace() { + let result = read_cpuacct_usage_from_path(&fixture_path("tests/cgroup/whitespace")); + assert_eq!(result.map(|s| s.total), Some(99999)); + } +} diff --git a/crates/datadog-metrics-collector/src/azure_windows.rs b/crates/datadog-metrics-collector/src/azure_windows.rs new file mode 100644 index 0000000..ecebb89 --- /dev/null +++ b/crates/datadog-metrics-collector/src/azure_windows.rs @@ -0,0 +1,20 @@ +// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! CPU metrics collector for Azure Functions (Windows) +//! +//! NOTE: Windows CPU enhanced metrics are not yet supported. +//! WindowsCpuStatsReader currently always returns None, so no CPU +//! usage information is reported in Windows environments. +//! +//! All CPU metrics will be reported in nanocores (1 core = 1,000,000,000 nanocores). + +use crate::azure_cpu::{CpuStats, CpuStatsReader}; + +pub struct WindowsCpuStatsReader; + +impl CpuStatsReader for WindowsCpuStatsReader { + fn read(&self) -> Option { + None + } +} diff --git a/crates/datadog-metrics-collector/src/lib.rs b/crates/datadog-metrics-collector/src/lib.rs index 173d62a..3e9558d 100644 --- a/crates/datadog-metrics-collector/src/lib.rs +++ b/crates/datadog-metrics-collector/src/lib.rs @@ -7,5 +7,10 @@ #![cfg_attr(not(test), deny(clippy::todo))] #![cfg_attr(not(test), deny(clippy::unimplemented))] +pub mod azure_cpu; pub mod azure_instance; +#[cfg(not(windows))] +pub(crate) mod azure_linux; pub mod azure_tags; +#[cfg(all(windows, feature = "windows-enhanced-metrics"))] +pub(crate) mod azure_windows; diff --git a/crates/datadog-metrics-collector/tests/cgroup/invalid_content b/crates/datadog-metrics-collector/tests/cgroup/invalid_content new file mode 100644 index 0000000..6cb3e8c --- /dev/null +++ b/crates/datadog-metrics-collector/tests/cgroup/invalid_content @@ -0,0 +1 @@ +not_a_number diff --git a/crates/datadog-metrics-collector/tests/cgroup/valid_cpuacct_usage b/crates/datadog-metrics-collector/tests/cgroup/valid_cpuacct_usage new file mode 100644 index 0000000..97b5955 --- /dev/null +++ b/crates/datadog-metrics-collector/tests/cgroup/valid_cpuacct_usage @@ -0,0 +1 @@ +12345678 diff --git a/crates/datadog-metrics-collector/tests/cgroup/whitespace b/crates/datadog-metrics-collector/tests/cgroup/whitespace new file mode 100644 index 0000000..f19b388 --- /dev/null +++ b/crates/datadog-metrics-collector/tests/cgroup/whitespace @@ -0,0 +1 @@ + 99999 diff --git a/crates/datadog-serverless-compat/Cargo.toml b/crates/datadog-serverless-compat/Cargo.toml index d50dfaf..9f25701 100644 --- a/crates/datadog-serverless-compat/Cargo.toml +++ b/crates/datadog-serverless-compat/Cargo.toml @@ -8,6 +8,7 @@ description = "Binary to run trace-agent and dogstatsd servers in Serverless env [features] default = [] windows-pipes = ["datadog-trace-agent/windows-pipes", "dogstatsd/windows-pipes"] +windows-enhanced-metrics = ["datadog-metrics-collector/windows-enhanced-metrics"] [dependencies] datadog-logs-agent = { path = "../datadog-logs-agent" } diff --git a/crates/datadog-serverless-compat/src/main.rs b/crates/datadog-serverless-compat/src/main.rs index c91541b..a0fccc2 100644 --- a/crates/datadog-serverless-compat/src/main.rs +++ b/crates/datadog-serverless-compat/src/main.rs @@ -24,6 +24,8 @@ use datadog_trace_agent::{ trace_processor, }; +use datadog_metrics_collector::azure_cpu::CpuMetricsCollector; + use libdd_trace_utils::{config_utils::read_cloud_env, trace_utils::EnvironmentType}; use datadog_fips::reqwest_adapter::create_reqwest_client_builder; @@ -45,6 +47,7 @@ use datadog_metrics_collector::azure_instance::InstanceMetricsCollector; use dogstatsd::metric::{EMPTY_TAGS, SortedTags}; use tokio_util::sync::CancellationToken; +const CPU_METRICS_COLLECTION_INTERVAL_SECS: u64 = 1; const DOGSTATSD_FLUSH_INTERVAL: u64 = 10; const INSTANCE_METRICS_COLLECTION_INTERVAL_SECS: u64 = 3; const DOGSTATSD_TIMEOUT_DURATION: Duration = Duration::from_secs(5); @@ -117,6 +120,17 @@ pub async fn main() { .ok() .and_then(|val| parse_metric_namespace(&val)); + // Only enable enhanced metrics for Linux Azure Functions + #[cfg(not(windows))] + let dd_enhanced_metrics = env_type == EnvironmentType::AzureFunction + && env::var("DD_ENHANCED_METRICS_ENABLED") + .map(|val| val.to_lowercase() != "false") + .unwrap_or(true); + + // Enhanced metrics are not yet supported in Windows environments + #[cfg(all(windows, feature = "windows-enhanced-metrics"))] + let dd_enhanced_metrics = false; + let https_proxy = env::var("DD_PROXY_HTTPS") .or_else(|_| env::var("HTTPS_PROXY")) .ok(); @@ -217,8 +231,11 @@ pub async fn main() { } }); - let enabled_metrics_components = - decide_metrics_components(dd_use_dogstatsd, instance_metric_enabled); + let enabled_metrics_components = decide_metrics_components( + dd_use_dogstatsd, + instance_metric_enabled, + dd_enhanced_metrics, + ); // The metrics aggregator and flusher are started together and shared between dogstatsd and enhanced metrics, // so they are started if either is enabled. @@ -270,6 +287,21 @@ pub async fn main() { None }; + let mut cpu_collector = + if enabled_metrics_components.start_cpu_metrics_collector && metrics_flusher.is_some() { + aggregator_handle.as_ref().map(|handle| { + let tags = datadog_metrics_collector::azure_tags::build_enhanced_metrics_tags(); + CpuMetricsCollector::new(handle.clone(), tags) + }) + } else { + if !enabled_metrics_components.start_cpu_metrics_collector { + info!("Enhanced metrics disabled"); + } else { + info!("Enhanced metrics enabled but metrics flusher not found"); + } + None + }; + let (log_flusher, _log_aggregator_handle): (Option, Option) = if dd_logs_enabled { debug!("Starting log agent"); @@ -292,8 +324,11 @@ pub async fn main() { let mut instance_metrics_collection_interval = interval(Duration::from_secs( INSTANCE_METRICS_COLLECTION_INTERVAL_SECS, )); + let mut cpu_collection_interval = + interval(Duration::from_secs(CPU_METRICS_COLLECTION_INTERVAL_SECS)); flush_interval.tick().await; // discard first tick, which is instantaneous instance_metrics_collection_interval.tick().await; + cpu_collection_interval.tick().await; // Builders for log batches that failed transiently in the previous flush // cycle. They are redriven on the next cycle before new batches are sent. @@ -330,6 +365,11 @@ pub async fn main() { collector.collect_and_submit(); } } + _ = cpu_collection_interval.tick(), if cpu_collector.is_some() => { + if let Some(ref mut collector) = cpu_collector { + collector.collect_and_submit(); + } + } } } } @@ -530,6 +570,7 @@ struct EnabledMetricsComponents { start_metrics_aggregator_and_flusher: bool, start_dogstatsd_listener: bool, start_instance_metrics_collector: bool, + start_cpu_metrics_collector: bool, } /// Determines which components should be started based on configuration. @@ -539,16 +580,19 @@ struct EnabledMetricsComponents { fn decide_metrics_components( dd_use_dogstatsd: bool, instance_metric_enabled: bool, + dd_enhanced_metrics: bool, ) -> EnabledMetricsComponents { let start_dogstatsd_listener = dd_use_dogstatsd; let start_instance_metrics_collector = instance_metric_enabled; + let start_cpu_metrics_collector = dd_enhanced_metrics; let start_metrics_aggregator_and_flusher = - start_dogstatsd_listener || start_instance_metrics_collector; + start_dogstatsd_listener || start_instance_metrics_collector || start_cpu_metrics_collector; EnabledMetricsComponents { start_metrics_aggregator_and_flusher, start_dogstatsd_listener, start_instance_metrics_collector, + start_cpu_metrics_collector, } } @@ -665,50 +709,80 @@ mod metrics_components_tests { #[test] fn test_decide_metrics_components() { - let cases: &[(bool, bool, EnabledMetricsComponents)] = &[ + let cases: &[(bool, bool, bool, EnabledMetricsComponents)] = &[ ( + false, false, false, EnabledMetricsComponents { start_metrics_aggregator_and_flusher: false, start_dogstatsd_listener: false, start_instance_metrics_collector: false, + start_cpu_metrics_collector: false, }, ), ( true, false, + false, EnabledMetricsComponents { start_metrics_aggregator_and_flusher: true, start_dogstatsd_listener: true, start_instance_metrics_collector: false, + start_cpu_metrics_collector: false, }, ), ( false, true, + false, EnabledMetricsComponents { start_metrics_aggregator_and_flusher: true, start_dogstatsd_listener: false, start_instance_metrics_collector: true, + start_cpu_metrics_collector: false, }, ), ( + true, + true, + false, + EnabledMetricsComponents { + start_metrics_aggregator_and_flusher: true, + start_dogstatsd_listener: true, + start_instance_metrics_collector: true, + start_cpu_metrics_collector: false, + }, + ), + ( + false, + true, + true, + EnabledMetricsComponents { + start_metrics_aggregator_and_flusher: true, + start_dogstatsd_listener: false, + start_instance_metrics_collector: true, + start_cpu_metrics_collector: true, + }, + ), + ( + true, true, true, EnabledMetricsComponents { start_metrics_aggregator_and_flusher: true, start_dogstatsd_listener: true, start_instance_metrics_collector: true, + start_cpu_metrics_collector: true, }, ), ]; - for (dogstatsd, instance, expected) in cases { - let actual = decide_metrics_components(*dogstatsd, *instance); + for (dogstatsd, instance, enhanced, expected) in cases { + let actual = decide_metrics_components(*dogstatsd, *instance, *enhanced); assert_eq!( &actual, expected, - "case (dd_use_dogstatsd={dogstatsd}, instance_metric_enabled={instance})" + "case (dd_use_dogstatsd={dogstatsd}, instance_metric_enabled={instance}, dd_enhanced_metrics={enhanced})" ); } }