functiontrace 0.5.3__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {functiontrace-0.5.3 → functiontrace-0.6.0}/Cargo.toml +2 -2
- {functiontrace-0.5.3/functiontrace.egg-info → functiontrace-0.6.0}/PKG-INFO +1 -1
- {functiontrace-0.5.3 → functiontrace-0.6.0/functiontrace.egg-info}/PKG-INFO +1 -1
- {functiontrace-0.5.3 → functiontrace-0.6.0}/pyproject.toml +1 -1
- {functiontrace-0.5.3 → functiontrace-0.6.0}/rust/lib.rs +295 -321
- {functiontrace-0.5.3 → functiontrace-0.6.0}/LICENSE.md +0 -0
- {functiontrace-0.5.3 → functiontrace-0.6.0}/MANIFEST.in +0 -0
- {functiontrace-0.5.3 → functiontrace-0.6.0}/README.rst +0 -0
- {functiontrace-0.5.3 → functiontrace-0.6.0}/build.rs +0 -0
- {functiontrace-0.5.3 → functiontrace-0.6.0}/functiontrace.egg-info/SOURCES.txt +0 -0
- {functiontrace-0.5.3 → functiontrace-0.6.0}/functiontrace.egg-info/dependency_links.txt +0 -0
- {functiontrace-0.5.3 → functiontrace-0.6.0}/functiontrace.egg-info/entry_points.txt +0 -0
- {functiontrace-0.5.3 → functiontrace-0.6.0}/functiontrace.egg-info/top_level.txt +0 -0
- {functiontrace-0.5.3 → functiontrace-0.6.0}/functiontrace.py +0 -0
- {functiontrace-0.5.3 → functiontrace-0.6.0}/rust/multiprocessing.sh.tpl +0 -0
- {functiontrace-0.5.3 → functiontrace-0.6.0}/setup.cfg +0 -0
- {functiontrace-0.5.3 → functiontrace-0.6.0}/setup.py +0 -0
|
@@ -10,7 +10,7 @@ functiontrace-server = { version = "0.8.4", default-features = false, features =
|
|
|
10
10
|
jiff = { version = "0.2", default-features = false, features = ["std", "serde"] }
|
|
11
11
|
libc = "0.2"
|
|
12
12
|
owo-colors = "4.2"
|
|
13
|
-
pyo3 = { version = "0.
|
|
13
|
+
pyo3 = { version = "0.30", features = ["eyre"] }
|
|
14
14
|
reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "rustls"] }
|
|
15
15
|
rmp-serde = "1.3"
|
|
16
16
|
serde = "1"
|
|
@@ -20,7 +20,7 @@ tracing-subscriber = { version = "0.3", default-features = false, features = ["a
|
|
|
20
20
|
|
|
21
21
|
[build-dependencies]
|
|
22
22
|
toml = { version = "1.1", default-features = false, features = ["parse", "serde"] }
|
|
23
|
-
pyo3-build-config = { version = "0.
|
|
23
|
+
pyo3-build-config = { version = "0.30" }
|
|
24
24
|
|
|
25
25
|
[lib]
|
|
26
26
|
# See https://github.com/PyO3/pyo3 for details
|
|
@@ -6,7 +6,7 @@ mod _functiontrace_rs {
|
|
|
6
6
|
use pyo3::prelude::*;
|
|
7
7
|
use serde::{Deserialize, Serialize};
|
|
8
8
|
use std::borrow::Cow;
|
|
9
|
-
use std::cell::
|
|
9
|
+
use std::cell::RefCell;
|
|
10
10
|
use std::ffi::{CStr, OsString};
|
|
11
11
|
use std::io::Write;
|
|
12
12
|
use std::os::unix::net::UnixStream;
|
|
@@ -50,7 +50,7 @@ mod _functiontrace_rs {
|
|
|
50
50
|
/// once full.
|
|
51
51
|
fn write(&mut self, src: &[u8]) -> std::io::Result<usize> {
|
|
52
52
|
if !STARTED.load(Ordering::Relaxed) {
|
|
53
|
-
// We haven't started yet, so we shouldn't write anything out.
|
|
53
|
+
// We haven't started yet, so we shouldn't write anything out. This avoids logging
|
|
54
54
|
// various internal setup things before we're ready.
|
|
55
55
|
return Ok(src.len());
|
|
56
56
|
}
|
|
@@ -58,8 +58,8 @@ mod _functiontrace_rs {
|
|
|
58
58
|
let bytes = src.len();
|
|
59
59
|
let tail = self.head + bytes;
|
|
60
60
|
|
|
61
|
-
if tail
|
|
62
|
-
if bytes
|
|
61
|
+
if tail > self.buffer.len() {
|
|
62
|
+
if bytes > self.buffer.len() {
|
|
63
63
|
// We'll never be able to fit this much.
|
|
64
64
|
return Err(std::io::Error::new(
|
|
65
65
|
std::io::ErrorKind::StorageFull,
|
|
@@ -67,7 +67,7 @@ mod _functiontrace_rs {
|
|
|
67
67
|
));
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
// We can't fit any more data, so need to flush.
|
|
70
|
+
// We can't fit any more data, so need to flush. The caller should retry after
|
|
71
71
|
// this.
|
|
72
72
|
self.flush()?;
|
|
73
73
|
|
|
@@ -85,13 +85,25 @@ mod _functiontrace_rs {
|
|
|
85
85
|
|
|
86
86
|
/// Flush our local buffer to the socket.
|
|
87
87
|
fn flush(&mut self) -> std::io::Result<()> {
|
|
88
|
-
// We're about to send all the data, so reset our buffer.
|
|
88
|
+
// We're about to send all the data, so reset our buffer. Do this now to ensure we
|
|
89
89
|
// don't miss it, since the buffer will be left in an invalid state if this doesn't
|
|
90
90
|
// happen.
|
|
91
|
-
let head = self.head;
|
|
92
|
-
self.head = 0;
|
|
91
|
+
let head = std::mem::take(&mut self.head);
|
|
93
92
|
|
|
94
|
-
|
|
93
|
+
if head != 0 {
|
|
94
|
+
self.socket.write_all(&self.buffer[..head])
|
|
95
|
+
} else {
|
|
96
|
+
Ok(())
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
impl Drop for ThreadState {
|
|
102
|
+
// Attempt to flush the buffer on drop. This should be run whenever TLS is torn down for
|
|
103
|
+
// threads, which should allow us to collect data from short-lived threads that don't call
|
|
104
|
+
// `thread_teardown` themselves.
|
|
105
|
+
fn drop(&mut self) {
|
|
106
|
+
let _ = self.flush();
|
|
95
107
|
}
|
|
96
108
|
}
|
|
97
109
|
|
|
@@ -105,16 +117,15 @@ mod _functiontrace_rs {
|
|
|
105
117
|
/// `sys.argv` for our process, which is used when registering new threads.
|
|
106
118
|
static ARGV: OnceLock<String> = OnceLock::new();
|
|
107
119
|
|
|
108
|
-
/// True iff we've started tracing and haven't been marked as terminated.
|
|
120
|
+
/// True iff we've started tracing and haven't been marked as terminated. When this is
|
|
109
121
|
/// set, we're allowed to send messages to the profile generation server.
|
|
110
122
|
static STARTED: AtomicBool = AtomicBool::new(false);
|
|
111
123
|
|
|
112
124
|
thread_local! {
|
|
113
125
|
/// A per-thread [`ThreadState`] for writing events to.
|
|
114
126
|
//
|
|
115
|
-
// NOTE: The `const` block
|
|
116
|
-
|
|
117
|
-
static THREAD_STATE: Cell<*mut ThreadState> = const { Cell::new(std::ptr::null_mut()) };
|
|
127
|
+
// NOTE: The `const` block allows the macro to avoid lazy initialization.
|
|
128
|
+
static THREAD_STATE: RefCell<Option<Box<ThreadState>>> = const { RefCell::new(None) };
|
|
118
129
|
}
|
|
119
130
|
|
|
120
131
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
@@ -140,22 +151,17 @@ mod _functiontrace_rs {
|
|
|
140
151
|
Duration::new(time.tv_sec as u64, time.tv_nsec as u32)
|
|
141
152
|
}
|
|
142
153
|
|
|
143
|
-
///
|
|
144
|
-
///
|
|
145
|
-
///
|
|
154
|
+
/// Run the given callback under the [`ThreadState`] for the current thread.
|
|
155
|
+
/// Returns `None` if the thread is not registered or the state is currently borrowed.
|
|
156
|
+
///
|
|
157
|
+
/// NOTE: The latter should only happen if we're running Python code inside a tracing event,
|
|
158
|
+
/// which is something we don't actually want to trace (it would be tracing FunctionTrace
|
|
159
|
+
/// implementation rather than the user's code).
|
|
146
160
|
#[inline(always)]
|
|
147
|
-
pub fn thread_state() ->
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
// We shouldn't be asked to reset a thread that hasn't yet been registered.
|
|
152
|
-
return None;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
Some(unsafe {
|
|
156
|
-
// SAFETY: state is a valid pointer to a ThreadState, since we always remove from TLS
|
|
157
|
-
// before freeing.
|
|
158
|
-
state.as_mut().expect("state is non-null")
|
|
161
|
+
pub fn thread_state<T>(access: impl FnOnce(&mut ThreadState) -> T) -> Option<T> {
|
|
162
|
+
THREAD_STATE.with(|state| {
|
|
163
|
+
let mut state = state.try_borrow_mut().ok()?;
|
|
164
|
+
Some(access(state.as_deref_mut()?))
|
|
159
165
|
})
|
|
160
166
|
}
|
|
161
167
|
|
|
@@ -288,7 +294,7 @@ mod _functiontrace_rs {
|
|
|
288
294
|
// We'll display this in the UI, so ensure it's under (the rather
|
|
289
295
|
// arbitrary) 100 chars.
|
|
290
296
|
if full_args.chars().count() > 95 {
|
|
291
|
-
full_args.
|
|
297
|
+
full_args = full_args.chars().take(95).collect();
|
|
292
298
|
full_args.push_str(" ...");
|
|
293
299
|
}
|
|
294
300
|
|
|
@@ -446,6 +452,7 @@ mod _functiontrace_rs {
|
|
|
446
452
|
};
|
|
447
453
|
|
|
448
454
|
// Create a multiprocessing wrapper for the current executable if it doesn't exist yet.
|
|
455
|
+
// TODO: This is slow. Better to just hash the executable.
|
|
449
456
|
std::fs::create_dir_all(
|
|
450
457
|
wrapper
|
|
451
458
|
.parent()
|
|
@@ -465,6 +472,8 @@ mod _functiontrace_rs {
|
|
|
465
472
|
&format!("'{}'", executable.replace('\'', "'\"'\"'")),
|
|
466
473
|
);
|
|
467
474
|
|
|
475
|
+
// TODO: this needs to be atomic, since multiple children could hit this case at
|
|
476
|
+
// once.
|
|
468
477
|
file.write_all(script.as_bytes())
|
|
469
478
|
.wrap_err("Failed to write multiprocessing wrapper")?;
|
|
470
479
|
file.set_permissions(std::fs::Permissions::from_mode(0o700))
|
|
@@ -486,18 +495,17 @@ mod _functiontrace_rs {
|
|
|
486
495
|
// Ensure we're properly handling teardown scenarios, including flushing data in both
|
|
487
496
|
// single and multi-threaded scenarios, and reseting anything that's needed on forks.
|
|
488
497
|
|
|
489
|
-
// Mark that we'll need to forget some information on forks.
|
|
490
|
-
//
|
|
491
|
-
// information.
|
|
498
|
+
// Mark that we'll need to forget some information on forks. In particular, we shouldn't
|
|
499
|
+
// think that we have a thread that's sent any information.
|
|
492
500
|
if unsafe { libc::pthread_atfork(None, None, Some(c_thread_reset)) } != 0 {
|
|
493
501
|
return Err(eyre!("Failed to register pthread_atfork() handler"));
|
|
494
502
|
}
|
|
495
503
|
|
|
496
504
|
// Things can get into a weird state during shutdown due to GC
|
|
497
|
-
// (bugs.python.org/issue21512).
|
|
498
|
-
//
|
|
505
|
+
// (bugs.python.org/issue21512). Halt our tracing instead to avoid odd behavior and ensure
|
|
506
|
+
// we've flushed all our data.
|
|
499
507
|
//
|
|
500
|
-
// NOTE: We specifically do this rather than `Py_AtExit`
|
|
508
|
+
// NOTE: We specifically do this rather than `Py_AtExit` since we want to run before all
|
|
501
509
|
// the Python teardown occurs.
|
|
502
510
|
Python::attach(|py| -> Result<()> {
|
|
503
511
|
py.import("atexit")
|
|
@@ -562,7 +570,8 @@ mod _functiontrace_rs {
|
|
|
562
570
|
_: &Bound<'_, pyo3::types::PyTuple>,
|
|
563
571
|
_kwargs: Option<&Bound<'_, pyo3::types::PyDict>>,
|
|
564
572
|
) -> Result<()> {
|
|
565
|
-
// Stop profiling and don't allow further logging
|
|
573
|
+
// Stop profiling and don't allow further logging.
|
|
574
|
+
// The outstanding messages will be flushed on exit when the ThreadState is dropped.
|
|
566
575
|
STARTED.store(false, Ordering::Relaxed);
|
|
567
576
|
|
|
568
577
|
unsafe {
|
|
@@ -570,23 +579,19 @@ mod _functiontrace_rs {
|
|
|
570
579
|
pyo3::ffi::PyEval_SetProfile(None, std::ptr::null_mut());
|
|
571
580
|
}
|
|
572
581
|
|
|
573
|
-
//
|
|
574
|
-
|
|
575
|
-
thread_teardown(writer).wrap_err("Failed to teardown main thread")?;
|
|
576
|
-
}
|
|
582
|
+
// TLS destruction isn't guaranteed to run on the main thread's shutdown.
|
|
583
|
+
thread_teardown();
|
|
577
584
|
|
|
578
585
|
Ok(())
|
|
579
586
|
}
|
|
580
587
|
|
|
581
588
|
/// Register a new thread, including creating the UnixStream to log messages for this thread
|
|
582
|
-
/// on.
|
|
583
|
-
///
|
|
584
|
-
/// A new [`ThreadState`] will be returned, which must freed once the thread has terminated.
|
|
589
|
+
/// on. The new [`ThreadState`] is stored in TLS until until [`thread_teardown`] is run.
|
|
585
590
|
fn thread_register() -> Result<()> {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
591
|
+
assert!(
|
|
592
|
+
THREAD_STATE.with(|state| { state.borrow().is_none() }),
|
|
593
|
+
"We can't have a ThreadState yet since we're responsible for registering it"
|
|
594
|
+
);
|
|
590
595
|
|
|
591
596
|
let register =
|
|
592
597
|
function_trace::FunctionTrace::RegisterThread(function_trace::ThreadRegistration {
|
|
@@ -610,37 +615,28 @@ mod _functiontrace_rs {
|
|
|
610
615
|
.wrap_err("Failed to emit RegisterThread message")?;
|
|
611
616
|
|
|
612
617
|
// Initialize the `ThreadState` for this thread, then associate the socket with it.
|
|
613
|
-
|
|
614
|
-
// We'll move this state into thread local storage, and will need to recover it on
|
|
615
|
-
// termination in order to free it.
|
|
616
|
-
let state = Box::into_raw(Box::new(ThreadState {
|
|
618
|
+
let state = Box::new(ThreadState {
|
|
617
619
|
socket,
|
|
618
620
|
|
|
619
621
|
buffer: [0u8; MSGPACK_BUFFER],
|
|
620
622
|
head: 0,
|
|
621
|
-
})
|
|
623
|
+
});
|
|
622
624
|
|
|
623
625
|
// Store our state in thread-specific storage so we can find it in the future.
|
|
624
626
|
// Tracing begins for the thread at this point, since we've now published `state` for other
|
|
625
627
|
// functions to start using.
|
|
626
|
-
THREAD_STATE.set(state);
|
|
628
|
+
THREAD_STATE.set(Some(state));
|
|
627
629
|
|
|
628
630
|
Ok(())
|
|
629
631
|
}
|
|
630
632
|
|
|
631
|
-
/// Tear down the given thread by
|
|
633
|
+
/// Tear down the given thread by dropping its [`ThreadState`], which will also flush any
|
|
634
|
+
/// outstanding messages.
|
|
632
635
|
///
|
|
633
|
-
/// This is called when the thread has shut down
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
.wrap_err("Failed to flush remaining messages")?;
|
|
638
|
-
|
|
639
|
-
// TODO: We should tear down the socket to avoid leaking O(# threads) resources.
|
|
640
|
-
// Historically doing this has crashed for some reason...
|
|
641
|
-
//
|
|
642
|
-
// TODO: And we should probably free state since it's pretty large...
|
|
643
|
-
Ok(())
|
|
636
|
+
/// This is called when the thread has shut down but we can't rely on `ThreadState::drop`
|
|
637
|
+
/// running, including in multiprocess situations.
|
|
638
|
+
pub fn thread_teardown() {
|
|
639
|
+
let _ = THREAD_STATE.take();
|
|
644
640
|
}
|
|
645
641
|
|
|
646
642
|
#[unsafe(no_mangle)]
|
|
@@ -654,26 +650,21 @@ mod _functiontrace_rs {
|
|
|
654
650
|
/// This is useful when we've just forked and want to ensure we don't reuse an existing socket.
|
|
655
651
|
fn thread_reset() -> Result<()> {
|
|
656
652
|
if !STARTED.load(Ordering::Relaxed) {
|
|
657
|
-
// We haven't actually started yet, but are for some reason being asked
|
|
658
|
-
//
|
|
653
|
+
// We haven't actually started yet, but are for some reason being asked to fork. This
|
|
654
|
+
// appears to be OS dependent.
|
|
659
655
|
return Ok(());
|
|
660
656
|
}
|
|
661
657
|
|
|
662
|
-
//
|
|
663
|
-
let state =
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
658
|
+
// Remove the old state without flushing the parent's socket.
|
|
659
|
+
let mut state = THREAD_STATE
|
|
660
|
+
.take()
|
|
661
|
+
.ok_or_eyre(eyre!("Thread wasn't yet registered"))?;
|
|
662
|
+
// Empty the buffer so the `Drop` impl doesn't flush it.
|
|
663
|
+
state.head = 0;
|
|
664
|
+
drop(state);
|
|
667
665
|
|
|
668
|
-
//
|
|
669
|
-
|
|
670
|
-
// SAFETY: `state` came from `Box::into_raw` previously and can only be freed once.
|
|
671
|
-
let _ = Box::from_raw(state);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// Create a new thread for this process. We don't need to start an entire new trace like
|
|
675
|
-
// with subprocess calls since we were forked and therefore already share our
|
|
676
|
-
// configuration.
|
|
666
|
+
// Create a new thread for this process. We don't need to start an entire new trace like
|
|
667
|
+
// with subprocess calls since we were forked and therefore already share our configuration.
|
|
677
668
|
thread_register().wrap_err("Failed to register new thread")?;
|
|
678
669
|
|
|
679
670
|
Ok(())
|
|
@@ -766,7 +757,7 @@ mod _functiontrace_rs {
|
|
|
766
757
|
Ok(())
|
|
767
758
|
}
|
|
768
759
|
|
|
769
|
-
/// The core logging function for FunctionTrace.
|
|
760
|
+
/// The core logging function for FunctionTrace. Configured by `PyEval_SetProfile`, and used
|
|
770
761
|
/// to record all function calls/returns.
|
|
771
762
|
extern "C" fn functiontrace(
|
|
772
763
|
_obj: *mut pyo3::ffi::PyObject,
|
|
@@ -774,142 +765,141 @@ mod _functiontrace_rs {
|
|
|
774
765
|
what: i32,
|
|
775
766
|
arg: *mut pyo3::ffi::PyObject,
|
|
776
767
|
) -> i32 {
|
|
777
|
-
let Some(mut writer) = thread_state() else {
|
|
778
|
-
// No reason to look at the event since we can't log it yet.
|
|
779
|
-
return 0;
|
|
780
|
-
};
|
|
781
|
-
|
|
782
768
|
if !STARTED.load(Ordering::Relaxed) {
|
|
783
769
|
// We aren't running (either starting up or shutting down), so shouldn't write
|
|
784
770
|
// anything.
|
|
785
771
|
return 0;
|
|
786
772
|
}
|
|
787
773
|
|
|
788
|
-
|
|
774
|
+
// We hold a mutable reference to the current ThreadState throughout this entire function.
|
|
775
|
+
// This ensures that we don't log anything from Python code that we call as part of the
|
|
776
|
+
// function (such as a `str()` implementation), since that's not part of the user's code.
|
|
777
|
+
thread_state(|mut writer| {
|
|
778
|
+
let time = trace_time();
|
|
789
779
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
780
|
+
// Return the function name when we're in a C_* context.
|
|
781
|
+
let c_func_name = || {
|
|
782
|
+
let func = arg as *mut pyo3::ffi::PyCFunctionObject;
|
|
793
783
|
|
|
794
|
-
unsafe {
|
|
795
|
-
// SAFETY: In the `C_*` cases, `arg` is a non-NULL `*PyCFunctionObject`, which
|
|
796
|
-
// always contains a valid method.
|
|
797
|
-
let method = (*func).m_ml;
|
|
798
|
-
(*method).ml_name
|
|
799
|
-
}
|
|
800
|
-
};
|
|
801
|
-
// Given a C string sourced from Python, convert it to a Cow that can be serialized.
|
|
802
|
-
let cstr_to_cow = |ptr: *const i8| {
|
|
803
|
-
if !ptr.is_null() {
|
|
804
784
|
unsafe {
|
|
805
|
-
// SAFETY: `
|
|
806
|
-
//
|
|
807
|
-
|
|
785
|
+
// SAFETY: In the `C_*` cases, `arg` is a non-NULL `*PyCFunctionObject`, which
|
|
786
|
+
// always contains a valid method.
|
|
787
|
+
let method = (*func).m_ml;
|
|
788
|
+
(*method).ml_name
|
|
808
789
|
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
Python::attach(|py| {
|
|
790
|
+
};
|
|
791
|
+
// Given a C string sourced from Python, convert it to a Cow that can be serialized.
|
|
792
|
+
let cstr_to_cow = |ptr: *const i8| {
|
|
793
|
+
if !ptr.is_null() {
|
|
794
|
+
unsafe {
|
|
795
|
+
// SAFETY: `ptr` is non-NULL and contains a valid C string.
|
|
796
|
+
// TODO: Can this change underneath us?
|
|
797
|
+
CStr::from_ptr(ptr)
|
|
798
|
+
}
|
|
799
|
+
.to_str()
|
|
800
|
+
.unwrap_or("<invalid string>")
|
|
801
|
+
.into()
|
|
802
|
+
} else {
|
|
803
|
+
Cow::Borrowed("<null>")
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
match what {
|
|
807
|
+
pyo3::ffi::PyTrace_CALL | pyo3::ffi::PyTrace_RETURN => {
|
|
808
|
+
// Cache information about each frame we see so that we don't need to look it up
|
|
809
|
+
// every time, which involves expensive Python operations.
|
|
810
|
+
static FRAMES: LazyLock<
|
|
811
|
+
dashmap::DashMap<usize, (Cow<'static, str>, Cow<'static, str>)>,
|
|
812
|
+
> = LazyLock::new(|| {
|
|
813
|
+
dashmap::DashMap::with_capacity(
|
|
814
|
+
// Even small programs will have a decent amount of function calls in the
|
|
815
|
+
// interpreter setup.
|
|
816
|
+
512,
|
|
817
|
+
)
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
let entry = FRAMES.entry(frame.addr()).or_insert_with(|| {
|
|
821
|
+
// We've never seen this frame before, so we'll need to load its information.
|
|
822
|
+
// This is fairly slow, since it'll involve going via Python rather than fast
|
|
823
|
+
// struct accesses.
|
|
845
824
|
let code = unsafe {
|
|
846
|
-
// SAFETY:
|
|
847
|
-
// came from `PyFrame_GetCode`.
|
|
848
|
-
Bound::from_borrowed_ptr(py, code)
|
|
849
|
-
};
|
|
825
|
+
// SAFETY: frame is guaranteed to be a valid PyFrameObject
|
|
850
826
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
if let Ok(filename) = obj.cast::<pyo3::types::PyString>() {
|
|
857
|
-
Cow::from(filename.to_string_lossy().into_owned())
|
|
858
|
-
} else {
|
|
859
|
-
Cow::Borrowed("<UNKNOWN>")
|
|
860
|
-
}
|
|
861
|
-
})
|
|
862
|
-
.unwrap_or_else(|_| Cow::Borrowed("<NONE>"))
|
|
827
|
+
// We'll be storing `frame` in our hashmap, so we don't want anyone
|
|
828
|
+
// else freeing it.
|
|
829
|
+
pyo3::ffi::Py_IncRef(frame as *mut pyo3::ffi::PyObject);
|
|
830
|
+
|
|
831
|
+
pyo3::ffi::PyFrame_GetCode(frame) as *mut pyo3::ffi::PyObject
|
|
863
832
|
};
|
|
864
833
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
pyo3::intern!(py, "co_name")
|
|
872
|
-
}));
|
|
834
|
+
Python::attach(|py| {
|
|
835
|
+
let code = unsafe {
|
|
836
|
+
// SAFETY: code is guaranteed to be a valid PyCodeObject since it
|
|
837
|
+
// came from `PyFrame_GetCode`.
|
|
838
|
+
Bound::from_owned_ptr(py, code)
|
|
839
|
+
};
|
|
873
840
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
841
|
+
// Given a Python object that _should_ be a string, convert it into an
|
|
842
|
+
// a Rust string.
|
|
843
|
+
let unicode_converter = |pyobj: PyResult<Bound<'_, PyAny>>| {
|
|
844
|
+
pyobj
|
|
845
|
+
.map(|obj| {
|
|
846
|
+
if let Ok(filename) = obj.cast::<pyo3::types::PyString>() {
|
|
847
|
+
Cow::from(filename.to_string_lossy().into_owned())
|
|
848
|
+
} else {
|
|
849
|
+
Cow::Borrowed("<UNKNOWN>")
|
|
850
|
+
}
|
|
851
|
+
})
|
|
852
|
+
.unwrap_or_else(|_| Cow::Borrowed("<NONE>"))
|
|
853
|
+
};
|
|
877
854
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
855
|
+
let filename =
|
|
856
|
+
unicode_converter(code.getattr(pyo3::intern!(py, "co_filename")));
|
|
857
|
+
let func_name = unicode_converter(code.getattr(if cfg!(Py_3_11) {
|
|
858
|
+
// co_qualname is better but was only added in 3.11.
|
|
859
|
+
pyo3::intern!(py, "co_qualname")
|
|
860
|
+
} else {
|
|
861
|
+
pyo3::intern!(py, "co_name")
|
|
862
|
+
}));
|
|
882
863
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
864
|
+
(filename, func_name)
|
|
865
|
+
})
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
if what == pyo3::ffi::PyTrace_CALL {
|
|
869
|
+
let linenumber = unsafe { pyo3::ffi::PyFrame_GetLineNumber(frame) }
|
|
870
|
+
.try_into()
|
|
871
|
+
.expect("Invalid line number for executing frame");
|
|
872
|
+
|
|
873
|
+
function_trace::FunctionTrace::Call {
|
|
874
|
+
filename: entry.0.clone(),
|
|
875
|
+
func_name: entry.1.clone(),
|
|
876
|
+
linenumber,
|
|
877
|
+
time,
|
|
878
|
+
}
|
|
879
|
+
.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
880
|
+
.expect("Failed to emit Call message");
|
|
881
|
+
} else {
|
|
882
|
+
function_trace::FunctionTrace::Return {
|
|
883
|
+
func_name: entry.1.clone(),
|
|
884
|
+
time,
|
|
885
|
+
}
|
|
886
|
+
.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
887
|
+
.expect("Failed to emit Return message")
|
|
895
888
|
}
|
|
896
|
-
.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
897
|
-
.expect("Failed to emit Return message")
|
|
898
889
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
};
|
|
890
|
+
pyo3::ffi::PyTrace_C_CALL => {
|
|
891
|
+
let func = unsafe {
|
|
892
|
+
// SAFETY: In the `C_CALL` case, `arg` is guaranteed to be a non-NULL pointer
|
|
893
|
+
// to a `PyCFunctionObject`.
|
|
894
|
+
&*(arg as *mut pyo3::ffi::PyCFunctionObject)
|
|
895
|
+
};
|
|
906
896
|
|
|
907
|
-
|
|
897
|
+
let func_name = cstr_to_cow(c_func_name());
|
|
908
898
|
|
|
909
|
-
|
|
910
|
-
|
|
899
|
+
// We either belong to a module or are a method on a class.
|
|
900
|
+
if !func.m_module.is_null() {
|
|
911
901
|
// NOTE: _Typically_ `m_module` is a PyModule, but there are no requirements on
|
|
912
|
-
// what type it needs to be!
|
|
902
|
+
// what type it needs to be! In practice, we only see PyModules and PyStrings.
|
|
913
903
|
Python::attach(|py| {
|
|
914
904
|
let m_module = unsafe {
|
|
915
905
|
// SAFETY: m_module is a non-NULL pointer to a valid Python object.
|
|
@@ -973,35 +963,35 @@ mod _functiontrace_rs {
|
|
|
973
963
|
.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
974
964
|
}
|
|
975
965
|
.expect("Failed to emit NativeCall message");
|
|
976
|
-
}
|
|
977
|
-
pyo3::ffi::PyTrace_C_RETURN | pyo3::ffi::PyTrace_C_EXCEPTION => {
|
|
978
|
-
if what == pyo3::ffi::PyTrace_C_EXCEPTION {
|
|
979
|
-
// C exceptions aren't exposed in a particularly interesting fashion, since they're
|
|
980
|
-
// done using the `PyErr_*` functions plus a NULL return value.
|
|
981
|
-
// However, if a `C_EXCEPTION` event is sent, we won't receive the corresponding
|
|
982
|
-
// `C_RETURN` event, so in practice we treat both of these events the same.
|
|
983
|
-
//
|
|
984
|
-
// TODO: Should we try exposing information about them? We'd probably need to
|
|
985
|
-
// change the `Exception` format in `functiontrace-server`.
|
|
986
966
|
}
|
|
967
|
+
pyo3::ffi::PyTrace_C_RETURN | pyo3::ffi::PyTrace_C_EXCEPTION => {
|
|
968
|
+
if what == pyo3::ffi::PyTrace_C_EXCEPTION {
|
|
969
|
+
// C exceptions aren't exposed in a particularly interesting fashion, since they're
|
|
970
|
+
// done using the `PyErr_*` functions plus a NULL return value.
|
|
971
|
+
// However, if a `C_EXCEPTION` event is sent, we won't receive the corresponding
|
|
972
|
+
// `C_RETURN` event, so in practice we treat both of these events the same.
|
|
973
|
+
//
|
|
974
|
+
// TODO: Should we try exposing information about them? We'd probably need to
|
|
975
|
+
// change the `Exception` format in `functiontrace-server`.
|
|
976
|
+
}
|
|
987
977
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
978
|
+
function_trace::FunctionTrace::NativeReturn {
|
|
979
|
+
func_name: cstr_to_cow(c_func_name()),
|
|
980
|
+
time,
|
|
981
|
+
}
|
|
982
|
+
.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
983
|
+
.expect("Failed to emit NativeReturn message");
|
|
991
984
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
}
|
|
1003
|
-
};
|
|
1004
|
-
|
|
985
|
+
pyo3::ffi::PyTrace_EXCEPTION => {
|
|
986
|
+
// TODO: Actually handle exceptions and do something interesting.
|
|
987
|
+
// We unfortunately can't get these from `PyEval_SetProfile`, so we'd need to use a
|
|
988
|
+
// different method to capture them.
|
|
989
|
+
}
|
|
990
|
+
v => {
|
|
991
|
+
tracing::error!(event = v, "Unexpected PyTrace message!");
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
});
|
|
1005
995
|
0
|
|
1006
996
|
}
|
|
1007
997
|
|
|
@@ -1051,15 +1041,17 @@ mod _functiontrace_rs {
|
|
|
1051
1041
|
}
|
|
1052
1042
|
};
|
|
1053
1043
|
|
|
1054
|
-
let config = if let Ok(
|
|
1055
|
-
.wrap_err("Failed to read config file")
|
|
1056
|
-
.and_then(|x| toml::from_slice::<Config>(&x).wrap_err("Failed to parse config file"))
|
|
1057
|
-
{
|
|
1044
|
+
let config = if let Ok(contents) = std::fs::read(&config_path) {
|
|
1058
1045
|
// We successfully loaded an existing config file.
|
|
1059
|
-
|
|
1046
|
+
toml::from_slice::<Config>(&contents).wrap_err_with(|| {
|
|
1047
|
+
format!(
|
|
1048
|
+
"Failed to load existing Functiontrace config file at {}",
|
|
1049
|
+
config_path.display()
|
|
1050
|
+
)
|
|
1051
|
+
})?
|
|
1060
1052
|
} else {
|
|
1061
|
-
// There's no config file, so assume this is the first time FunctionTrace has been
|
|
1062
|
-
//
|
|
1053
|
+
// There's no config file, so assume this is the first time FunctionTrace has been used
|
|
1054
|
+
// and generate a new one.
|
|
1063
1055
|
let logo = "\
|
|
1064
1056
|
███████╗██╗ ██╗███╗ ██╗ ██████╗████████╗██╗ ██████╗ ███╗ ██╗████████╗██████╗ █████╗ ██████╗███████╗\n\
|
|
1065
1057
|
██╔════╝██║ ██║████╗ ██║██╔════╝╚══██╔══╝██║██╔═══██╗████╗ ██║╚══██╔══╝██╔══██╗██╔══██╗██╔════╝██╔════╝\n\
|
|
@@ -1097,7 +1089,7 @@ mod _functiontrace_rs {
|
|
|
1097
1089
|
println!();
|
|
1098
1090
|
println!(
|
|
1099
1091
|
"{}",
|
|
1100
|
-
"Welcome to FunctionTrace, a low
|
|
1092
|
+
"Welcome to FunctionTrace, a low overhead tracing profiler for Python".bold()
|
|
1101
1093
|
);
|
|
1102
1094
|
println!();
|
|
1103
1095
|
println!(
|
|
@@ -1140,8 +1132,8 @@ mod _functiontrace_rs {
|
|
|
1140
1132
|
config
|
|
1141
1133
|
};
|
|
1142
1134
|
|
|
1143
|
-
// We need to write out the config if it's changed.
|
|
1144
|
-
//
|
|
1135
|
+
// We need to write out the config if it's changed. Track the new and old configs so we can
|
|
1136
|
+
// tell.
|
|
1145
1137
|
let mut new_config = config.clone();
|
|
1146
1138
|
|
|
1147
1139
|
////////////////////////////////////////////////////////////////////////
|
|
@@ -1292,7 +1284,7 @@ mod _functiontrace_rs {
|
|
|
1292
1284
|
if let Ok(valid_expiration) = Timestamp::strptime(DEMO_KEY_FORMAT, demo_key) {
|
|
1293
1285
|
if now
|
|
1294
1286
|
.until(valid_expiration)
|
|
1295
|
-
.
|
|
1287
|
+
.is_ok_and(|remaining| remaining.is_positive())
|
|
1296
1288
|
{
|
|
1297
1289
|
// The license is still valid
|
|
1298
1290
|
eprintln!(
|
|
@@ -1453,14 +1445,14 @@ mod allocation_wrappers {
|
|
|
1453
1445
|
|
|
1454
1446
|
/// Log the given allocation details.
|
|
1455
1447
|
fn allocation_log<F: FnOnce() -> function_trace::AllocationDetails>(msg: F) {
|
|
1456
|
-
|
|
1448
|
+
crate::_functiontrace_rs::thread_state(|mut writer| {
|
|
1457
1449
|
function_trace::FunctionTrace::Allocation {
|
|
1458
1450
|
time: crate::_functiontrace_rs::trace_time(),
|
|
1459
1451
|
details: msg(),
|
|
1460
1452
|
}
|
|
1461
1453
|
.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
1462
1454
|
.expect("Failed to emit Allocation message");
|
|
1463
|
-
}
|
|
1455
|
+
});
|
|
1464
1456
|
}
|
|
1465
1457
|
|
|
1466
1458
|
/// Given an allocator->ctx for a logging allocator, convert it to point to the original
|
|
@@ -1477,33 +1469,36 @@ mod allocation_wrappers {
|
|
|
1477
1469
|
#[unsafe(no_mangle)]
|
|
1478
1470
|
pub extern "C" fn log_malloc(ctx: *mut c_void, bytes: usize) -> *mut c_void {
|
|
1479
1471
|
let wrapped_allocator = allocator_ctx(ctx);
|
|
1480
|
-
let addr = wrapped_allocator
|
|
1472
|
+
if let Some(addr) = wrapped_allocator
|
|
1481
1473
|
.malloc
|
|
1482
1474
|
.map(|malloc| malloc(wrapped_allocator.ctx, bytes))
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1475
|
+
{
|
|
1476
|
+
allocation_log(|| function_trace::AllocationDetails::Alloc {
|
|
1477
|
+
bytes,
|
|
1478
|
+
addr: addr as usize,
|
|
1479
|
+
});
|
|
1480
|
+
addr
|
|
1481
|
+
} else {
|
|
1482
|
+
std::ptr::null_mut()
|
|
1483
|
+
}
|
|
1491
1484
|
}
|
|
1492
1485
|
|
|
1493
1486
|
#[unsafe(no_mangle)]
|
|
1494
1487
|
pub extern "C" fn log_calloc(ctx: *mut c_void, nelems: usize, elsize: usize) -> *mut c_void {
|
|
1495
1488
|
let wrapped_allocator = allocator_ctx(ctx);
|
|
1496
|
-
let addr = wrapped_allocator
|
|
1489
|
+
if let Some(addr) = wrapped_allocator
|
|
1497
1490
|
.calloc
|
|
1498
1491
|
.map(|calloc| calloc(wrapped_allocator.ctx, nelems, elsize))
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
});
|
|
1492
|
+
{
|
|
1493
|
+
allocation_log(|| function_trace::AllocationDetails::Alloc {
|
|
1494
|
+
bytes: nelems * elsize,
|
|
1495
|
+
addr: addr as usize,
|
|
1496
|
+
});
|
|
1505
1497
|
|
|
1506
|
-
|
|
1498
|
+
addr
|
|
1499
|
+
} else {
|
|
1500
|
+
std::ptr::null_mut()
|
|
1501
|
+
}
|
|
1507
1502
|
}
|
|
1508
1503
|
|
|
1509
1504
|
#[unsafe(no_mangle)]
|
|
@@ -1513,18 +1508,20 @@ mod allocation_wrappers {
|
|
|
1513
1508
|
new_size: usize,
|
|
1514
1509
|
) -> *mut c_void {
|
|
1515
1510
|
let wrapped_allocator = allocator_ctx(ctx);
|
|
1516
|
-
let addr = wrapped_allocator
|
|
1511
|
+
if let Some(addr) = wrapped_allocator
|
|
1517
1512
|
.realloc
|
|
1518
1513
|
.map(|realloc| realloc(wrapped_allocator.ctx, old_addr, new_size))
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
});
|
|
1514
|
+
{
|
|
1515
|
+
allocation_log(|| function_trace::AllocationDetails::Realloc {
|
|
1516
|
+
bytes: new_size,
|
|
1517
|
+
old_addr: old_addr as usize,
|
|
1518
|
+
new_addr: addr as usize,
|
|
1519
|
+
});
|
|
1526
1520
|
|
|
1527
|
-
|
|
1521
|
+
addr
|
|
1522
|
+
} else {
|
|
1523
|
+
std::ptr::null_mut()
|
|
1524
|
+
}
|
|
1528
1525
|
}
|
|
1529
1526
|
|
|
1530
1527
|
#[unsafe(no_mangle)]
|
|
@@ -1608,52 +1605,28 @@ mod hooks {
|
|
|
1608
1605
|
// Hooking Framework
|
|
1609
1606
|
////////////////////////////////////////////////////////////////////////////
|
|
1610
1607
|
|
|
1611
|
-
///
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
/// modify.
|
|
1621
|
-
macro_rules! hook_counter {
|
|
1622
|
-
// The accumulator has fully collected all of our hooks, so we can now emit a vec of them.
|
|
1623
|
-
( [$( ($target:literal, $wrapper:ident, $counter:expr) ),* $(,)?], $_counter:expr) => {
|
|
1624
|
-
[$( Hook {
|
|
1608
|
+
/// Generate a static slice of [`Hook`]s with info about the target functions.
|
|
1609
|
+
macro_rules! hooks {
|
|
1610
|
+
// Entry: setup a counter an accumulator array
|
|
1611
|
+
($(($target:literal, $wrapper:ident)),* $(,)?) => {
|
|
1612
|
+
hooks!(@count 0; []; $(($target, $wrapper),)*)
|
|
1613
|
+
};
|
|
1614
|
+
// Recursive case: consume one function and put info into the accumulator array.
|
|
1615
|
+
(@count $index:expr; [$($hooks:expr,)*]; ($target:literal, $wrapper:ident), $($rest:tt)*) => {
|
|
1616
|
+
hooks!(@count $index + 1; [$($hooks,)* Hook {
|
|
1625
1617
|
target: $target,
|
|
1626
|
-
wrapper: $wrapper::<{ $
|
|
1618
|
+
wrapper: $wrapper::<{ $index }>,
|
|
1627
1619
|
original: OnceLock::new(),
|
|
1628
|
-
})
|
|
1620
|
+
},]; $($rest)*)
|
|
1629
1621
|
};
|
|
1630
|
-
//
|
|
1631
|
-
(
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
( [$($acc:tt),* $(,)?], $counter:expr, $target:literal, $wrapper:ident) => {
|
|
1639
|
-
hook_counter!( [
|
|
1640
|
-
$($acc),*, ($target, $wrapper, $counter)
|
|
1641
|
-
], $counter +1)
|
|
1642
|
-
};
|
|
1643
|
-
// The main recursive case
|
|
1644
|
-
( [ $( $acc:tt ),* $(,)?], $counter:expr, $target:literal, $wrapper:ident, $($rest:tt),*) => {
|
|
1645
|
-
hook_counter!( [
|
|
1646
|
-
$($acc),*, ($target, $wrapper, $counter)
|
|
1647
|
-
], $counter +1, $($rest),*)
|
|
1648
|
-
};
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
/// Generate an array of [`Hook`]s. In particular, we need to generate unique references into
|
|
1652
|
-
/// [`HOOKS`] that each wrapper is aware of, meaning we need to use a custom push-down
|
|
1653
|
-
/// accumulator and counting macro.
|
|
1654
|
-
macro_rules! hooks {
|
|
1655
|
-
($(($target:literal, $wrapper:ident)),* $(,)?) => {
|
|
1656
|
-
hook_counter!([], 0, $($target, $wrapper),*)
|
|
1622
|
+
// Base case: we have no more functions to hook, so emit the hooks.
|
|
1623
|
+
(@count $index:expr; [$($hooks:expr,)*];) => {
|
|
1624
|
+
{
|
|
1625
|
+
// We need a static array with a fixed size to get a reference to. Generate this in
|
|
1626
|
+
// the macro so the caller doesn't need to manually update the size.
|
|
1627
|
+
static ENTRIES: [Hook; $index] = [$($hooks,)*];
|
|
1628
|
+
&ENTRIES
|
|
1629
|
+
}
|
|
1657
1630
|
};
|
|
1658
1631
|
}
|
|
1659
1632
|
|
|
@@ -1662,18 +1635,19 @@ mod hooks {
|
|
|
1662
1635
|
/// NOTE: This must be created by [`hooks`], since we actually generate new variants of the
|
|
1663
1636
|
/// wrapper function for each hook, allowing the function to determine which hook triggered it
|
|
1664
1637
|
/// at runtime.
|
|
1665
|
-
static HOOKS: [Hook
|
|
1638
|
+
static HOOKS: &[Hook] = hooks!(
|
|
1639
|
+
("builtins.__import__", logging_import),
|
|
1666
1640
|
("builtins.print", logging_print),
|
|
1641
|
+
("logging.critical", logging_print),
|
|
1667
1642
|
("logging.debug", logging_print),
|
|
1668
|
-
("logging.log", logging_print),
|
|
1669
|
-
("logging.info", logging_print),
|
|
1670
|
-
("logging.warning", logging_print),
|
|
1671
1643
|
("logging.error", logging_print),
|
|
1672
|
-
("logging.critical", logging_print),
|
|
1673
|
-
("logging.fatal", logging_print),
|
|
1674
1644
|
("logging.exception", logging_print),
|
|
1675
|
-
("
|
|
1676
|
-
("
|
|
1645
|
+
("logging.fatal", logging_print),
|
|
1646
|
+
("logging.info", logging_print),
|
|
1647
|
+
("logging.log", logging_print),
|
|
1648
|
+
("logging.warning", logging_print),
|
|
1649
|
+
("multiprocessing.util._exit_function", process_exit),
|
|
1650
|
+
("os._exit", process_exit),
|
|
1677
1651
|
);
|
|
1678
1652
|
|
|
1679
1653
|
/// Information, used during both runtime and at [`install`] time, about a specific function
|
|
@@ -1704,7 +1678,7 @@ mod hooks {
|
|
|
1704
1678
|
.expect("Wrapper functions can't be called until hooked");
|
|
1705
1679
|
|
|
1706
1680
|
let proxied = Python::attach(|py| -> Result<()> {
|
|
1707
|
-
|
|
1681
|
+
crate::_functiontrace_rs::thread_state(|mut writer| -> Result<()> {
|
|
1708
1682
|
let args = unsafe {
|
|
1709
1683
|
// SAFETY: `args` represents a valid argument tuple
|
|
1710
1684
|
Bound::from_borrowed_ptr(py, args)
|
|
@@ -1718,7 +1692,9 @@ mod hooks {
|
|
|
1718
1692
|
msg.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
1719
1693
|
.wrap_err("Failed to emit Allocation message")?;
|
|
1720
1694
|
}
|
|
1721
|
-
|
|
1695
|
+
Ok(())
|
|
1696
|
+
})
|
|
1697
|
+
.transpose()?;
|
|
1722
1698
|
|
|
1723
1699
|
Ok(())
|
|
1724
1700
|
})
|
|
@@ -1809,19 +1785,17 @@ mod hooks {
|
|
|
1809
1785
|
/// Though this is mixed in with the rest of the hooks, this is actually load-bearing for
|
|
1810
1786
|
/// general FunctionTrace functionality.
|
|
1811
1787
|
///
|
|
1812
|
-
///
|
|
1813
|
-
///
|
|
1814
|
-
|
|
1788
|
+
/// Anything that exits with `os._exit` (including `multiprocessing` by default) will
|
|
1789
|
+
/// immediately terminate the process without running cleanup/`atexit()`. Hook this to ensure we
|
|
1790
|
+
/// can flush as needed.
|
|
1791
|
+
extern "C" fn process_exit<const HOOK: usize>(
|
|
1815
1792
|
_self: *mut PyObject,
|
|
1816
1793
|
args: *mut PyObject,
|
|
1817
1794
|
kwargs: *mut PyObject,
|
|
1818
1795
|
) -> *mut PyObject {
|
|
1819
1796
|
let hook = &HOOKS[HOOK];
|
|
1820
1797
|
|
|
1821
|
-
|
|
1822
|
-
crate::_functiontrace_rs::thread_teardown(writer)
|
|
1823
|
-
.expect("Multiprocessing teardown should be successful");
|
|
1824
|
-
}
|
|
1798
|
+
crate::_functiontrace_rs::thread_teardown();
|
|
1825
1799
|
|
|
1826
1800
|
// Directly pass the command on, since we don't need to log/etc for this.
|
|
1827
1801
|
proxy(hook, |_args| None, args, kwargs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|