Skip to content

Commit 7a51d82

Browse files
authored
Merge pull request #479 from pythonspeed/474-recent-versions-likely-have-source-code-less-jupyter-tracebacks
Recent versions likely have source-code-less Jupyter traceback
2 parents 0d58901 + 39b700c commit 7a51d82

File tree

12 files changed

+478
-310
lines changed

12 files changed

+478
-310
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Release notes
22

3+
## 2023.1.0 (2023-1-20)
4+
5+
### Bugfixes
6+
7+
- Fix regression where source code would sometimes be missing from flamegraphs, most notably in Jupyter profiling. ([#474](https://github.com/pythonspeed/filprofiler/issues/474))
8+
39
## 2022.11.0 (2022-11-07)
410

511
### Features

Cargo.lock

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

filpreload/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ path = "../memapi"
1818
features = []
1919

2020
[dependencies.pyo3]
21-
version = "0.17"
21+
version = "0.18"
2222
default-features = false
2323

2424
[build-dependencies]

filpreload/src/lib.rs

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
use parking_lot::Mutex;
33
use pymemprofile_api::memorytracking::LineNumberInfo::LineNumber;
44
use pymemprofile_api::memorytracking::{
5-
AllocationTracker, CallSiteId, Callstack, FunctionId, VecFunctionLocations, PARENT_PROCESS,
5+
AllocationTracker, CallSiteId, Callstack, FunctionId, IdentityCleaner, VecFunctionLocations,
6+
PARENT_PROCESS,
67
};
78
use pymemprofile_api::oom::{InfiniteMemory, OutOfMemoryEstimator, RealMemoryInfo};
89
use std::cell::RefCell;
910
use std::ffi::CStr;
1011
use std::os::raw::{c_char, c_int, c_void};
12+
use std::path::Path;
1113

1214
#[macro_use]
1315
extern crate lazy_static;
@@ -87,6 +89,7 @@ fn set_current_callstack(callstack: &Callstack) {
8789
}
8890

8991
extern "C" {
92+
fn _exit(exit_code: std::os::raw::c_int);
9093
fn free(address: *mut c_void);
9194
}
9295

@@ -152,7 +155,23 @@ fn add_allocation(
152155

153156
if oom {
154157
// Uh-oh, we're out of memory.
155-
allocations.oom_dump();
158+
eprintln!(
159+
"=fil-profile= We'll try to dump out SVGs. Note that no HTML file will be written."
160+
);
161+
let default_path = allocations.default_path.clone();
162+
// Release the lock, since dumping the flamegraph will reacquire it:
163+
drop(tracker_state);
164+
165+
dump_to_flamegraph(
166+
&default_path,
167+
false,
168+
"out-of-memory",
169+
"Current allocations at out-of-memory time",
170+
false,
171+
);
172+
unsafe {
173+
_exit(53);
174+
}
156175
};
157176
Ok(())
158177
}
@@ -180,11 +199,55 @@ fn reset(default_path: String) {
180199
tracker_state.allocations.reset(default_path);
181200
}
182201

202+
fn dump_to_flamegraph(
203+
path: &str,
204+
peak: bool,
205+
base_filename: &str,
206+
title: &str,
207+
to_be_post_processed: bool,
208+
) {
209+
// In order to render the flamegraph, we want to load source code using
210+
// Python's linecache. That means calling into Python, which might release
211+
// the GIL, allowing another thread to run, and it will try to allocation
212+
// and hit the TRACKER_STATE mutex. And now we're deadlocked. So we make
213+
// sure flamegraph rendering does not require TRACKER_STATE to be locked.
214+
let (allocated_bytes, flamegraph_callstacks) = {
215+
let mut tracker_state = TRACKER_STATE.lock();
216+
let allocations = &mut tracker_state.allocations;
217+
218+
// Print warning if we're missing allocations.
219+
allocations.warn_on_problems(peak);
220+
let allocated_bytes = if peak {
221+
allocations.get_peak_allocated_bytes()
222+
} else {
223+
allocations.get_current_allocated_bytes()
224+
};
225+
let flamegraph_callstacks = allocations.combine_callstacks(peak, IdentityCleaner);
226+
(allocated_bytes, flamegraph_callstacks)
227+
};
228+
229+
eprintln!("=fil-profile= Preparing to write to {}", path);
230+
let directory_path = Path::new(path);
231+
232+
let title = format!(
233+
"{} ({:.1} MiB)",
234+
title,
235+
allocated_bytes as f64 / (1024.0 * 1024.0)
236+
);
237+
let subtitle = r#"Made with the Fil profiler. <a href="https://pythonspeed.com/fil/" style="text-decoration: underline;" target="_parent">Try it on your code!</a>"#;
238+
flamegraph_callstacks.write_flamegraphs(
239+
directory_path,
240+
base_filename,
241+
&title,
242+
subtitle,
243+
"bytes",
244+
to_be_post_processed,
245+
)
246+
}
247+
183248
/// Dump all callstacks in peak memory usage to format used by flamegraph.
184249
fn dump_peak_to_flamegraph(path: &str) {
185-
let mut tracker_state = TRACKER_STATE.lock();
186-
let allocations = &mut tracker_state.allocations;
187-
allocations.dump_peak_to_flamegraph(path);
250+
dump_to_flamegraph(path, true, "peak-memory", "Peak Tracked Memory Usage", true);
188251
}
189252

190253
#[no_mangle]
@@ -318,8 +381,8 @@ extern "C" {
318381
fn is_initialized() -> c_int;
319382

320383
// Increment/decrement reentrancy counter.
321-
fn fil_increment_reentrancy();
322-
fn fil_decrement_reentrancy();
384+
//fn fil_increment_reentrancy();
385+
//fn fil_decrement_reentrancy();
323386
}
324387

325388
struct FilMmapAPI;
@@ -337,7 +400,7 @@ impl pymemprofile_api::mmap::MmapAPI for FilMmapAPI {
337400
}
338401

339402
fn is_initialized(&self) -> bool {
340-
return unsafe { is_initialized() == 1 };
403+
unsafe { is_initialized() == 1 }
341404
}
342405
}
343406

filprofiler/_report.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,19 +76,16 @@ def render_report(output_path: str, now: datetime) -> str:
7676
7777
<h2>Profiling result</h2>
7878
<div style="text-align: center;"><p><input type="button" onclick="fullScreen('#peak');" value="Full screen"> · <a href="peak-memory.svg" target="_blank"><button>Open in new window</button></a></p>
79-
<iframe id="peak" src="peak-memory.svg" width="100%" height="400" scrolling="auto" frameborder="0"></iframe>
79+
<iframe id="peak" src="peak-memory.svg" width="100%" height="700" scrolling="auto" frameborder="0"></iframe>
8080
</div>
8181
<br>
8282
<blockquote class="center">
83-
<p style="text-align: center;"><em>Check out my other project:</em></p>
84-
<h3>Find memory and performance bottlenecks in production!</h3>
85-
<p>When your data pipeline is too slow in production, reproducing the problem
86-
on your laptop is hard or impossible—which means identifying and fixing the problem can be tricky.</p>
87-
<p>What if you knew the cause of the problem as soon as you realized it was happening?</p>
88-
<p>That's how
89-
<strong><a href="https://sciagraph.com/">the Sciagraph profiler</a></strong> can help you:
90-
it's designed to <strong>find performance
91-
and memory bottlenecks by continuously profiling in production.</strong></p></blockquote>
83+
<h3>Find performance bottlenecks in your data processing jobs with the Sciagraph profiler</h3>
84+
<p><strong><a href="https://sciagraph.com/">The Sciagraph profiler</a></strong> can help you
85+
<strong>find performance
86+
and memory bottlenecks with low overhead, so you can use it in both development and production.</strong></p>
87+
<p>Unlike Fil, it includes performance profiling. Sciagraph's memory profiling uses sampling so it runs faster than Fil, but unlike Fil
88+
it can't accurately profile small allocations or run natively on macOS.</p></blockquote>
9289
<br>
9390
<br>
9491
<div style="text-align: center;"><p><input type="button" onclick="fullScreen('#peak-reversed');" value="Full screen"> ·

memapi/Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "pymemprofile_api"
33
version = "0.1.0"
44
authors = ["Itamar Turner-Trauring <[email protected]>"]
5-
edition = "2018"
5+
edition = "2021"
66
license = "Apache-2.0"
77

88
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -18,6 +18,7 @@ once_cell = "1.17"
1818
libloading = "0.7"
1919
libc = "0.2"
2020
serde = {version = "1", features = ["derive"] }
21+
parking_lot = "0.12.1"
2122

2223
[dependencies.inferno]
2324
version = "0.11"
@@ -29,9 +30,7 @@ default-features = false
2930
features = ["memory", "process"]
3031

3132
[dependencies.pyo3]
32-
version = "0.17"
33-
default-features = false
34-
features = []
33+
version = "0.18"
3534

3635
[target.'cfg(target_os = "linux")'.dependencies]
3736
cgroups-rs = "0.3.0"
@@ -40,8 +39,9 @@ cgroups-rs = "0.3.0"
4039
proptest = "1.0"
4140
proc-maps = "0.3.0"
4241
tempfile = "3.3.0"
42+
rusty-fork = "0.3.0"
4343

4444
[features]
4545
default = []
4646
# Optimize for the production version of Fil.
47-
fil4prod = []
47+
fil4prod = []

0 commit comments

Comments
 (0)