Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add environment variable for tracking child processes #84

Merged
merged 2 commits into from
Aug 16, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,10 @@ Whenever to use a more intrusive, faster unwinding algorithm; enabled by default

Setting it to `0` will on average significantly slow down unwinding. This option
is provided only for debugging purposes.

### `MEMORY_PROFILER_TRACK_CHILD_PROCESSES`

*Default: `0`*

If set to `1`, bytehound will also track the memory allocations of child processes spawned by the
profiled process.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explicitly also document that:

  1. this only applies to child processes which are forkd and execd,
  2. that the MEMORY_PROFILER_OUTPUT needs to be set with the appropriate placeholders so that the filenames of both the parent and the child are different

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

29 changes: 29 additions & 0 deletions integration-tests/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1278,3 +1278,32 @@ fn test_cross_thread_alloc_non_culled() {

assert!( iter.next().is_none() );
}

#[test]
fn test_track_spawned_children() {
let cwd = workdir();

compile_with_flags( "spawn-child.c", &["-o", "spawn-child"] );

run_on_target(
&cwd,
"./spawn-child",
EMPTY_ARGS,
&[
("LD_PRELOAD", preload_path().into_os_string()),
("MEMORY_PROFILER_LOG", "debug".into()),
("MEMORY_PROFILER_TRACK_CHILD_PROCESSES", "1".into())
]
).assert_success();

eprintln!("cwd: {}", cwd.display());

for entry in std::fs::read_dir(cwd).unwrap() {
let entry = entry.unwrap();
if entry.file_name().to_str().unwrap().starts_with("memory-profiling_ls") {
return;
}
}

panic!("Memory profiling file for spawned process not found");
}
22 changes: 22 additions & 0 deletions integration-tests/test-programs/spawn-child.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
usleep( 100000 );
malloc( 10001 );

pid_t pid = fork();
if( pid == 0 ) {
// Child
if (execl("/usr/bin/ls", "/usr/bin/ls", NULL) == -1) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use the basic.c program here instead and in the integration test verify that:

  1. the file for the parent is created and contains the allocations we expect,
  2. the file for the child is created and contains the allocations we expect (you can just grab the checks from #[test] for the basic.c, move it into a separate functions, and then call it in two places)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I changed the test to incorporate the basic program.

return 1;
}
return 0;
}

waitpid(pid, NULL, 0);
malloc( 10003 );

return 0;
}
6 changes: 4 additions & 2 deletions preload/src/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::thread;
use crate::arc_lite::ArcLite;
use crate::event::{InternalAllocationId, InternalEvent, send_event};
use crate::spin_lock::{SpinLock, SpinLockGuard};
use crate::syscall;
use crate::{opt, syscall};
use crate::unwind::{ThreadUnwindState, prepare_to_start_unwinding};
use crate::timestamp::Timestamp;
use crate::allocation_tracker::AllocationTracker;
Expand Down Expand Up @@ -553,7 +553,9 @@ fn initialize_stage_2() {
crate::init::initialize_atexit_hook();
crate::init::initialize_signal_handlers();

std::env::remove_var( "LD_PRELOAD" );
if !opt::get().track_child_processes {
std::env::remove_var( "LD_PRELOAD" );
}

info!( "Stage 2 initialization finished" );
}
Expand Down
8 changes: 6 additions & 2 deletions preload/src/opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ pub struct Opts {
pub backtrace_cache_size_level_2: usize,
pub cull_temporary_allocations: bool,
pub temporary_allocation_lifetime_threshold: u64,
pub temporary_allocation_pending_threshold: Option< usize >
pub temporary_allocation_pending_threshold: Option< usize >,
pub track_child_processes: bool
}

static mut OPTS: Opts = Opts {
Expand All @@ -48,6 +49,7 @@ static mut OPTS: Opts = Opts {
cull_temporary_allocations: false,
temporary_allocation_lifetime_threshold: 10000,
temporary_allocation_pending_threshold: None,
track_child_processes: false
};

trait ParseVar: Sized {
Expand Down Expand Up @@ -143,7 +145,9 @@ pub unsafe fn initialize() {
"MEMORY_PROFILER_TEMPORARY_ALLOCATION_LIFETIME_THRESHOLD"
=> &mut opts.temporary_allocation_lifetime_threshold,
"MEMORY_PROFILER_TEMPORARY_ALLOCATION_PENDING_THRESHOLD"
=> &mut opts.temporary_allocation_pending_threshold
=> &mut opts.temporary_allocation_pending_threshold,
"MEMORY_PROFILER_TRACK_CHILD_PROCESSES"
=> &mut opts.track_child_processes
}

opts.is_initialized = true;
Expand Down