diff --git a/Project.toml b/Project.toml index 288d5c4..0b9aaf6 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "BugReporting" uuid = "bcf9a6e7-4020-453c-b88e-690564246bb8" authors = ["Keno Fischer ", "Tim Besard "] -version = "0.3.0" +version = "0.3.1" [deps] Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" diff --git a/README.md b/README.md index 66a19d7..ec8f7c8 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,15 @@ Run `julia` inside `rr record` but do not upload the recorded trace. Useful for Generate a bug report, but limit the execution time of the debugged process to `SECONDS` seconds. This is useful for generating reports for hangs. +### `--bug-report=rr,chaos` + +Generate an rr trace, while enabling so-called chaos mode. This is useful for flushing +out threading-related issues (refer to the rr documentation for more details). + ## Using the traces for local debugging You can use this package also for debugging your own Julia code locally. Use `--bug-report=rr-local` -to record a trace, and `replay(PATH)` to replay a trace. +to record a trace, and `replay()` to replay the latest trace. For example, if you have a script in a project that you'd like to trace, run `julia --bug-report=rr -- --project=foo run.jl`. diff --git a/src/BugReporting.jl b/src/BugReporting.jl index a486c53..9c9849f 100644 --- a/src/BugReporting.jl +++ b/src/BugReporting.jl @@ -148,7 +148,7 @@ function compress_trace(trace_directory::String, output_file::String) end function rr_record(julia_cmd::Cmd, julia_args...; rr_flags=default_rr_record_flags, - trace_dir=default_rr_trace_dir(), timeout=0, extras=false) + trace_dir=default_rr_trace_dir(), timeout=0, chaos=false, extras=false) check_rr_available() check_perf_event_paranoid(; rr_flags=rr_flags) @@ -159,6 +159,10 @@ function rr_record(julia_cmd::Cmd, julia_args...; rr_flags=default_rr_record_fla # and shouldn't leak into the record environment (where we may load another Python) delete!(new_env, "PYTHONHOME") + if chaos + rr_flags = `--chaos $rr_flags` + end + proc = rr() do rr_path rr_cmd = `$(rr_path) record $rr_flags $julia_cmd $julia_args` cmd = ignorestatus(setenv(rr_cmd, new_env)) @@ -293,17 +297,16 @@ function get_sourcecode(commit) return dir end -function replay(trace_url=default_rr_trace_dir(); gdb_commands=[], gdb_flags=``, - rr_replay_flags=``) +function get_trace_dir(trace_url) # download remote traces if startswith(trace_url, "s3://") trace_url = string("https://s3.amazonaws.com/julialang-dumps/", trace_url[6:end]) end if startswith(trace_url, "http://") || startswith(trace_url, "https://") trace_url = download_rr_trace(trace_url) - rr_replay_flags = `$rr_replay_flags --serve-files` - # for remote traces, we assume it originated on a different system, so we need to - # tell rr to serve files as it's unlikely they will be available locally. + remote = true + else + remote = false end # If it's a file, try to decompress it @@ -315,7 +318,24 @@ function replay(trace_url=default_rr_trace_dir(); gdb_commands=[], gdb_flags=``, if !isdir(trace_url) error("Invalid trace location: $(trace_url)") end - trace_dir = find_latest_trace(trace_url) + + (; dir=find_latest_trace(trace_url), remote=remote) +end + +function read_trace_info(trace_url=default_rr_trace_dir();) + trace_dir, _ = get_trace_dir(trace_url) + json = read(`$(rr()) traceinfo $trace_dir`, String) + JSON.parse(json) +end + +function replay(trace_url=default_rr_trace_dir(); gdb_commands=[], gdb_flags=``, + rr_replay_flags=``) + trace_dir, remote = get_trace_dir(trace_url) + if remote + rr_replay_flags = `$rr_replay_flags --serve-files` + # for remote traces, we assume it originated on a different system, so we need to + # tell rr to serve files as it's unlikely they will be available locally. + end # read Julia-specific metadata from the trace if ispath(joinpath(trace_dir, "julia_metadata.json")) @@ -483,12 +503,17 @@ function make_interactive_report(report_arg, ARGS=[]) # split the report specification into the type and any modifiers report_type, report_modifiers... = split(report_arg, ',') timeout = 0 + chaos = false for report_modifier in report_modifiers - option, value = split(report_modifier, '=') + option, values... = split(report_modifier, '=') if option == "timeout" - timeout = parse_time(value) + length(values) == 1 || error("Invalid report option: $(report_modifier)") + timeout = parse_time(values[1]) + elseif option == "chaos" + length(values) == 0 || error("Invalid report option: $(report_modifier)") + chaos = true else - error("Unknown report option: $(option)") + error("Unknown report option: $(report_modifier)") end end @@ -508,11 +533,11 @@ function make_interactive_report(report_arg, ARGS=[]) cmd = `$cmd --history-file=no` if report_type == "rr-local" - proc = rr_record(cmd, ARGS...; timeout=timeout, extras=true) + proc = rr_record(cmd, ARGS...; timeout=timeout, chaos=chaos, extras=true) handle_child_error(proc) elseif report_type == "rr" mktempdir() do trace_dir - proc = rr_record(cmd, ARGS...; trace_dir=trace_dir, timeout=timeout, extras=true) + proc = rr_record(cmd, ARGS...; trace_dir=trace_dir, timeout=timeout, chaos=chaos, extras=true) "Preparing trace for upload (if your trace is large this may take a few minutes)..." rr_pack(trace_dir) params = get_upload_params() diff --git a/test/rr.jl b/test/rr.jl index de71832..bf039c5 100644 --- a/test/rr.jl +++ b/test/rr.jl @@ -227,4 +227,19 @@ end @test success(proc) @test contains(output.stdout, "Starting sleep") end + + # Test that we can enable chaos mode + mktempdir() do temp_trace_dir + proc, output = withenv("_RR_TRACE_DIR" => temp_trace_dir) do + cmd = ```$(Base.julia_cmd()) --project=$(dirname(@__DIR__)) + --bug-report=rr-local,chaos + --eval "42"``` + communicate(cmd) + end + success(proc) || println(output.stderr) + @test success(proc) + + trace_info = BugReporting.read_trace_info(temp_trace_dir) + @test get(trace_info, "chaosMode", missing) == true + end end