Skip to content

Commit e1a622f

Browse files
authored
fix: use /proc/pid/environ to read the env variable for runtime (#330)
* fix: use /proc/pid/environ to read the env variable for runtime * test: add simple runtime test case * fix: scan all the proc files * style: clippy * refactor: disable runtime tag sicne it's not yet reliable
1 parent 3bf0712 commit e1a622f

File tree

4 files changed

+105
-62
lines changed

4 files changed

+105
-62
lines changed

bottlecap-run/bottlecap_dev.sh

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,13 @@ all(){
9696
time build_dockerized
9797
version_nbr=$(publish)
9898
update "$version_nbr"
99-
invoke
99+
# invoke
100100
}
101101

102102
format () {
103103
cd ../bottlecap
104104
cargo fmt --all
105-
cargo clippy --workspace --all-features
106-
cargo clippy --fix
105+
cargo clippy --workspace --all-features --fix
107106
cd -
108107
}
109108

bottlecap/src/metrics/aggregator.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,8 @@ mod tests {
429429
use std::collections::hash_map;
430430
use std::sync::Arc;
431431

432-
const SINGLE_METRIC_SIZE: usize = 205;
433-
const SINGLE_DISTRIBUTION_SIZE: u64 = 152;
432+
const SINGLE_METRIC_SIZE: usize = 187;
433+
const SINGLE_DISTRIBUTION_SIZE: u64 = 135;
434434

435435
fn create_tags_provider() -> Arc<provider::Provider> {
436436
let config = Arc::new(config::Config::default());
@@ -736,7 +736,7 @@ mod tests {
736736
fn consume_metrics_batch_bytes() {
737737
let expected_metrics_per_batch = 2;
738738
let total_number_of_metrics = 5;
739-
let two_metrics_size = 398;
739+
let two_metrics_size = 362;
740740
let max_bytes = SINGLE_METRIC_SIZE * expected_metrics_per_batch + 13;
741741
let mut aggregator = Aggregator {
742742
tags_provider: create_tags_provider(),

bottlecap/src/tags/lambda/tags.rs

Lines changed: 99 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
use crate::config;
22
use std::collections::hash_map;
33
use std::env::consts::ARCH;
4-
use std::fs::File;
5-
use std::io;
6-
use std::io::BufRead;
7-
use std::path::Path;
4+
use std::fs;
85
use std::sync::Arc;
6+
use std::time::Instant;
97
use tracing::debug;
108

119
// Environment variables for the Lambda execution environment info
@@ -21,8 +19,8 @@ pub const FUNCTION_ARN_KEY: &str = "function_arn";
2119
const FUNCTION_NAME_KEY: &str = "functionname";
2220
// ExecutedVersionKey is the tag key for a function's executed version
2321
const EXECUTED_VERSION_KEY: &str = "executedversion";
24-
// RuntimeKey is the tag key for a function's runtime (e.g node, python)
25-
const RUNTIME_KEY: &str = "runtime";
22+
// RuntimeKey is the tag key for a function's runtime (e.g. node, python)
23+
// const RUNTIME_KEY: &str = "runtime";
2624
// MemorySizeKey is the tag key for a function's allocated memory size
2725
const MEMORY_SIZE_KEY: &str = "memorysize";
2826
// TODO(astuyve): fetch architecture from the runtime
@@ -118,16 +116,11 @@ fn tags_from_env(
118116
if let Ok(memory_size) = std::env::var(MEMORY_SIZE_VAR) {
119117
tags_map.insert(MEMORY_SIZE_KEY.to_string(), memory_size);
120118
}
121-
if let Ok(exec_runtime_env) = std::env::var(RUNTIME_VAR) {
122-
// AWS_Lambda_java8
123-
let runtime = exec_runtime_env.split('_').last().unwrap_or("unknown");
124-
tags_map.insert(RUNTIME_KEY.to_string(), runtime.to_string());
125-
} else {
126-
tags_map.insert(
127-
RUNTIME_KEY.to_string(),
128-
resolve_provided_runtime("/etc/os-release"),
129-
);
130-
}
119+
120+
let runtime = resolve_runtime("/proc", "/etc/os-release");
121+
// TODO runtime resolution is too fast, need to change approach. Resolving it anyway to get debug info and performance of resolution
122+
debug!("Resolved runtime: {runtime}. Not adding to tags yet");
123+
// tags_map.insert(RUNTIME_KEY.to_string(), runtime);
131124

132125
tags_map.insert(ARCHITECTURE_KEY.to_string(), arch_to_platform().to_string());
133126
tags_map.insert(
@@ -151,39 +144,76 @@ fn tags_from_env(
151144
tags_map
152145
}
153146

154-
fn resolve_provided_runtime(path: &str) -> String {
155-
let path = Path::new(path);
147+
fn resolve_runtime(proc_path: &str, fallback_provided_al_path: &str) -> String {
148+
let start = Instant::now();
149+
match fs::read_dir(proc_path) {
150+
Ok(proc_dir) => {
151+
let search_environ_runtime = proc_dir
152+
.filter_map(Result::ok)
153+
.filter(|entry| {
154+
entry.path().is_dir()
155+
&& entry
156+
.file_name()
157+
.into_string()
158+
.ok()
159+
.is_some_and(|pid_folder| pid_folder.chars().all(char::is_numeric))
160+
})
161+
.filter(|pid_folder| pid_folder.file_name().ne("1"))
162+
.filter_map(|pid_folder| fs::read(pid_folder.path().join("environ")).ok())
163+
.find(|environ_bytes| {
164+
String::from_utf8(environ_bytes.clone())
165+
.map(|s| s.contains(RUNTIME_VAR))
166+
.unwrap_or(false)
167+
})
168+
.and_then(|runtime_byte_strings| {
169+
runtime_byte_strings
170+
.split(|byte| *byte == b'\0')
171+
.filter_map(|s| String::from_utf8(s.to_vec()).ok())
172+
.find(|line| line.contains(RUNTIME_VAR))
173+
.and_then(|runtime_var_line| {
174+
// AWS_EXECUTION_ENV=AWS_Lambda_java8
175+
runtime_var_line.split('_').last().map(String::from)
176+
})
177+
});
156178

157-
let file = match File::open(path) {
158-
Err(why) => {
159-
debug!(
160-
"Couldn't read provided runtime. Cannot read: {}. Returning unknown",
161-
why
162-
);
163-
return "unknown".to_string();
179+
let search_time = start.elapsed().as_micros().to_string();
180+
if let Some(runtime_from_environ) = search_environ_runtime {
181+
debug!("Proc runtime search successful in {search_time}us: {runtime_from_environ}");
182+
return runtime_from_environ.replace('\"', "");
183+
};
184+
debug!("Proc runtime search unsuccessful after {search_time}us");
164185
}
165-
Ok(file) => file,
166-
};
167-
let reader = io::BufReader::new(file);
168-
for line in reader.lines().map_while(Result::ok) {
169-
if line.starts_with("PRETTY_NAME=") {
170-
let parts: Vec<&str> = line.split('=').collect();
171-
if parts.len() > 1 {
172-
match parts[1]
173-
.split(' ')
174-
.last()
175-
.unwrap_or("")
176-
.replace('\"', "")
177-
.as_str()
178-
{
179-
"2" => return "provided.al2".to_string(),
180-
s if s.starts_with("2023") => return "provided.al2023".to_string(),
181-
_ => break,
182-
}
183-
}
186+
Err(e) => {
187+
debug!("Could not resolve runtime {e}");
184188
}
185189
}
186-
"unknown".to_string()
190+
191+
debug!("Checking '{fallback_provided_al_path}' for provided_al");
192+
let start = Instant::now();
193+
194+
let provided_al = fs::read_to_string(fallback_provided_al_path)
195+
.ok()
196+
.and_then(|fallback_provided_al_content| {
197+
fallback_provided_al_content
198+
.lines()
199+
.find(|line| line.starts_with("PRETTY_NAME="))
200+
.and_then(
201+
|pretty_name_line| match pretty_name_line.replace('\"', "").as_str() {
202+
"PRETTY_NAME=Amazon Linux 2" => Some("provided.al2".to_string()),
203+
s if s.starts_with("PRETTY_NAME=Amazon Linux 2023") => {
204+
Some("provided.al2023".to_string())
205+
}
206+
_ => None,
207+
},
208+
)
209+
})
210+
.unwrap_or_else(|| "unknown".to_string());
211+
212+
debug!(
213+
"Provided runtime {provided_al}, it took: {:?}",
214+
start.elapsed()
215+
);
216+
provided_al
187217
}
188218

189219
impl Lambda {
@@ -222,13 +252,15 @@ mod tests {
222252
use super::*;
223253
use crate::config::Config;
224254
use serial_test::serial;
255+
use std::fs::File;
225256
use std::io::Write;
257+
use std::path::Path;
226258

227259
#[test]
228260
fn test_new_from_config() {
229261
let metadata = hash_map::HashMap::new();
230-
let tags = Lambda::new_from_config(Arc::new(config::Config::default()), &metadata);
231-
assert_eq!(tags.tags_map.len(), 4);
262+
let tags = Lambda::new_from_config(Arc::new(Config::default()), &metadata);
263+
assert_eq!(tags.tags_map.len(), 3);
232264
assert_eq!(
233265
tags.tags_map.get(COMPUTE_STATS_KEY).unwrap(),
234266
COMPUTE_STATS_VALUE
@@ -238,7 +270,6 @@ mod tests {
238270
tags.tags_map.get(ARCHITECTURE_KEY).unwrap(),
239271
&arch.to_string()
240272
);
241-
assert_eq!(tags.tags_map.get(RUNTIME_KEY).unwrap(), "unknown");
242273

243274
assert_eq!(
244275
tags.tags_map.get(EXTENSION_VERSION_KEY).unwrap(),
@@ -253,7 +284,7 @@ mod tests {
253284
FUNCTION_ARN_KEY.to_string(),
254285
"arn:aws:lambda:us-west-2:123456789012:function:my-function".to_string(),
255286
);
256-
let tags = Lambda::new_from_config(Arc::new(config::Config::default()), &metadata);
287+
let tags = Lambda::new_from_config(Arc::new(Config::default()), &metadata);
257288
assert_eq!(tags.tags_map.get(REGION_KEY).unwrap(), "us-west-2");
258289
assert_eq!(tags.tags_map.get(ACCOUNT_ID_KEY).unwrap(), "123456789012");
259290
assert_eq!(tags.tags_map.get(AWS_ACCOUNT_KEY).unwrap(), "123456789012");
@@ -295,15 +326,30 @@ mod tests {
295326
);
296327
}
297328

329+
#[test]
330+
fn test_resolve_runtime() {
331+
let proc_id_folder = Path::new("/tmp/test-bottlecap/proc_root/123");
332+
fs::create_dir_all(proc_id_folder).unwrap();
333+
let path = proc_id_folder.join("environ");
334+
let content = "\0NAME =\"AmazonLinux\"\0V=\"2\0AWS_EXECUTION_ENV=\"AWS_Lambda_java123\"\0somethingelse=\"abd\0\"";
335+
336+
let mut file = File::create(&path).unwrap();
337+
file.write_all(content.as_bytes()).unwrap();
338+
339+
let runtime = resolve_runtime(proc_id_folder.parent().unwrap().to_str().unwrap(), "");
340+
fs::remove_file(path).unwrap();
341+
assert_eq!(runtime, "java123");
342+
}
343+
298344
#[test]
299345
fn test_resolve_provided_al2() {
300346
let path = "/tmp/test-os-release1";
301347
let content = "NAME =\"Amazon Linux\"\nVERSION=\"2\nPRETTY_NAME=\"Amazon Linux 2\"";
302348
let mut file = File::create(path).unwrap();
303349
file.write_all(content.as_bytes()).unwrap();
304350

305-
let runtime = resolve_provided_runtime(path);
306-
std::fs::remove_file(path).unwrap();
351+
let runtime = resolve_runtime("", path);
352+
fs::remove_file(path).unwrap();
307353
assert_eq!(runtime, "provided.al2");
308354
}
309355

@@ -315,8 +361,8 @@ mod tests {
315361
let mut file = File::create(path).unwrap();
316362
file.write_all(content.as_bytes()).unwrap();
317363

318-
let runtime = resolve_provided_runtime(path);
319-
std::fs::remove_file(path).unwrap();
364+
let runtime = resolve_runtime("", path);
365+
fs::remove_file(path).unwrap();
320366
assert_eq!(runtime, "provided.al2023");
321367
}
322368
}

bottlecap/tests/metrics_integration_test.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ async fn test_enhanced_metrics() {
2222
let regexp_compute_state = r#"_dd.compute_stats:1"#;
2323
let regexp_arch = r#"architecture:x86_64"#;
2424
let regexp_function_arn = r#"function_arn:test-arn"#;
25-
let regexp_runtime = r#"runtime:unknown"#;
2625

2726
let server = MockServer::start();
2827
let hello_mock = server.mock(|when, then| {
@@ -33,8 +32,7 @@ async fn test_enhanced_metrics() {
3332
.body_contains(regexp_metric_name)
3433
.body_contains(regexp_compute_state)
3534
.body_contains(regexp_arch)
36-
.body_contains(regexp_function_arn)
37-
.body_contains(regexp_runtime);
35+
.body_contains(regexp_function_arn);
3836
then.status(reqwest::StatusCode::ACCEPTED.as_u16());
3937
});
4038

0 commit comments

Comments
 (0)