11use crate :: config;
22use std:: collections:: hash_map;
33use 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;
85use std:: sync:: Arc ;
6+ use std:: time:: Instant ;
97use tracing:: debug;
108
119// Environment variables for the Lambda execution environment info
@@ -21,8 +19,8 @@ pub const FUNCTION_ARN_KEY: &str = "function_arn";
2119const FUNCTION_NAME_KEY : & str = "functionname" ;
2220// ExecutedVersionKey is the tag key for a function's executed version
2321const 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
2725const 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
189219impl 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 = "\0 NAME =\" AmazonLinux\" \0 V=\" 2\0 AWS_EXECUTION_ENV=\" AWS_Lambda_java123\" \0 somethingelse=\" 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\" \n VERSION=\" 2\n PRETTY_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}
0 commit comments