Skip to content

Commit 05b14cd

Browse files
authored
[SVLS-7018] Support profiling in Azure Functions (#40)
* Handle proxy payload * Send proxy payload through mpsc channel and handle forwarding and retries in background task * Add additional tags in profiles * Respect DD_APM_PROFILING_DD_URL for profiling intake url override * Detect environment when setting _dd.origin tag
1 parent 18b49ba commit 05b14cd

12 files changed

Lines changed: 909 additions & 713 deletions

File tree

Cargo.lock

Lines changed: 526 additions & 643 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

LICENSE-3rdparty.csv

Lines changed: 36 additions & 50 deletions
Large diffs are not rendered by default.

crates/datadog-serverless-compat/src/main.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use zstd::zstd_safe::CompressionLevel;
1818

1919
use datadog_trace_agent::{
2020
aggregator::TraceAggregator,
21-
config, env_verifier, mini_agent, stats_flusher, stats_processor,
21+
config, env_verifier, mini_agent, proxy_flusher, stats_flusher, stats_processor,
2222
trace_flusher::{self, TraceFlusher},
2323
trace_processor,
2424
};
@@ -124,13 +124,16 @@ pub async fn main() {
124124
Arc::clone(&config),
125125
));
126126

127+
let proxy_flusher = Arc::new(proxy_flusher::ProxyFlusher::new(Arc::clone(&config)));
128+
127129
let mini_agent = Box::new(mini_agent::MiniAgent {
128130
config: Arc::clone(&config),
129131
env_verifier,
130132
trace_processor,
131133
trace_flusher,
132134
stats_processor,
133135
stats_flusher,
136+
proxy_flusher,
134137
});
135138

136139
tokio::spawn(async move {

crates/datadog-trace-agent/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ libdd-trace-protobuf = { git = "https://github.com/DataDog/libdatadog", rev = "4
2626
libdd-trace-utils = { git = "https://github.com/DataDog/libdatadog", rev = "435107c245112397914935c0f7148a18b91cafc6", features = [
2727
"mini_agent",
2828
] }
29+
datadog-fips = { path = "../datadog-fips" }
30+
reqwest = { version = "0.12.23", features = ["json", "http2"], default-features = false }
31+
bytes = "1.10.1"
2932

3033
[dev-dependencies]
3134
rmp-serde = "1.1.1"

crates/datadog-trace-agent/src/config.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,21 @@ pub struct Config {
8181
pub os: String,
8282
pub tags: Tags,
8383
/// how often to flush stats, in seconds
84-
pub stats_flush_interval: u64,
84+
pub stats_flush_interval_secs: u64,
8585
/// how often to flush traces, in seconds
86-
pub trace_flush_interval: u64,
86+
pub trace_flush_interval_secs: u64,
8787
pub trace_intake: Endpoint,
8888
pub trace_stats_intake: Endpoint,
89+
/// Profiling intake endpoint (for proxying profiling data to Datadog)
90+
pub profiling_intake: Endpoint,
91+
/// Timeout for each proxy request, in seconds
92+
pub proxy_request_timeout_secs: u64,
93+
/// Maximum number of retry attempts for failed proxy requests
94+
pub proxy_request_max_retries: u32,
95+
/// Base backoff duration for proxy request retries, in milliseconds
96+
pub proxy_request_retry_backoff_base_ms: u64,
8997
/// timeout for environment verification, in milliseconds
90-
pub verify_env_timeout: u64,
98+
pub verify_env_timeout_ms: u64,
9199
pub proxy_url: Option<String>,
92100
}
93101

@@ -119,6 +127,14 @@ impl Config {
119127
trace_stats_intake_url = trace_stats_url_prefixed(&endpoint_prefix);
120128
};
121129

130+
// TODO: Create helper functions for this in libdatadog
131+
let mut profiling_intake_url = format!("https://intake.profile.{}/api/v2/profile", dd_site);
132+
// DD_APM_PROFILING_DD_URL env var will primarily be used for integration tests
133+
// overrides the prefix of the profiling intake url
134+
if let Ok(endpoint_prefix) = env::var("DD_APM_PROFILING_DD_URL") {
135+
profiling_intake_url = format!("{endpoint_prefix}/api/v2/profile");
136+
};
137+
122138
let obfuscation_config = obfuscation_config::ObfuscationConfig::new().map_err(|err| {
123139
anyhow::anyhow!(
124140
"Error creating obfuscation config, Mini Agent will not start. Error: {err}",
@@ -137,9 +153,12 @@ impl Config {
137153
env_type,
138154
os: env::consts::OS.to_string(),
139155
max_request_content_length: 10 * 1024 * 1024, // 10MB in Bytes
140-
trace_flush_interval: 3,
141-
stats_flush_interval: 3,
142-
verify_env_timeout: 100,
156+
trace_flush_interval_secs: 3,
157+
stats_flush_interval_secs: 3,
158+
proxy_request_timeout_secs: 30,
159+
proxy_request_max_retries: 3,
160+
proxy_request_retry_backoff_base_ms: 100,
161+
verify_env_timeout_ms: 100,
143162
dd_dogstatsd_port,
144163
dd_site,
145164
trace_intake: Endpoint {
@@ -149,6 +168,11 @@ impl Config {
149168
},
150169
trace_stats_intake: Endpoint {
151170
url: hyper::Uri::from_str(&trace_stats_intake_url).unwrap(),
171+
api_key: Some(api_key.clone()),
172+
..Default::default()
173+
},
174+
profiling_intake: Endpoint {
175+
url: hyper::Uri::from_str(&profiling_intake_url).unwrap(),
152176
api_key: Some(api_key),
153177
..Default::default()
154178
},

crates/datadog-trace-agent/src/http_utils.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/
22
// SPDX-License-Identifier: Apache-2.0
33

4+
use core::time::Duration;
5+
use datadog_fips::reqwest_adapter::create_reqwest_client_builder;
46
use hyper::{
57
header,
68
http::{self, HeaderMap},
79
Response, StatusCode,
810
};
911
use libdd_common::hyper_migration;
1012
use serde_json::json;
13+
use std::error::Error;
1114
use tracing::{debug, error};
1215

1316
/// Does two things:
@@ -111,6 +114,19 @@ pub fn verify_request_content_length(
111114
None
112115
}
113116

117+
/// Builds a reqwest client with optional proxy configuration and timeout.
118+
/// Uses rustls TLS by default. FIPS-compliant TLS is available via the fips feature
119+
pub fn build_client(
120+
proxy_url: Option<&str>,
121+
timeout: Duration,
122+
) -> Result<reqwest::Client, Box<dyn Error>> {
123+
let mut builder = create_reqwest_client_builder()?.timeout(timeout);
124+
if let Some(proxy) = proxy_url {
125+
builder = builder.proxy(reqwest::Proxy::https(proxy)?);
126+
}
127+
Ok(builder.build()?)
128+
}
129+
114130
#[cfg(test)]
115131
mod tests {
116132
use http_body_util::BodyExt;

crates/datadog-trace-agent/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub mod config;
1212
pub mod env_verifier;
1313
pub mod http_utils;
1414
pub mod mini_agent;
15+
pub mod proxy_flusher;
1516
pub mod stats_flusher;
1617
pub mod stats_processor;
1718
pub mod trace_flusher;

crates/datadog-trace-agent/src/mini_agent.rs

Lines changed: 91 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/
22
// SPDX-License-Identifier: Apache-2.0
33

4+
use http_body_util::BodyExt;
45
use hyper::service::service_fn;
56
use hyper::{http, Method, Response, StatusCode};
67
use libdd_common::hyper_migration;
@@ -12,7 +13,8 @@ use std::time::Instant;
1213
use tokio::sync::mpsc::{self, Receiver, Sender};
1314
use tracing::{debug, error};
1415

15-
use crate::http_utils::log_and_create_http_response;
16+
use crate::http_utils::{log_and_create_http_response, verify_request_content_length};
17+
use crate::proxy_flusher::{ProxyFlusher, ProxyRequest};
1618
use crate::{config, env_verifier, stats_flusher, stats_processor, trace_flusher, trace_processor};
1719
use libdd_trace_protobuf::pb;
1820
use libdd_trace_utils::trace_utils;
@@ -22,8 +24,10 @@ const MINI_AGENT_PORT: usize = 8126;
2224
const TRACE_ENDPOINT_PATH: &str = "/v0.4/traces";
2325
const STATS_ENDPOINT_PATH: &str = "/v0.6/stats";
2426
const INFO_ENDPOINT_PATH: &str = "/info";
27+
const PROFILING_ENDPOINT_PATH: &str = "/profiling/v1/input";
2528
const TRACER_PAYLOAD_CHANNEL_BUFFER_SIZE: usize = 10;
2629
const STATS_PAYLOAD_CHANNEL_BUFFER_SIZE: usize = 10;
30+
const PROXY_PAYLOAD_CHANNEL_BUFFER_SIZE: usize = 10;
2731

2832
pub struct MiniAgent {
2933
pub config: Arc<config::Config>,
@@ -32,6 +36,7 @@ pub struct MiniAgent {
3236
pub stats_processor: Arc<dyn stats_processor::StatsProcessor + Send + Sync>,
3337
pub stats_flusher: Arc<dyn stats_flusher::StatsFlusher + Send + Sync>,
3438
pub env_verifier: Arc<dyn env_verifier::EnvVerifier + Send + Sync>,
39+
pub proxy_flusher: Arc<ProxyFlusher>,
3540
}
3641

3742
impl MiniAgent {
@@ -42,7 +47,7 @@ impl MiniAgent {
4247
let mini_agent_metadata = Arc::new(
4348
self.env_verifier
4449
.verify_environment(
45-
self.config.verify_env_timeout,
50+
self.config.verify_env_timeout_ms,
4651
&self.config.env_type,
4752
&self.config.os,
4853
)
@@ -82,6 +87,17 @@ impl MiniAgent {
8287
.await;
8388
});
8489

90+
// channels to send processed profiling requests to our proxy flusher
91+
let (proxy_tx, proxy_rx): (Sender<ProxyRequest>, Receiver<ProxyRequest>) =
92+
mpsc::channel(PROXY_PAYLOAD_CHANNEL_BUFFER_SIZE);
93+
94+
// start our proxy flusher for profiling requests
95+
let proxy_flusher = self.proxy_flusher.clone();
96+
tokio::spawn(async move {
97+
let proxy_flusher = proxy_flusher.clone();
98+
proxy_flusher.start_proxy_flusher(proxy_rx).await;
99+
});
100+
85101
// setup our hyper http server, where the endpoint_handler handles incoming requests
86102
let trace_processor = self.trace_processor.clone();
87103
let stats_processor = self.stats_processor.clone();
@@ -96,14 +112,17 @@ impl MiniAgent {
96112
let endpoint_config = endpoint_config.clone();
97113
let mini_agent_metadata = Arc::clone(&mini_agent_metadata);
98114

115+
let proxy_tx = proxy_tx.clone();
116+
99117
MiniAgent::trace_endpoint_handler(
100118
endpoint_config,
101119
req.map(hyper_migration::Body::incoming),
102-
trace_processor,
103-
trace_tx,
104-
stats_processor,
105-
stats_tx,
106-
mini_agent_metadata,
120+
trace_processor.clone(),
121+
trace_tx.clone(),
122+
stats_processor.clone(),
123+
stats_tx.clone(),
124+
Arc::clone(&mini_agent_metadata),
125+
proxy_tx.clone(),
107126
)
108127
});
109128

@@ -167,6 +186,7 @@ impl MiniAgent {
167186
stats_processor: Arc<dyn stats_processor::StatsProcessor + Send + Sync>,
168187
stats_tx: Sender<pb::ClientStatsPayload>,
169188
mini_agent_metadata: Arc<trace_utils::MiniAgentMetadata>,
189+
proxy_tx: Sender<ProxyRequest>,
170190
) -> http::Result<hyper_migration::HttpResponse> {
171191
match (req.method(), req.uri().path()) {
172192
(&Method::PUT | &Method::POST, TRACE_ENDPOINT_PATH) => {
@@ -190,6 +210,15 @@ impl MiniAgent {
190210
),
191211
}
192212
}
213+
(&Method::POST, PROFILING_ENDPOINT_PATH) => {
214+
match Self::profiling_proxy_handler(config, req, proxy_tx).await {
215+
Ok(res) => Ok(res),
216+
Err(err) => log_and_create_http_response(
217+
&format!("Error processing profiling request: {err}"),
218+
StatusCode::INTERNAL_SERVER_ERROR,
219+
),
220+
}
221+
}
193222
(_, INFO_ENDPOINT_PATH) => match Self::info_handler(config.dd_dogstatsd_port) {
194223
Ok(res) => Ok(res),
195224
Err(err) => log_and_create_http_response(
@@ -205,13 +234,67 @@ impl MiniAgent {
205234
}
206235
}
207236

237+
/// Handles incoming proxy requests for profiling - can be abstracted into a generic proxy handler for other proxy requests in the future
238+
async fn profiling_proxy_handler(
239+
config: Arc<config::Config>,
240+
request: hyper_migration::HttpRequest,
241+
proxy_tx: Sender<ProxyRequest>,
242+
) -> http::Result<hyper_migration::HttpResponse> {
243+
debug!("Trace Agent | Received profiling request");
244+
245+
// Extract headers and body
246+
let (parts, body) = request.into_parts();
247+
if let Some(response) = verify_request_content_length(
248+
&parts.headers,
249+
config.max_request_content_length,
250+
"Error processing profiling request",
251+
) {
252+
return response;
253+
}
254+
255+
let body_bytes = match body.collect().await {
256+
Ok(collected) => collected.to_bytes(),
257+
Err(e) => {
258+
return log_and_create_http_response(
259+
&format!("Error reading profiling request body: {e}"),
260+
StatusCode::BAD_REQUEST,
261+
);
262+
}
263+
};
264+
265+
// Create proxy request
266+
let proxy_request = ProxyRequest {
267+
headers: parts.headers,
268+
body: body_bytes,
269+
target_url: config.profiling_intake.url.to_string(),
270+
};
271+
272+
debug!(
273+
"Trace Agent | Sending profiling request to channel, target: {}",
274+
proxy_request.target_url
275+
);
276+
277+
// Send to channel
278+
match proxy_tx.send(proxy_request).await {
279+
Ok(_) => log_and_create_http_response(
280+
"Successfully buffered profiling request to be flushed",
281+
StatusCode::OK,
282+
),
283+
Err(err) => log_and_create_http_response(
284+
&format!("Error sending profiling request to the proxy flusher: {err}"),
285+
StatusCode::INTERNAL_SERVER_ERROR,
286+
),
287+
}
288+
}
289+
208290
fn info_handler(dd_dogstatsd_port: u16) -> http::Result<hyper_migration::HttpResponse> {
209291
let response_json = json!(
210292
{
211293
"endpoints": [
212294
TRACE_ENDPOINT_PATH,
213295
STATS_ENDPOINT_PATH,
214-
INFO_ENDPOINT_PATH
296+
INFO_ENDPOINT_PATH,
297+
PROFILING_ENDPOINT_PATH
215298
],
216299
"client_drop_p0s": true,
217300
"config": {

0 commit comments

Comments
 (0)