functiontrace 0.4.3__tar.gz → 0.4.4__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.4.3 → functiontrace-0.4.4}/Cargo.toml +1 -0
- {functiontrace-0.4.3 → functiontrace-0.4.4}/MANIFEST.in +0 -1
- {functiontrace-0.4.3/functiontrace.egg-info → functiontrace-0.4.4}/PKG-INFO +1 -1
- {functiontrace-0.4.3 → functiontrace-0.4.4/functiontrace.egg-info}/PKG-INFO +1 -1
- {functiontrace-0.4.3 → functiontrace-0.4.4}/functiontrace.egg-info/SOURCES.txt +0 -4
- functiontrace-0.4.4/functiontrace.egg-info/top_level.txt +1 -0
- {functiontrace-0.4.3 → functiontrace-0.4.4}/functiontrace.py +0 -1
- {functiontrace-0.4.3 → functiontrace-0.4.4}/pyproject.toml +1 -1
- {functiontrace-0.4.3 → functiontrace-0.4.4}/rust/lib.rs +134 -219
- functiontrace-0.4.4/setup.py +5 -0
- functiontrace-0.4.3/_functiontrace.c +0 -160
- functiontrace-0.4.3/functiontrace.egg-info/top_level.txt +0 -2
- functiontrace-0.4.3/mpack/LICENSE +0 -22
- functiontrace-0.4.3/mpack/mpack.c +0 -6440
- functiontrace-0.4.3/mpack/mpack.h +0 -7151
- functiontrace-0.4.3/setup.py +0 -14
- {functiontrace-0.4.3 → functiontrace-0.4.4}/LICENSE.md +0 -0
- {functiontrace-0.4.3 → functiontrace-0.4.4}/README.rst +0 -0
- {functiontrace-0.4.3 → functiontrace-0.4.4}/build.rs +0 -0
- {functiontrace-0.4.3 → functiontrace-0.4.4}/functiontrace.egg-info/dependency_links.txt +0 -0
- {functiontrace-0.4.3 → functiontrace-0.4.4}/functiontrace.egg-info/entry_points.txt +0 -0
- {functiontrace-0.4.3 → functiontrace-0.4.4}/setup.cfg +0 -0
|
@@ -5,6 +5,7 @@ edition = "2021"
|
|
|
5
5
|
|
|
6
6
|
[dependencies]
|
|
7
7
|
color-eyre = { version = "0.6", default-features = false }
|
|
8
|
+
dashmap = "6"
|
|
8
9
|
functiontrace-server = { version = "0.8.4", default-features = false, features = ["serialize"] }
|
|
9
10
|
libc = "0.2"
|
|
10
11
|
pyo3 = { version = "0.25", features = ["eyre"] }
|
|
@@ -2,7 +2,6 @@ Cargo.toml
|
|
|
2
2
|
LICENSE.md
|
|
3
3
|
MANIFEST.in
|
|
4
4
|
README.rst
|
|
5
|
-
_functiontrace.c
|
|
6
5
|
build.rs
|
|
7
6
|
functiontrace.py
|
|
8
7
|
pyproject.toml
|
|
@@ -12,7 +11,4 @@ functiontrace.egg-info/SOURCES.txt
|
|
|
12
11
|
functiontrace.egg-info/dependency_links.txt
|
|
13
12
|
functiontrace.egg-info/entry_points.txt
|
|
14
13
|
functiontrace.egg-info/top_level.txt
|
|
15
|
-
mpack/LICENSE
|
|
16
|
-
mpack/mpack.c
|
|
17
|
-
mpack/mpack.h
|
|
18
14
|
rust/lib.rs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
functiontrace
|
|
@@ -5,11 +5,12 @@ mod _functiontrace_rs {
|
|
|
5
5
|
use pyo3::prelude::*; // TODO: What's in here?
|
|
6
6
|
use serde::Serialize;
|
|
7
7
|
use std::borrow::Cow;
|
|
8
|
+
use std::cell::Cell;
|
|
8
9
|
use std::ffi::{c_void, CStr, OsString};
|
|
9
10
|
use std::io::Write;
|
|
10
11
|
use std::os::unix::net::UnixStream;
|
|
11
|
-
use std::sync::atomic::{AtomicBool,
|
|
12
|
-
use std::sync::OnceLock;
|
|
12
|
+
use std::sync::atomic::{AtomicBool, Ordering};
|
|
13
|
+
use std::sync::{LazyLock, OnceLock};
|
|
13
14
|
use std::time::Duration;
|
|
14
15
|
|
|
15
16
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
@@ -18,34 +19,22 @@ mod _functiontrace_rs {
|
|
|
18
19
|
/// The size of messagepack buffers. This is picked somewhat arbitrarily, but should be
|
|
19
20
|
/// reasonably small since we'll need one per thread, and should be large enough that we
|
|
20
21
|
/// can fit many messages in before needing to do expensive UnixStream syscalls.
|
|
21
|
-
const
|
|
22
|
+
const MSGPACK_BUFFER: usize = 1 << 17;
|
|
22
23
|
|
|
23
24
|
/// Storage specific to each thread used for buffering/transmitting data.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
flush: extern "C" fn(writer: *const MpackWriter, buffer: *const u8, bytes: usize),
|
|
33
|
-
error_fn: *const (),
|
|
34
|
-
teardown: *const (),
|
|
35
|
-
context: *mut UnixStream,
|
|
36
|
-
|
|
37
|
-
buffer: *mut u8,
|
|
38
|
-
current: *mut u8,
|
|
39
|
-
end: *mut u8,
|
|
40
|
-
|
|
41
|
-
// XXX: This is an enum that we never use. Since it doesn't have a clearly-defined size,
|
|
42
|
-
// we allocate 8 bytes for it. We MUST NOT access this field!
|
|
43
|
-
_error: u64,
|
|
25
|
+
pub struct ThreadState {
|
|
26
|
+
socket: UnixStream,
|
|
27
|
+
|
|
28
|
+
// TODO: Conceptually this should just be a `BufWriter`, but in practice using that
|
|
29
|
+
// triggers 0 byte writes that cause `functiontrace-server` to close the connection.
|
|
30
|
+
// Implementing this ourselves is pretty trivial, so...
|
|
31
|
+
buffer: [u8; MSGPACK_BUFFER],
|
|
32
|
+
head: usize,
|
|
44
33
|
}
|
|
45
34
|
|
|
46
|
-
impl Write for
|
|
47
|
-
/// A small [`Write`] implementation that
|
|
48
|
-
///
|
|
35
|
+
impl Write for ThreadState {
|
|
36
|
+
/// A small [`Write`] implementation that writes to a buffer and only flushes to the socket
|
|
37
|
+
/// once full.
|
|
49
38
|
fn write(&mut self, src: &[u8]) -> std::io::Result<usize> {
|
|
50
39
|
if !STARTED.load(Ordering::Relaxed) {
|
|
51
40
|
// We haven't started yet, so we shouldn't write anything out. This avoids logging
|
|
@@ -53,21 +42,15 @@ mod _functiontrace_rs {
|
|
|
53
42
|
return Ok(src.len());
|
|
54
43
|
}
|
|
55
44
|
|
|
56
|
-
let len = unsafe {
|
|
57
|
-
// SAFETY: writer.(current|end) are both within writer.buffer.
|
|
58
|
-
self.end.offset_from(self.current)
|
|
59
|
-
}
|
|
60
|
-
.try_into()
|
|
61
|
-
.expect("end > current");
|
|
62
|
-
|
|
63
45
|
let bytes = src.len();
|
|
46
|
+
let tail = self.head + bytes;
|
|
64
47
|
|
|
65
|
-
if
|
|
66
|
-
if bytes >=
|
|
48
|
+
if tail >= self.buffer.len() {
|
|
49
|
+
if bytes >= self.buffer.len() {
|
|
67
50
|
// We'll never be able to fit this much.
|
|
68
51
|
return Err(std::io::Error::new(
|
|
69
52
|
std::io::ErrorKind::StorageFull,
|
|
70
|
-
"
|
|
53
|
+
"msgpack buffer too small",
|
|
71
54
|
));
|
|
72
55
|
}
|
|
73
56
|
|
|
@@ -77,44 +60,25 @@ mod _functiontrace_rs {
|
|
|
77
60
|
|
|
78
61
|
return Err(std::io::Error::new(
|
|
79
62
|
std::io::ErrorKind::Interrupted,
|
|
80
|
-
"
|
|
63
|
+
"msgpack buffer is full, flushing",
|
|
81
64
|
));
|
|
82
65
|
}
|
|
83
66
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
buf[..bytes].copy_from_slice(src);
|
|
87
|
-
|
|
88
|
-
unsafe {
|
|
89
|
-
// SAFETY: end = current + len, and bytes < len, so current + bytes < end
|
|
90
|
-
self.current = self.current.add(bytes)
|
|
91
|
-
};
|
|
67
|
+
self.buffer[self.head..tail].copy_from_slice(src);
|
|
68
|
+
self.head = tail;
|
|
92
69
|
|
|
93
70
|
Ok(bytes)
|
|
94
71
|
}
|
|
95
72
|
|
|
96
|
-
///
|
|
73
|
+
/// Flush our local buffer to the socket.
|
|
97
74
|
fn flush(&mut self) -> std::io::Result<()> {
|
|
98
|
-
let len = unsafe {
|
|
99
|
-
// SAFETY: writer.current is within writer.buffer.
|
|
100
|
-
self.current.offset_from(self.buffer)
|
|
101
|
-
}
|
|
102
|
-
.try_into()
|
|
103
|
-
.expect("current > end");
|
|
104
|
-
|
|
105
|
-
let data = unsafe { std::slice::from_raw_parts_mut(self.buffer, len) };
|
|
106
|
-
|
|
107
75
|
// We're about to send all of the data, so reset our buffer. Do this now to ensure we
|
|
108
76
|
// don't miss it, since the buffer will be left in an invalid state if this doesn't
|
|
109
77
|
// happen.
|
|
110
|
-
|
|
78
|
+
let head = self.head;
|
|
79
|
+
self.head = 0;
|
|
111
80
|
|
|
112
|
-
|
|
113
|
-
// SAFETY: `writer` is valid and `writer->context` contains a `UnixStream` emitted from
|
|
114
|
-
// `thread_register`.
|
|
115
|
-
self.context.as_ref().expect("context is non-null")
|
|
116
|
-
};
|
|
117
|
-
socket.write_all(data)
|
|
81
|
+
self.socket.write_all(&self.buffer[..head])
|
|
118
82
|
}
|
|
119
83
|
}
|
|
120
84
|
|
|
@@ -125,13 +89,6 @@ mod _functiontrace_rs {
|
|
|
125
89
|
/// The UnixStream address we're using to communicate with the profile server.
|
|
126
90
|
static SOCKADDR: OnceLock<OsString> = OnceLock::new();
|
|
127
91
|
|
|
128
|
-
/// The pthread_key we use for accessing [`ThreadState`], acquired from the C extension.
|
|
129
|
-
///
|
|
130
|
-
/// NOTE: We store this as an atomic rather than use a [`OnceLock`] since we need to access it
|
|
131
|
-
/// in the critical path. There's some overhead to `OnceLock` acquisitions, while simple loads
|
|
132
|
-
/// and stores of [`AtomicU32`]s have no overhead (at least on x86).
|
|
133
|
-
static TSS_KEY: AtomicU32 = AtomicU32::new(0);
|
|
134
|
-
|
|
135
92
|
/// `sys.argv` for our process, which is used when registering new threads.
|
|
136
93
|
static ARGV: OnceLock<String> = OnceLock::new();
|
|
137
94
|
|
|
@@ -139,53 +96,12 @@ mod _functiontrace_rs {
|
|
|
139
96
|
/// set, we're allowed to send messages to the profile generation server.
|
|
140
97
|
static STARTED: AtomicBool = AtomicBool::new(false);
|
|
141
98
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
149
|
-
|
|
150
|
-
/// We expose all of our C functions in a single struct that's loaded once at the start of the
|
|
151
|
-
/// program, avoiding most of the overhead from the previous `dlopen-esque` interface.
|
|
152
|
-
#[derive(Debug)]
|
|
153
|
-
#[repr(C)]
|
|
154
|
-
struct RustFunctions {
|
|
155
|
-
set_config: usize,
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/// It's easiest to return pointer-sized types over the Python API, so allocate our
|
|
159
|
-
/// [`RustFunctions`] and leak it. This will be used by the C extension and never freed.
|
|
160
|
-
#[pyfunction]
|
|
161
|
-
fn c_api() -> usize {
|
|
162
|
-
let api = Box::new(RustFunctions {
|
|
163
|
-
set_config: set_config as usize,
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
Box::into_raw(api) as usize
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/// Called by the C module to pass the `c_functiontrace` handler to us.
|
|
170
|
-
#[unsafe(no_mangle)]
|
|
171
|
-
extern "C" fn set_config(c_functiontrace: pyo3::ffi::Py_tracefunc) -> libc::pthread_key_t {
|
|
172
|
-
// Allocate a Tss_Key for storing thread state
|
|
173
|
-
let mut tss_key = 0;
|
|
174
|
-
if unsafe {
|
|
175
|
-
// SAFETY: tss_key is live
|
|
176
|
-
libc::pthread_key_create(&raw mut tss_key, Some(c_thread_teardown))
|
|
177
|
-
} != 0
|
|
178
|
-
{
|
|
179
|
-
panic!("Failed to create Tss_Key");
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
TRACEFUNC
|
|
183
|
-
.set(c_functiontrace)
|
|
184
|
-
.expect("set_config can only be called once");
|
|
185
|
-
|
|
186
|
-
TSS_KEY.store(tss_key, Ordering::Relaxed);
|
|
187
|
-
|
|
188
|
-
tss_key
|
|
99
|
+
thread_local! {
|
|
100
|
+
/// A per-thread [`ThreadState`] for writing events to.
|
|
101
|
+
//
|
|
102
|
+
// NOTE: The `const` block appears to be necessary in order to generate reasonable
|
|
103
|
+
// assembly. Unclear why...
|
|
104
|
+
static THREAD_STATE: Cell<*mut ThreadState> = const { Cell::new(std::ptr::null_mut()) };
|
|
189
105
|
}
|
|
190
106
|
|
|
191
107
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
@@ -216,13 +132,7 @@ mod _functiontrace_rs {
|
|
|
216
132
|
/// `Box::into_raw`.
|
|
217
133
|
#[inline(always)]
|
|
218
134
|
pub fn thread_state() -> Option<&'static mut ThreadState> {
|
|
219
|
-
|
|
220
|
-
let tss_key = TSS_KEY.load(Ordering::Relaxed);
|
|
221
|
-
|
|
222
|
-
let state = unsafe {
|
|
223
|
-
// SAFETY: tss_key is a valid `pthread_key_t`
|
|
224
|
-
libc::pthread_getspecific(tss_key) as *mut ThreadState
|
|
225
|
-
};
|
|
135
|
+
let state = THREAD_STATE.get();
|
|
226
136
|
|
|
227
137
|
if state.is_null() {
|
|
228
138
|
// We shouldn't be asked to reset at thread that hasn't yet been registered.
|
|
@@ -241,7 +151,7 @@ mod _functiontrace_rs {
|
|
|
241
151
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
242
152
|
|
|
243
153
|
/// Initializes and returns the [`UnixStream`] we'll communicate to `functiontrace-server` on.
|
|
244
|
-
fn message_initialize() ->
|
|
154
|
+
fn message_initialize() -> UnixStream {
|
|
245
155
|
let sockaddr = SOCKADDR
|
|
246
156
|
.get()
|
|
247
157
|
.expect("Must be initialized before setting up messaging");
|
|
@@ -252,7 +162,7 @@ mod _functiontrace_rs {
|
|
|
252
162
|
loop {
|
|
253
163
|
match UnixStream::connect(sockaddr) {
|
|
254
164
|
Ok(s) => {
|
|
255
|
-
break
|
|
165
|
+
break s;
|
|
256
166
|
}
|
|
257
167
|
_ => {
|
|
258
168
|
if start.elapsed() > Duration::from_millis(1000) {
|
|
@@ -265,30 +175,6 @@ mod _functiontrace_rs {
|
|
|
265
175
|
}
|
|
266
176
|
}
|
|
267
177
|
|
|
268
|
-
/// Flush events to this socket once the given buffer is full according to `mpack`.
|
|
269
|
-
#[unsafe(no_mangle)]
|
|
270
|
-
extern "C" fn Mpack_Flush(writer: *const MpackWriter, buffer: *const u8, bytes: usize) {
|
|
271
|
-
let data = unsafe {
|
|
272
|
-
// SAFETY: `bytes` represents the initialized number of bytes in `buffer` when called
|
|
273
|
-
// by `mpack_writer_flush_message`.
|
|
274
|
-
std::slice::from_raw_parts(buffer, bytes)
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
let mut socket = unsafe {
|
|
278
|
-
// SAFETY: `writer` is valid and `writer->context` contains a `UnixStream` emitted from
|
|
279
|
-
// `register_thread`.
|
|
280
|
-
writer
|
|
281
|
-
.as_ref()
|
|
282
|
-
.expect("writer is non-null")
|
|
283
|
-
.context
|
|
284
|
-
.as_ref()
|
|
285
|
-
.expect("context is non-null")
|
|
286
|
-
};
|
|
287
|
-
if let Err(e) = socket.write_all(data) {
|
|
288
|
-
panic!("Socket send failed: {e}");
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
178
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
293
179
|
// Tracing Implementation
|
|
294
180
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
@@ -568,11 +454,10 @@ mod _functiontrace_rs {
|
|
|
568
454
|
Ok(())
|
|
569
455
|
}
|
|
570
456
|
|
|
571
|
-
/// Register a new thread, including creating the UnixStream
|
|
572
|
-
///
|
|
457
|
+
/// Register a new thread, including creating the UnixStream to log messages for this thread
|
|
458
|
+
/// on.
|
|
573
459
|
///
|
|
574
|
-
/// A new [`ThreadState`] will be returned, which
|
|
575
|
-
/// This state must be returned to Rust code later to be freed.
|
|
460
|
+
/// A new [`ThreadState`] will be returned, which must freed once the thread has terminated.
|
|
576
461
|
fn thread_register() -> Result<()> {
|
|
577
462
|
if thread_state().is_some() {
|
|
578
463
|
// We shouldn't have a ThreadState yet, as we're the ones that create it.
|
|
@@ -600,46 +485,21 @@ mod _functiontrace_rs {
|
|
|
600
485
|
.serialize(&mut rmp_serde::encode::Serializer::new(&mut socket))
|
|
601
486
|
.wrap_err("Failed to emit RegisterThread message")?;
|
|
602
487
|
|
|
603
|
-
//
|
|
604
|
-
// This will be stored in ThreadState, and will need to be freed by Rust code.
|
|
605
|
-
let buf = Box::into_raw(Box::new([0u8; MPACK_BUFFER])) as *mut u8;
|
|
606
|
-
|
|
607
|
-
// Initialize the `MpackWriter` for this thread, then associate the socket with it.
|
|
488
|
+
// Initialize the `ThreadState` for this thread, then associate the socket with it.
|
|
608
489
|
//
|
|
609
|
-
// We'll
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
buffer: buf as _,
|
|
618
|
-
current: buf as _,
|
|
619
|
-
end: unsafe {
|
|
620
|
-
// SAFETY: buf is an allocated array of MPACK_BUFFER bytes
|
|
621
|
-
buf.add(MPACK_BUFFER)
|
|
622
|
-
} as _,
|
|
623
|
-
|
|
624
|
-
teardown: std::ptr::null(),
|
|
625
|
-
error_fn: std::ptr::null(),
|
|
626
|
-
_error: 0,
|
|
627
|
-
},
|
|
628
|
-
));
|
|
490
|
+
// We'll move this state into thread local storage, and will need to recover it on
|
|
491
|
+
// termination in order to free it.
|
|
492
|
+
let state = Box::into_raw(Box::new(ThreadState {
|
|
493
|
+
socket,
|
|
494
|
+
|
|
495
|
+
buffer: [0u8; MSGPACK_BUFFER],
|
|
496
|
+
head: 0,
|
|
497
|
+
}));
|
|
629
498
|
|
|
630
499
|
// Store our state in thread-specific storage so we can find it in the future.
|
|
631
500
|
// Tracing begins for the thread at this point, since we've now published `state` for other
|
|
632
501
|
// functions to start using.
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
if unsafe {
|
|
636
|
-
// SAFETY: tss_key is a valid `pthread_key_t`, and `state` is a valid pointer to a
|
|
637
|
-
// heap-allocated `ThreadState` object
|
|
638
|
-
libc::pthread_setspecific(tss_key, state as *const _)
|
|
639
|
-
} != 0
|
|
640
|
-
{
|
|
641
|
-
return Err(eyre!("Failed to set tss_key on thread startup"));
|
|
642
|
-
}
|
|
502
|
+
THREAD_STATE.set(state);
|
|
643
503
|
|
|
644
504
|
Ok(())
|
|
645
505
|
}
|
|
@@ -652,7 +512,7 @@ mod _functiontrace_rs {
|
|
|
652
512
|
}
|
|
653
513
|
|
|
654
514
|
let writer = unsafe {
|
|
655
|
-
// SAFETY: writer is a valid pointer to an initialized `
|
|
515
|
+
// SAFETY: writer is a valid pointer to an initialized `ThreadState`.
|
|
656
516
|
&mut (state as *mut ThreadState)
|
|
657
517
|
.as_mut()
|
|
658
518
|
.expect("state is non-null")
|
|
@@ -664,7 +524,7 @@ mod _functiontrace_rs {
|
|
|
664
524
|
/// Tear down the given thread by flushing any outstanding messages.
|
|
665
525
|
///
|
|
666
526
|
/// This is called when the thread has shut down, including in multithread/process situations.
|
|
667
|
-
pub fn thread_teardown(writer: &mut
|
|
527
|
+
pub fn thread_teardown(writer: &mut ThreadState) -> Result<()> {
|
|
668
528
|
writer
|
|
669
529
|
.flush()
|
|
670
530
|
.wrap_err("Failed to flush remaining messages")?;
|
|
@@ -686,8 +546,6 @@ mod _functiontrace_rs {
|
|
|
686
546
|
///
|
|
687
547
|
/// This is useful when we've just forked and want to ensure we don't reuse an existing socket.
|
|
688
548
|
fn thread_reset() -> Result<()> {
|
|
689
|
-
let tss_key = TSS_KEY.load(Ordering::Relaxed);
|
|
690
|
-
|
|
691
549
|
if !STARTED.load(Ordering::Relaxed) {
|
|
692
550
|
// We haven't actually started yet, but are for some reason being asked
|
|
693
551
|
// to fork. This appears to be OS dependent.
|
|
@@ -698,30 +556,14 @@ mod _functiontrace_rs {
|
|
|
698
556
|
let state = thread_state().ok_or_eyre(eyre!("Thread wasn't yet registered"))?;
|
|
699
557
|
|
|
700
558
|
// Remove the TLS reference so no one else can load state after we've freed it.
|
|
701
|
-
|
|
702
|
-
// SAFETY: tss_key is a valid `pthread_key_t`, and `state` is a valid pointer to a
|
|
703
|
-
// heap-allocated `ThreadState` object
|
|
704
|
-
libc::pthread_setspecific(tss_key, std::ptr::null())
|
|
705
|
-
} != 0
|
|
706
|
-
{
|
|
707
|
-
return Err(eyre!("Failed to clear TLS"));
|
|
708
|
-
}
|
|
559
|
+
THREAD_STATE.set(std::ptr::null_mut());
|
|
709
560
|
|
|
710
|
-
|
|
711
|
-
// SAFETY: writer.buffer contains a valid pointer to a boxed buffer
|
|
712
|
-
(state.buffer as *mut [u8; MPACK_BUFFER])
|
|
713
|
-
.as_mut()
|
|
714
|
-
.expect("buffer is initialized when state is and is never modified")
|
|
715
|
-
};
|
|
716
|
-
|
|
717
|
-
// Free the thread's mpack state
|
|
561
|
+
// Free the thread's state, including the socket
|
|
718
562
|
unsafe {
|
|
719
|
-
|
|
563
|
+
// SAFETY: `state` came from `Box::into_raw` previously and can only be freed once.
|
|
720
564
|
let _ = Box::from_raw(state);
|
|
721
565
|
}
|
|
722
566
|
|
|
723
|
-
// TODO: Should we free the socket too?
|
|
724
|
-
|
|
725
567
|
// Create a new thread for this process. We don't need to start an entire new trace like
|
|
726
568
|
// with subprocess calls since we were forked and therefore already share our
|
|
727
569
|
// configuration.
|
|
@@ -816,7 +658,7 @@ mod _functiontrace_rs {
|
|
|
816
658
|
/// The core logging function for FunctionTrace. Configured by `PyEval_SetProfile`, and used
|
|
817
659
|
/// to record all function calls/returns.
|
|
818
660
|
extern "C" fn functiontrace(
|
|
819
|
-
|
|
661
|
+
_obj: *mut pyo3::ffi::PyObject,
|
|
820
662
|
frame: *mut pyo3::ffi::PyFrameObject,
|
|
821
663
|
what: i32,
|
|
822
664
|
arg: *mut pyo3::ffi::PyObject,
|
|
@@ -833,7 +675,6 @@ mod _functiontrace_rs {
|
|
|
833
675
|
}
|
|
834
676
|
|
|
835
677
|
let time = trace_time();
|
|
836
|
-
let c_functiontrace = *TRACEFUNC.get().expect("tracefunc is set in set_config");
|
|
837
678
|
|
|
838
679
|
// Return the function name when we're in a C_* context.
|
|
839
680
|
let c_func_name = || {
|
|
@@ -861,14 +702,88 @@ mod _functiontrace_rs {
|
|
|
861
702
|
Cow::Borrowed("<null>")
|
|
862
703
|
}
|
|
863
704
|
};
|
|
864
|
-
|
|
865
705
|
match what {
|
|
866
706
|
pyo3::ffi::PyTrace_CALL | pyo3::ffi::PyTrace_RETURN => {
|
|
867
|
-
//
|
|
868
|
-
//
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
707
|
+
// Cache information about each frame we see so that we don't need to look it up
|
|
708
|
+
// every time, which involves expensive Python operations.
|
|
709
|
+
static FRAMES: LazyLock<
|
|
710
|
+
dashmap::DashMap<usize, (Cow<'static, str>, Cow<'static, str>)>,
|
|
711
|
+
> = LazyLock::new(|| {
|
|
712
|
+
dashmap::DashMap::with_capacity(
|
|
713
|
+
// Even small programs will have a decent amount of function calls in the
|
|
714
|
+
// interpreter setup.
|
|
715
|
+
512,
|
|
716
|
+
)
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
let entry = FRAMES.entry(frame.addr()).or_insert_with(|| {
|
|
720
|
+
// We've never seen this frame before, so we'll need to load its
|
|
721
|
+
// information. This is fairly slow, since it'll involve going via Python
|
|
722
|
+
// rather than fast struct accesses.
|
|
723
|
+
let code = unsafe {
|
|
724
|
+
// SAFETY: frame is guaranteed to be a valid PyFrameObject
|
|
725
|
+
|
|
726
|
+
// We'll be storing `frame` in our hashmap, so we don't want anyone
|
|
727
|
+
// else freeing it.
|
|
728
|
+
pyo3::ffi::Py_IncRef(frame as *mut pyo3::ffi::PyObject);
|
|
729
|
+
|
|
730
|
+
pyo3::ffi::PyFrame_GetCode(frame) as *mut pyo3::ffi::PyObject
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
Python::with_gil(|py| {
|
|
734
|
+
let code = unsafe {
|
|
735
|
+
// SAFETY: code is guaranteed to be a valid PyCodeObject since it
|
|
736
|
+
// came from `PyFrame_GetCode`.
|
|
737
|
+
Bound::from_borrowed_ptr(py, code)
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
// Given a Python object that _should_ be a string, convert it into an
|
|
741
|
+
// a Rust string.
|
|
742
|
+
let unicode_converter = |pyobj: PyResult<Bound<'_, PyAny>>| {
|
|
743
|
+
pyobj
|
|
744
|
+
.map(|obj| {
|
|
745
|
+
if let Ok(filename) = obj.downcast::<pyo3::types::PyString>() {
|
|
746
|
+
Cow::from(filename.to_string_lossy().into_owned())
|
|
747
|
+
} else {
|
|
748
|
+
Cow::Borrowed("<UNKNOWN>")
|
|
749
|
+
}
|
|
750
|
+
})
|
|
751
|
+
.unwrap_or_else(|_| Cow::Borrowed("<NONE>"))
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
let filename =
|
|
755
|
+
unicode_converter(code.getattr(pyo3::intern!(py, "co_filename")));
|
|
756
|
+
let func_name = unicode_converter(code.getattr(if cfg!(Py_3_11) {
|
|
757
|
+
// co_qualname is better but was only added in 3.11.
|
|
758
|
+
pyo3::intern!(py, "co_qualname")
|
|
759
|
+
} else {
|
|
760
|
+
pyo3::intern!(py, "co_name")
|
|
761
|
+
}));
|
|
762
|
+
|
|
763
|
+
(filename, func_name)
|
|
764
|
+
})
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
if what == pyo3::ffi::PyTrace_CALL {
|
|
768
|
+
let linenumber = unsafe { pyo3::ffi::PyFrame_GetLineNumber(frame) }
|
|
769
|
+
.try_into()
|
|
770
|
+
.expect("Invalid line number for executing frame");
|
|
771
|
+
|
|
772
|
+
function_trace::FunctionTrace::Call {
|
|
773
|
+
filename: entry.0.clone(),
|
|
774
|
+
func_name: entry.1.clone(),
|
|
775
|
+
linenumber,
|
|
776
|
+
time,
|
|
777
|
+
}
|
|
778
|
+
.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
779
|
+
.expect("Failed to emit Call message");
|
|
780
|
+
} else {
|
|
781
|
+
function_trace::FunctionTrace::Return {
|
|
782
|
+
func_name: entry.1.clone(),
|
|
783
|
+
time,
|
|
784
|
+
}
|
|
785
|
+
.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
786
|
+
.expect("Failed to emit Return message")
|
|
872
787
|
}
|
|
873
788
|
}
|
|
874
789
|
pyo3::ffi::PyTrace_C_CALL => {
|