diff --git a/Cargo.lock b/Cargo.lock index 9985655..81fef7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -390,6 +390,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "datadog-metrics-collector" +version = "0.1.0" +dependencies = [ + "dogstatsd", + "num_cpus", + "tracing", +] + [[package]] name = "datadog-protos" version = "0.1.0" @@ -408,6 +417,7 @@ dependencies = [ name = "datadog-serverless-compat" version = "0.1.0" dependencies = [ + "datadog-metrics-collector", "datadog-trace-agent", "dogstatsd", "libdd-trace-utils", @@ -426,6 +436,7 @@ dependencies = [ "async-trait", "bytes", "datadog-fips", + "dogstatsd", "duplicate", "http-body-util", "hyper", @@ -435,6 +446,7 @@ dependencies = [ "libdd-trace-obfuscation", "libdd-trace-protobuf", "libdd-trace-utils", + "num_cpus", "reqwest", "rmp-serde", "serde", @@ -853,6 +865,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" @@ -1492,6 +1510,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1745,7 +1773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.114", diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 6b22109..804a26a 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -69,6 +69,7 @@ headers,https://github.com/hyperium/headers,MIT,Sean McArthur heck,https://github.com/withoutboats/heck,MIT OR Apache-2.0,The heck Authors heck,https://github.com/withoutboats/heck,MIT OR Apache-2.0,Without Boats +hermit-abi,https://github.com/hermit-os/hermit-rs,MIT OR Apache-2.0,Stefan Lankes hex,https://github.com/KokaKiwi/rust-hex,MIT OR Apache-2.0,KokaKiwi home,https://github.com/rust-lang/cargo,MIT OR Apache-2.0,Brian Anderson http,https://github.com/hyperium/http,MIT OR Apache-2.0,"Alex Crichton , Carl Lerche , Sean McArthur " @@ -119,6 +120,7 @@ multimap,https://github.com/havarnov/multimap,MIT OR Apache-2.0,Håvar Nøvik , Josh Triplett , The Nushell Project Developers" num-traits,https://github.com/rust-num/num-traits,MIT OR Apache-2.0,The Rust Project Developers +num_cpus,https://github.com/seanmonstar/num_cpus,MIT OR Apache-2.0,Sean McArthur once_cell,https://github.com/matklad/once_cell,MIT OR Apache-2.0,Aleksey Kladov openssl-probe,https://github.com/rustls/openssl-probe,MIT OR Apache-2.0,Alex Crichton ordered-float,https://github.com/reem/rust-ordered-float,MIT,"Jonathan Reem , Matt Brubeck " diff --git a/crates/datadog-metrics-collector/Cargo.toml b/crates/datadog-metrics-collector/Cargo.toml new file mode 100644 index 0000000..4061bd1 --- /dev/null +++ b/crates/datadog-metrics-collector/Cargo.toml @@ -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 } +num_cpus = "1.16" +tracing = { version = "0.1", default-features = false } diff --git a/crates/datadog-metrics-collector/src/cpu.rs b/crates/datadog-metrics-collector/src/cpu.rs new file mode 100644 index 0000000..d31ef1c --- /dev/null +++ b/crates/datadog-metrics-collector/src/cpu.rs @@ -0,0 +1,143 @@ +// 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 metrics collection, computing CPU usage +//! adnd limit and submitting them as distribution metrics to Datadog. +//! +//! All CPU metrics are reported in nanocores (1 core = 1,000,000,000 nanocores). + +use dogstatsd::aggregator_service::AggregatorHandle; +use dogstatsd::metric::{Metric, MetricValue, SortedTags}; +use tracing::{debug, error}; + +const CPU_USAGE_METRIC: &str = "azure.functions.enhanced.test.cpu.usage"; +const CPU_USAGE_PRECISE_METRIC: &str = "azure.functions.enhanced.test.cpu.usage.precise"; +const CPU_LIMIT_METRIC: &str = "azure.functions.enhanced.test.cpu.limit"; + +/// Computed CPU total and limit metrics +pub struct CpuStats { + pub total: f64, // Cumulative CPU usage in nanoseconds + pub limit: Option, // CPU limit in nanocores + pub defaulted_limit: bool, // Whether CPU limit was defaulted to host CPU count +} + +pub trait CpuStatsReader { + fn read(&self) -> Option; +} + +pub struct CpuMetricsCollector { + reader: Box, + aggregator: AggregatorHandle, + tags: Option, + collection_interval_secs: u64, + last_usage_ns: f64, + 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 + /// * `collection_interval_secs` - The interval in seconds to collect the metrics + pub fn new( + aggregator: AggregatorHandle, + tags: Option, + collection_interval_secs: u64, + ) -> Self { + #[cfg(target_os = "windows")] + let reader: Box = Box::new(crate::windows::WindowsCpuStatsReader); + #[cfg(not(target_os = "windows"))] + let reader: Box = Box::new(crate::linux::LinuxCpuStatsReader); + Self { + reader, + aggregator, + tags, + collection_interval_secs, + last_usage_ns: -1.0, + last_collection_time: std::time::Instant::now(), + } + } + + pub fn collect_and_submit(&mut self) { + if let Some(cpu_stats) = self.reader.read() { + // Submit metrics + debug!("Collected cpu stats!"); + let current_usage_ns = cpu_stats.total; + debug!("CPU usage: {}", cpu_stats.total); + let now_instant = std::time::Instant::now(); + + // Skip first collection + if self.last_usage_ns == -1.0 { + debug!("First CPU collection, skipping rate computation"); + self.last_usage_ns = current_usage_ns; + self.last_collection_time = now_instant; + return; + } + + let delta_ns = current_usage_ns - self.last_usage_ns; + self.last_usage_ns = current_usage_ns; + let elapsed_secs = self.last_collection_time.elapsed().as_secs_f64(); + debug!("Elapsed time: {} seconds", elapsed_secs); + self.last_collection_time = now_instant; + + // Divide nanoseconds delta by collection interval to get usage rate in nanocores + let usage_rate_nc = delta_ns / self.collection_interval_secs as f64; + debug!("Usage rate: {} nanocores/s", usage_rate_nc); + let precise_usage_rate_nc = delta_ns / elapsed_secs; + debug!("Precise usage rate: {} nanocores/s", precise_usage_rate_nc); + + let now = std::time::UNIX_EPOCH + .elapsed() + .map(|d| d.as_secs()) + .unwrap_or(0) + .try_into() + .unwrap_or(0); + + let precise_metric = Metric::new( + CPU_USAGE_PRECISE_METRIC.into(), + MetricValue::distribution(precise_usage_rate_nc), + self.tags.clone(), + Some(now), + ); + + if let Err(e) = self.aggregator.insert_batch(vec![precise_metric]) { + error!("Failed to insert CPU usage precise metric: {}", e); + } + + 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); + } + + if let Some(limit) = cpu_stats.limit { + debug!("CPU limit: {}", limit); + if cpu_stats.defaulted_limit { + debug!("CPU limit defaulted to host CPU count"); + } + let limit_metric = Metric::new( + CPU_LIMIT_METRIC.into(), + MetricValue::distribution(limit), + self.tags.clone(), + Some(now), + ); + if let Err(e) = self.aggregator.insert_batch(vec![limit_metric]) { + error!("Failed to insert CPU limit metric: {}", e); + } + } + debug!("Submitting CPU metrics!"); + } else { + debug!("Skipping CPU metrics collection - could not find data to generate CPU usage and limit enhanced metrics"); + } + } +} diff --git a/crates/datadog-metrics-collector/src/lib.rs b/crates/datadog-metrics-collector/src/lib.rs new file mode 100644 index 0000000..f600d65 --- /dev/null +++ b/crates/datadog-metrics-collector/src/lib.rs @@ -0,0 +1,14 @@ +// 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 cpu; +#[cfg(not(target_os = "windows"))] +pub mod linux; +#[cfg(target_os = "windows")] +pub mod windows; diff --git a/crates/datadog-metrics-collector/src/linux.rs b/crates/datadog-metrics-collector/src/linux.rs new file mode 100644 index 0000000..87220fc --- /dev/null +++ b/crates/datadog-metrics-collector/src/linux.rs @@ -0,0 +1,201 @@ +// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! CPU metrics collector for Azure Functions +//! +//! This module provides functionality to read raw CPU statistics from cgroup v1 files +//! and compute the CPU usage and limit in Linux environments. +//! +//! All CPU metrics are reported in nanocores (1 core = 1,000,000,000 nanocores). + +use crate::cpu::{CpuStats, CpuStatsReader}; +use num_cpus; +use std::fs; +use std::io; +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 +const CGROUP_CPUSET_CPUS_PATH: &str = "/sys/fs/cgroup/cpuset/cpuset.cpus"; // Specifies the CPUs that tasks in this cgroup are permitted to access +const CGROUP_CPU_PERIOD_PATH: &str = "/sys/fs/cgroup/cpu/cpu.cfs_period_us"; // Specifies a period of time, in microseconds, for how regularly a cgroup's access to CPU resources should be reallocated +const CGROUP_CPU_QUOTA_PATH: &str = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"; // Specifies the total amount of time, in microseconds, for which all tasks in a cgroup can run during one period + +/// Statistics from cgroup v1 files, normalized to nanoseconds +struct CgroupStats { + total: Option, // Cumulative CPU usage (from cpuacct.usage) in nanoseconds + cpu_count: Option, // Number of accessible logical CPUs (from cpuset.cpus) + scheduler_period: Option, // CFS scheduler period (from cpu.cfs_period_us) in nanoseconds + scheduler_quota: Option, // CFS scheduler quota (from cpu.cfs_quota_us) in nanoseconds +} + +pub struct LinuxCpuStatsReader; + +impl CpuStatsReader for LinuxCpuStatsReader { + fn read(&self) -> Option { + let cgroup_stats = read_cgroup_stats(); + build_cpu_stats(&cgroup_stats) + } +} + +/// Builds CPU stats - rate and limit +fn build_cpu_stats(cgroup_stats: &CgroupStats) -> Option { + let total = cgroup_stats.total?; + + let (limit_nc, defaulted) = compute_cpu_limit_nc(cgroup_stats); + + Some(CpuStats { + total: total as f64, + limit: Some(limit_nc), + defaulted_limit: defaulted, + }) +} + +/// Reads raw CPU statistics from cgroup v1 files and converts to nanoseconds +fn read_cgroup_stats() -> CgroupStats { + let total = fs::read_to_string(CGROUP_CPU_USAGE_PATH) + .ok() + .and_then(|contents| contents.trim().parse::().ok()); + if total.is_none() { + debug!("Could not read CPU usage from {CGROUP_CPU_USAGE_PATH}"); + } + + let cpu_count = read_cpu_count_from_file(CGROUP_CPUSET_CPUS_PATH).ok(); + if cpu_count.is_none() { + debug!("Could not read CPU count from {CGROUP_CPUSET_CPUS_PATH}"); + } + + let scheduler_period = fs::read_to_string(CGROUP_CPU_PERIOD_PATH) + .ok() + .and_then(|contents| contents.trim().parse::().map(|v| v * 1000).ok()); // Convert from microseconds to nanoseconds + if scheduler_period.is_none() { + debug!("Could not read scheduler period from {CGROUP_CPU_PERIOD_PATH}"); + } + + let scheduler_quota = fs::read_to_string(CGROUP_CPU_QUOTA_PATH) + .ok() + .and_then(|contents| { + contents.trim().parse::().ok().and_then(|quota| { + // Convert from microseconds to nanoseconds + if quota == -1 { + debug!("CFS scheduler quota is -1, setting to None"); + None + } else { + Some((quota * 1000) as u64) + } + }) + }); + if scheduler_quota.is_none() { + debug!("Could not read scheduler quota from {CGROUP_CPU_QUOTA_PATH}"); + } + + CgroupStats { + total, + cpu_count, + scheduler_period, + scheduler_quota, + } +} + +/// Reads CPU count from cpuset.cpus +/// +/// The cpuset.cpus file contains a comma-separated list, with dashes to represent ranges of CPUs, +/// e.g., "0-2,16" represents CPUs 0, 1, 2, and 16 +/// This function returns the count of CPUs, in this case 4. +fn read_cpu_count_from_file(path: &str) -> Result { + let contents = fs::read_to_string(path)?; + let cpuset_str = contents.trim(); + if cpuset_str.is_empty() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("File {path} is empty"), + )); + } + debug!("Contents of {path}: {cpuset_str}"); + + let mut cpu_count: u64 = 0; + + for part in cpuset_str.split(',') { + let range: Vec<&str> = part.split('-').collect(); + if range.len() == 2 { + // Range like "0-3" + debug!("Range: {range:?}"); + let start: u64 = range[0].parse().map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("Failed to parse u64 from range {range:?}: {e}"), + ) + })?; + let end: u64 = range[1].parse().map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("Failed to parse u64 from range {range:?}: {e}"), + ) + })?; + cpu_count += end - start + 1; + } else { + // Single CPU like "2" + debug!("Single CPU: {part}"); + cpu_count += 1; + } + } + + debug!("Total CPU count: {cpu_count}"); + Ok(cpu_count) +} + +/// Computes the CPU limit in nanocores, with fallback to host CPU count +fn compute_cpu_limit_nc(cgroup_stats: &CgroupStats) -> (f64, bool) { + match compute_cgroup_cpu_limit_nc(cgroup_stats) { + Some(limit) => (limit, false), + None => { + let host_cpu_count = num_cpus::get() as f64; + debug!( + "No CPU limit found, defaulting to host CPU count: {} CPUs", + host_cpu_count + ); + (host_cpu_count * 1000000000.0, true) // Convert to nanocores + } + } +} + +/// Computes the CPU limit in nanocores from cgroup statistics +/// Limit is computed using min(CPUSet, CFS CPU Quota) +fn compute_cgroup_cpu_limit_nc(cgroup_stats: &CgroupStats) -> Option { + let mut limit_nc = None; + + if let Some(cpu_count) = cgroup_stats.cpu_count { + debug!("CPU count from cpuset: {cpu_count}"); + let host_cpu_count = num_cpus::get() as u64; + if cpu_count != host_cpu_count { + debug!("CPU count from cpuset is not equal to host CPU count"); + let cpuset_limit_nc = cpu_count as f64 * 1000000000.0; // Convert to nanocores + limit_nc = Some(cpuset_limit_nc); + debug!( + "CPU limit from cpuset: {} CPUs ({} nanocores)", + cpu_count, cpuset_limit_nc + ); + } + } + + if let (Some(scheduler_quota), Some(scheduler_period)) = + (cgroup_stats.scheduler_quota, cgroup_stats.scheduler_period) + { + let quota_limit_nc = 1000000000.0 * (scheduler_quota as f64 / scheduler_period as f64); + match limit_nc { + None => { + limit_nc = Some(quota_limit_nc); + debug!( + "limit_nc is None, setting CPU limit from cfs quota: {} nanocores", + quota_limit_nc + ); + } + Some(current_limit_nc) if quota_limit_nc < current_limit_nc => { + limit_nc = Some(quota_limit_nc); + debug!("CPU limit from cfs quota is less than current limit, setting CPU limit from cfs quota: {} nanocores", quota_limit_nc); + } + _ => { + debug!("Keeping cpuset limit: {:?} nanocores", limit_nc); + } + } + } + limit_nc +} diff --git a/crates/datadog-metrics-collector/src/windows.rs b/crates/datadog-metrics-collector/src/windows.rs new file mode 100644 index 0000000..1be055a --- /dev/null +++ b/crates/datadog-metrics-collector/src/windows.rs @@ -0,0 +1,14 @@ +// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +use crate::cpu::{CpuStats, CpuStatsReader}; +use tracing::debug; + +pub struct WindowsCpuStatsReader; + +impl CpuStatsReader for WindowsCpuStatsReader { + fn read(&self) -> Option { + debug!("Reading CPU stats from Windows"); + None + } +} diff --git a/crates/datadog-serverless-compat/Cargo.toml b/crates/datadog-serverless-compat/Cargo.toml index b4f96b4..02378c9 100644 --- a/crates/datadog-serverless-compat/Cargo.toml +++ b/crates/datadog-serverless-compat/Cargo.toml @@ -7,6 +7,7 @@ description = "Binary to run trace-agent and dogstatsd servers in Serverless env [dependencies] datadog-trace-agent = { path = "../datadog-trace-agent" } +datadog-metrics-collector = { path = "../datadog-metrics-collector" } libdd-trace-utils = { git = "https://github.com/DataDog/libdatadog", rev = "d52ee90209cb12a28bdda0114535c1a985a29d95" } dogstatsd = { path = "../dogstatsd", default-features = true } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/crates/datadog-serverless-compat/src/main.rs b/crates/datadog-serverless-compat/src/main.rs index adc106e..066f186 100644 --- a/crates/datadog-serverless-compat/src/main.rs +++ b/crates/datadog-serverless-compat/src/main.rs @@ -23,6 +23,8 @@ use datadog_trace_agent::{ trace_processor, }; +use datadog_metrics_collector::cpu::CpuMetricsCollector; + use libdd_trace_utils::{config_utils::read_cloud_env, trace_utils::EnvironmentType}; use dogstatsd::{ @@ -38,6 +40,7 @@ use dogstatsd::{ use dogstatsd::metric::{SortedTags, EMPTY_TAGS}; use tokio_util::sync::CancellationToken; +const CPU_METRICS_COLLECTION_INTERVAL: u64 = 3; const DOGSTATSD_FLUSH_INTERVAL: u64 = 10; const DOGSTATSD_TIMEOUT_DURATION: Duration = Duration::from_secs(5); const DEFAULT_DOGSTATSD_PORT: u16 = 8125; @@ -49,7 +52,7 @@ pub async fn main() { .map(|val| val.to_lowercase()) .unwrap_or("info".to_string()); - let (_, env_type) = match read_cloud_env() { + let (app_name, env_type) = match read_cloud_env() { Some(value) => value, None => { error!("Unable to identify environment. Shutting down Mini Agent."); @@ -103,6 +106,10 @@ pub async fn main() { .ok() .and_then(|val| parse_metric_namespace(&val)); + let dd_enhanced_metrics = env::var("DD_ENHANCED_METRICS") + .map(|val| val.to_lowercase() != "false") + .unwrap_or(true); + let https_proxy = env::var("DD_PROXY_HTTPS") .or_else(|_| env::var("HTTPS_PROXY")) .ok(); @@ -169,7 +176,7 @@ pub async fn main() { } }); - let (metrics_flusher, _aggregator_handle) = if dd_use_dogstatsd { + let (metrics_flusher, aggregator_handle) = if dd_use_dogstatsd { debug!("Starting dogstatsd"); let (_, metrics_flusher, aggregator_handle) = start_dogstatsd( dd_dogstatsd_port, @@ -192,15 +199,49 @@ pub async fn main() { (None, None) }; + // If DD_ENHANCED_METRICS is true, start the CPU metrics collector + // Use the existing aggregator handle + // TODO: See if this works in Google Cloud Functions Gen 1. If not, only enable this for Azure Functions. + let mut cpu_collector = if dd_enhanced_metrics { + aggregator_handle.as_ref().map(|handle| { + // Elastic Premium and Premium plans use WEBSITE_INSTANCE_ID to identify the instance + // Flex Consumption and Consumption plans use WEBSITE_POD_NAME or CONTAINER_NAME + let instance_id = env::var("WEBSITE_INSTANCE_ID") + .or_else(|_| env::var("WEBSITE_POD_NAME")) + .or_else(|_| env::var("CONTAINER_NAME")) + .ok(); + debug!("Instance ID: {:?}", instance_id); + let mut tag_str = format!("functionname:{}", app_name); + if let Some(id) = instance_id { + tag_str.push_str(&format!(",instance_id:{}", id)); + } + let tags = SortedTags::parse(&tag_str).ok(); + CpuMetricsCollector::new(handle.clone(), tags, CPU_METRICS_COLLECTION_INTERVAL) + }) + } else { + info!("Enhanced metrics disabled"); + None + }; + let mut flush_interval = interval(Duration::from_secs(DOGSTATSD_FLUSH_INTERVAL)); + let mut cpu_collection_interval = + interval(Duration::from_secs(CPU_METRICS_COLLECTION_INTERVAL)); flush_interval.tick().await; // discard first tick, which is instantaneous + cpu_collection_interval.tick().await; loop { - flush_interval.tick().await; - - if let Some(metrics_flusher) = metrics_flusher.as_ref() { - debug!("Flushing dogstatsd metrics"); - metrics_flusher.flush().await; + tokio::select! { + _ = flush_interval.tick() => { + if let Some(metrics_flusher) = metrics_flusher.as_ref() { + debug!("Flushing dogstatsd metrics"); + metrics_flusher.flush().await; + } + } + _ = cpu_collection_interval.tick() => { + if let Some(ref mut collector) = cpu_collector { + collector.collect_and_submit(); + } + } } } } diff --git a/crates/datadog-trace-agent/Cargo.toml b/crates/datadog-trace-agent/Cargo.toml index 8a60864..e554fe1 100644 --- a/crates/datadog-trace-agent/Cargo.toml +++ b/crates/datadog-trace-agent/Cargo.toml @@ -29,6 +29,8 @@ libdd-trace-obfuscation = { git = "https://github.com/DataDog/libdatadog", rev = datadog-fips = { path = "../datadog-fips" } reqwest = { version = "0.12.23", features = ["json", "http2"], default-features = false } bytes = "1.10.1" +dogstatsd = { path = "../dogstatsd", default-features = true } +num_cpus = "1.16" [dev-dependencies] rmp-serde = "1.1.1" diff --git a/crates/dogstatsd/src/origin.rs b/crates/dogstatsd/src/origin.rs index d0c0952..818766d 100644 --- a/crates/dogstatsd/src/origin.rs +++ b/crates/dogstatsd/src/origin.rs @@ -18,6 +18,7 @@ const AZURE_FUNCTIONS_TAG_VALUE: &str = "azurefunction"; const DATADOG_PREFIX: &str = "datadog."; const AWS_LAMBDA_PREFIX: &str = "aws.lambda"; const GOOGLE_CLOUD_RUN_PREFIX: &str = "gcp.run"; +const AZURE_FUNCTIONS_PREFIX: &str = "azure.functions"; const JVM_PREFIX: &str = "jvm."; const RUNTIME_PREFIX: &str = "runtime."; @@ -83,15 +84,17 @@ impl Metric { .join("."); // Determine the service based on metric prefix first - let service = if metric_name.starts_with(JVM_PREFIX) - || metric_name.starts_with(RUNTIME_PREFIX) - { - OriginService::ServerlessRuntime - } else if metric_prefix == AWS_LAMBDA_PREFIX || metric_prefix == GOOGLE_CLOUD_RUN_PREFIX { - OriginService::ServerlessEnhanced - } else { - OriginService::ServerlessCustom - }; + let service = + if metric_name.starts_with(JVM_PREFIX) || metric_name.starts_with(RUNTIME_PREFIX) { + OriginService::ServerlessRuntime + } else if metric_prefix == AWS_LAMBDA_PREFIX + || metric_prefix == GOOGLE_CLOUD_RUN_PREFIX + || metric_prefix == AZURE_FUNCTIONS_PREFIX + { + OriginService::ServerlessEnhanced + } else { + OriginService::ServerlessCustom + }; // Then determine the category based on tags let category = if has_tag_value(&tags, AWS_LAMBDA_TAG_KEY, "") {