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.
@@ -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"] }
@@ -1,4 +1,3 @@
1
- graft mpack
2
1
  graft rust
3
2
  include Cargo.toml
4
3
  include build.rs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: functiontrace
3
- Version: 0.4.3
3
+ Version: 0.4.4
4
4
  Summary: A profiling tool that traces your application's execution
5
5
  Author: Alistair Dobke
6
6
  Author-email: Matt Bryant <functiontrace@programsareproofs.com>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: functiontrace
3
- Version: 0.4.3
3
+ Version: 0.4.4
4
4
  Summary: A profiling tool that traces your application's execution
5
5
  Author: Alistair Dobke
6
6
  Author-email: Matt Bryant <functiontrace@programsareproofs.com>
@@ -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,7 +5,6 @@ import sys
5
5
  import tempfile
6
6
  from importlib import machinery, util
7
7
 
8
- import _functiontrace # noqa: F401 - this import is load bearing since we need to run code on import
9
8
  import _functiontrace_rs as rust
10
9
 
11
10
  PYTHON_TEMPLATE = """#!/bin/sh
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "functiontrace"
3
- version = "0.4.3"
3
+ version = "0.4.4"
4
4
  description = "A profiling tool that traces your application's execution"
5
5
  authors = [
6
6
  { name = "Matt Bryant", email = "functiontrace@programsareproofs.com"},
@@ -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, AtomicU32, Ordering};
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 MPACK_BUFFER: usize = 1 << 17;
22
+ const MSGPACK_BUFFER: usize = 1 << 17;
22
23
 
23
24
  /// Storage specific to each thread used for buffering/transmitting data.
24
- type ThreadState = MpackWriter;
25
-
26
- /// A `mpack_writer_t` struct, which `mpack.h` will use to pass us our per-thread context in.
27
- ///
28
- /// NOTE: This must directly match the `mpack_writer_t` definition in C, since we're going to
29
- /// be creating and modifying it ourselves!
30
- #[repr(C)]
31
- pub struct MpackWriter {
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 MpackWriter {
47
- /// A small [`Write`] implementation that allows us to write to an
48
- /// `mpack_writer_t`'s interior buffer while keeping its state consistent.
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 bytes > len {
66
- if bytes >= MPACK_BUFFER {
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
- "mpack buffer too small",
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
- "mpack buffer is full, flushing",
63
+ "msgpack buffer is full, flushing",
81
64
  ));
82
65
  }
83
66
 
84
- let buf = unsafe { std::slice::from_raw_parts_mut(self.current, len) };
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
- /// This roughly implements `Mpack_Flush`.
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
- self.current = self.buffer;
78
+ let head = self.head;
79
+ self.head = 0;
111
80
 
112
- let mut socket = unsafe {
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
- /// The `Fprofile_FunctionTrace` function defined in the C extension. This is needed to finish
143
- /// moving setup to the Rust code.
144
- static TRACEFUNC: OnceLock<pyo3::ffi::Py_tracefunc> = OnceLock::new();
145
-
146
- ////////////////////////////////////////////////////////////////////////////////////////////////
147
- // Rust <-> C Communication
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
- // Check that we're able to properly initialize
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() -> Box<UnixStream> {
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 Box::new(s);
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 for mpack to log messages for this
572
- /// thread on.
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 contains a fully initialized `MpackWriter`.
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
- // Allocate a new buffer for mpack to write messages into.
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 return this state to the C code, which must pass it back to us to avoid leaking.
610
- let state = Box::into_raw(Box::new(
611
- // XXX: This must match `mpack_writer_init`, since mpack.c will be using this structure
612
- // later!
613
- MpackWriter {
614
- context: Box::into_raw(socket),
615
- flush: Mpack_Flush,
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
- let tss_key = TSS_KEY.load(Ordering::Relaxed);
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 `MpackWriter`.
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 MpackWriter) -> Result<()> {
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
- if unsafe {
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
- let buf = unsafe {
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
- let _ = Box::from_raw(buf);
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
- obj: *mut pyo3::ffi::PyObject,
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
- // For now we delegate these to the C implementation, since we can't access the
868
- // `PyCodeObject` fields that we require via Rust.
869
- unsafe {
870
- // SAFETY: tracefunc is a valid function pointer to `Fprofile_FunctionTrace`.
871
- return c_functiontrace(obj, frame, what, arg);
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 => {
@@ -0,0 +1,5 @@
1
+ from setuptools import setup
2
+
3
+
4
+ if __name__ == "__main__":
5
+ setup(py_modules=["functiontrace"])