functiontrace 0.6.0__tar.gz → 0.6.2__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.6.0/functiontrace.egg-info → functiontrace-0.6.2}/PKG-INFO +3 -3
- {functiontrace-0.6.0 → functiontrace-0.6.2/functiontrace.egg-info}/PKG-INFO +3 -3
- {functiontrace-0.6.0 → functiontrace-0.6.2}/pyproject.toml +5 -2
- {functiontrace-0.6.0 → functiontrace-0.6.2}/rust/lib.rs +72 -88
- {functiontrace-0.6.0 → functiontrace-0.6.2}/Cargo.toml +0 -0
- {functiontrace-0.6.0 → functiontrace-0.6.2}/LICENSE.md +0 -0
- {functiontrace-0.6.0 → functiontrace-0.6.2}/MANIFEST.in +0 -0
- {functiontrace-0.6.0 → functiontrace-0.6.2}/README.rst +0 -0
- {functiontrace-0.6.0 → functiontrace-0.6.2}/build.rs +0 -0
- {functiontrace-0.6.0 → functiontrace-0.6.2}/functiontrace.egg-info/SOURCES.txt +0 -0
- {functiontrace-0.6.0 → functiontrace-0.6.2}/functiontrace.egg-info/dependency_links.txt +0 -0
- {functiontrace-0.6.0 → functiontrace-0.6.2}/functiontrace.egg-info/entry_points.txt +0 -0
- {functiontrace-0.6.0 → functiontrace-0.6.2}/functiontrace.egg-info/top_level.txt +0 -0
- {functiontrace-0.6.0 → functiontrace-0.6.2}/functiontrace.py +0 -0
- {functiontrace-0.6.0 → functiontrace-0.6.2}/rust/multiprocessing.sh.tpl +0 -0
- {functiontrace-0.6.0 → functiontrace-0.6.2}/setup.cfg +0 -0
- {functiontrace-0.6.0 → functiontrace-0.6.2}/setup.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: functiontrace
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: A profiling tool that traces your application's execution
|
|
5
|
-
Author: Alistair Dobke
|
|
6
|
-
|
|
5
|
+
Author: Matt Bryant, Alistair Dobke
|
|
6
|
+
Maintainer-email: Matt Bryant <functiontrace@programsareproofs.com>
|
|
7
7
|
License-Expression: LicenseRef-Prosperity-Public-License-3.0.0
|
|
8
8
|
Project-URL: homepage, https://functiontrace.com
|
|
9
9
|
Project-URL: repository, https://gitlab.com/mbryant/functiontrace
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: functiontrace
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: A profiling tool that traces your application's execution
|
|
5
|
-
Author: Alistair Dobke
|
|
6
|
-
|
|
5
|
+
Author: Matt Bryant, Alistair Dobke
|
|
6
|
+
Maintainer-email: Matt Bryant <functiontrace@programsareproofs.com>
|
|
7
7
|
License-Expression: LicenseRef-Prosperity-Public-License-3.0.0
|
|
8
8
|
Project-URL: homepage, https://functiontrace.com
|
|
9
9
|
Project-URL: repository, https://gitlab.com/mbryant/functiontrace
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "functiontrace"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.2"
|
|
4
4
|
description = "A profiling tool that traces your application's execution"
|
|
5
5
|
authors = [
|
|
6
|
-
{ name = "Matt Bryant"
|
|
6
|
+
{ name = "Matt Bryant" },
|
|
7
7
|
{ name = "Alistair Dobke" }
|
|
8
8
|
]
|
|
9
|
+
maintainers = [
|
|
10
|
+
{ name = "Matt Bryant", email = "functiontrace@programsareproofs.com" }
|
|
11
|
+
]
|
|
9
12
|
license = "LicenseRef-Prosperity-Public-License-3.0.0"
|
|
10
13
|
license-files = ["LICENSE.md"]
|
|
11
14
|
readme = "README.rst"
|
|
@@ -8,7 +8,7 @@ mod _functiontrace_rs {
|
|
|
8
8
|
use std::borrow::Cow;
|
|
9
9
|
use std::cell::RefCell;
|
|
10
10
|
use std::ffi::{CStr, OsString};
|
|
11
|
-
use std::io::Write;
|
|
11
|
+
use std::io::{BufWriter, Read, Write};
|
|
12
12
|
use std::os::unix::net::UnixStream;
|
|
13
13
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
14
14
|
use std::sync::{LazyLock, OnceLock};
|
|
@@ -29,25 +29,18 @@ mod _functiontrace_rs {
|
|
|
29
29
|
license_key: Option<String>,
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/// The size of messagepack buffers.
|
|
32
|
+
/// The size of messagepack buffers. This is picked somewhat arbitrarily, but should be
|
|
33
33
|
/// reasonably small since we'll need one per thread, and should be large enough that we
|
|
34
34
|
/// can fit many messages in before needing to do expensive UnixStream syscalls.
|
|
35
35
|
const MSGPACK_BUFFER: usize = 1 << 17;
|
|
36
36
|
|
|
37
37
|
/// Storage specific to each thread used for buffering/transmitting data.
|
|
38
38
|
pub struct ThreadState {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// TODO: Conceptually this should just be a `BufWriter`, but in practice using that
|
|
42
|
-
// triggers 0 byte writes that cause `functiontrace-server` to close the connection.
|
|
43
|
-
// Implementing this ourselves is pretty trivial, so...
|
|
44
|
-
buffer: [u8; MSGPACK_BUFFER],
|
|
45
|
-
head: usize,
|
|
39
|
+
writer: BufWriter<UnixStream>,
|
|
46
40
|
}
|
|
47
41
|
|
|
48
42
|
impl Write for ThreadState {
|
|
49
|
-
///
|
|
50
|
-
/// once full.
|
|
43
|
+
/// Buffer trace data for the socket while tracing is enabled.
|
|
51
44
|
fn write(&mut self, src: &[u8]) -> std::io::Result<usize> {
|
|
52
45
|
if !STARTED.load(Ordering::Relaxed) {
|
|
53
46
|
// We haven't started yet, so we shouldn't write anything out. This avoids logging
|
|
@@ -55,55 +48,12 @@ mod _functiontrace_rs {
|
|
|
55
48
|
return Ok(src.len());
|
|
56
49
|
}
|
|
57
50
|
|
|
58
|
-
|
|
59
|
-
let tail = self.head + bytes;
|
|
60
|
-
|
|
61
|
-
if tail > self.buffer.len() {
|
|
62
|
-
if bytes > self.buffer.len() {
|
|
63
|
-
// We'll never be able to fit this much.
|
|
64
|
-
return Err(std::io::Error::new(
|
|
65
|
-
std::io::ErrorKind::StorageFull,
|
|
66
|
-
"msgpack buffer too small",
|
|
67
|
-
));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// We can't fit any more data, so need to flush. The caller should retry after
|
|
71
|
-
// this.
|
|
72
|
-
self.flush()?;
|
|
73
|
-
|
|
74
|
-
return Err(std::io::Error::new(
|
|
75
|
-
std::io::ErrorKind::Interrupted,
|
|
76
|
-
"msgpack buffer is full, flushing",
|
|
77
|
-
));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
self.buffer[self.head..tail].copy_from_slice(src);
|
|
81
|
-
self.head = tail;
|
|
82
|
-
|
|
83
|
-
Ok(bytes)
|
|
51
|
+
self.writer.write(src)
|
|
84
52
|
}
|
|
85
53
|
|
|
86
54
|
/// Flush our local buffer to the socket.
|
|
87
55
|
fn flush(&mut self) -> std::io::Result<()> {
|
|
88
|
-
|
|
89
|
-
// don't miss it, since the buffer will be left in an invalid state if this doesn't
|
|
90
|
-
// happen.
|
|
91
|
-
let head = std::mem::take(&mut self.head);
|
|
92
|
-
|
|
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();
|
|
56
|
+
self.writer.flush()
|
|
107
57
|
}
|
|
108
58
|
}
|
|
109
59
|
|
|
@@ -427,8 +377,28 @@ mod _functiontrace_rs {
|
|
|
427
377
|
// Close the initialization socket to trigger `functiontrace-server` to
|
|
428
378
|
// read our message, since otherwise it'll wait forever for more data.
|
|
429
379
|
socket
|
|
430
|
-
.shutdown(std::net::Shutdown::
|
|
431
|
-
.wrap_err("Failed to
|
|
380
|
+
.shutdown(std::net::Shutdown::Write)
|
|
381
|
+
.wrap_err("Failed to finish initialization message")?;
|
|
382
|
+
|
|
383
|
+
// Modern `functiontrace-server`s send their version, allowing us to ensure
|
|
384
|
+
// protocols match.
|
|
385
|
+
let version = {
|
|
386
|
+
let mut reply = Vec::with_capacity(16);
|
|
387
|
+
socket
|
|
388
|
+
.read_to_end(&mut reply)
|
|
389
|
+
.wrap_err("Failed to read server version")?;
|
|
390
|
+
|
|
391
|
+
if reply.is_empty() {
|
|
392
|
+
// Old servers will close the socket without sending anything. We know they're
|
|
393
|
+
// older than 0.8.6, which is sufficient for us to handle.
|
|
394
|
+
"0.8.6".to_string()
|
|
395
|
+
} else {
|
|
396
|
+
rmp_serde::from_slice(&reply)
|
|
397
|
+
.wrap_err("Failed to parse server version as string")?
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
tracing::info!("Connected to functiontrace-server {version}");
|
|
432
402
|
}
|
|
433
403
|
|
|
434
404
|
// `multiprocessing` uses an absolute path to an executable to launch forkserver workers
|
|
@@ -616,10 +586,7 @@ mod _functiontrace_rs {
|
|
|
616
586
|
|
|
617
587
|
// Initialize the `ThreadState` for this thread, then associate the socket with it.
|
|
618
588
|
let state = Box::new(ThreadState {
|
|
619
|
-
socket,
|
|
620
|
-
|
|
621
|
-
buffer: [0u8; MSGPACK_BUFFER],
|
|
622
|
-
head: 0,
|
|
589
|
+
writer: BufWriter::with_capacity(MSGPACK_BUFFER, socket),
|
|
623
590
|
});
|
|
624
591
|
|
|
625
592
|
// Store our state in thread-specific storage so we can find it in the future.
|
|
@@ -633,8 +600,8 @@ mod _functiontrace_rs {
|
|
|
633
600
|
/// Tear down the given thread by dropping its [`ThreadState`], which will also flush any
|
|
634
601
|
/// outstanding messages.
|
|
635
602
|
///
|
|
636
|
-
/// This is called when the thread has shut down but we can't rely on
|
|
637
|
-
///
|
|
603
|
+
/// This is called when the thread has shut down but we can't rely on TLS destructors running,
|
|
604
|
+
/// including in multiprocess situations.
|
|
638
605
|
pub fn thread_teardown() {
|
|
639
606
|
let _ = THREAD_STATE.take();
|
|
640
607
|
}
|
|
@@ -656,12 +623,11 @@ mod _functiontrace_rs {
|
|
|
656
623
|
}
|
|
657
624
|
|
|
658
625
|
// Remove the old state without flushing the parent's socket.
|
|
659
|
-
let
|
|
626
|
+
let ThreadState { writer } = *THREAD_STATE
|
|
660
627
|
.take()
|
|
661
628
|
.ok_or_eyre(eyre!("Thread wasn't yet registered"))?;
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
drop(state);
|
|
629
|
+
let (socket, _buffer) = writer.into_parts();
|
|
630
|
+
drop(socket);
|
|
665
631
|
|
|
666
632
|
// Create a new thread for this process. We don't need to start an entire new trace like
|
|
667
633
|
// with subprocess calls since we were forked and therefore already share our configuration.
|
|
@@ -1664,11 +1630,13 @@ mod hooks {
|
|
|
1664
1630
|
|
|
1665
1631
|
/// A generic wrapper that parses out the arguments, calls the given handler function to handle
|
|
1666
1632
|
/// custom hooking logic, then proxies the arguments out to the original wrapped function.
|
|
1633
|
+
/// The handler emits events synchronously so they can borrow from its local Python objects.
|
|
1667
1634
|
fn proxy(
|
|
1668
1635
|
hook: &Hook,
|
|
1669
1636
|
handler: impl FnOnce(
|
|
1670
1637
|
&Bound<'_, pyo3::types::PyTuple>,
|
|
1671
|
-
|
|
1638
|
+
&mut dyn FnMut(function_trace::FunctionTrace<'_>),
|
|
1639
|
+
),
|
|
1672
1640
|
args: *mut PyObject,
|
|
1673
1641
|
kwargs: *mut PyObject,
|
|
1674
1642
|
) -> *mut PyObject {
|
|
@@ -1688,10 +1656,14 @@ mod hooks {
|
|
|
1688
1656
|
.map_err(Into::<PyErr>::into)
|
|
1689
1657
|
.wrap_err("Python args must be a tuple")?;
|
|
1690
1658
|
|
|
1691
|
-
|
|
1659
|
+
handler(args, &mut |msg| {
|
|
1692
1660
|
msg.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
1693
|
-
.
|
|
1694
|
-
|
|
1661
|
+
.expect(
|
|
1662
|
+
// TODO: This isn't entirely true, but you'd have to be actively
|
|
1663
|
+
// malicious to hit this case.
|
|
1664
|
+
"Hooks emit capped size events so can't fail to serialize",
|
|
1665
|
+
);
|
|
1666
|
+
});
|
|
1695
1667
|
Ok(())
|
|
1696
1668
|
})
|
|
1697
1669
|
.transpose()?;
|
|
@@ -1730,18 +1702,27 @@ mod hooks {
|
|
|
1730
1702
|
|
|
1731
1703
|
proxy(
|
|
1732
1704
|
hook,
|
|
1733
|
-
|args| {
|
|
1705
|
+
|args, emit| {
|
|
1734
1706
|
// Logging functions should roughly call `str()` on their arguments
|
|
1735
|
-
|
|
1736
|
-
let
|
|
1737
|
-
.
|
|
1738
|
-
.and_then(|
|
|
1739
|
-
.unwrap_or(
|
|
1707
|
+
let args = args.str().ok();
|
|
1708
|
+
let value = args
|
|
1709
|
+
.as_ref()
|
|
1710
|
+
.and_then(|args| args.to_str().ok())
|
|
1711
|
+
.unwrap_or("<invalid string>");
|
|
1740
1712
|
|
|
1741
|
-
|
|
1713
|
+
let value = if value.chars().count() > 1024 {
|
|
1714
|
+
let mut value: String = value.chars().take(1021).collect();
|
|
1715
|
+
value.push_str("...");
|
|
1716
|
+
|
|
1717
|
+
Cow::Owned(value)
|
|
1718
|
+
} else {
|
|
1719
|
+
Cow::Borrowed(value)
|
|
1720
|
+
};
|
|
1721
|
+
|
|
1722
|
+
emit(function_trace::FunctionTrace::Log {
|
|
1742
1723
|
time: trace_time(),
|
|
1743
1724
|
log_type: Cow::Borrowed(hook.target),
|
|
1744
|
-
log_value:
|
|
1725
|
+
log_value: value,
|
|
1745
1726
|
})
|
|
1746
1727
|
},
|
|
1747
1728
|
args,
|
|
@@ -1759,22 +1740,22 @@ mod hooks {
|
|
|
1759
1740
|
|
|
1760
1741
|
proxy(
|
|
1761
1742
|
hook,
|
|
1762
|
-
|args| {
|
|
1743
|
+
|args, emit| {
|
|
1763
1744
|
// `import` takes rather complicated arguments, but in practice we only care about
|
|
1764
1745
|
// the first argument (name)
|
|
1765
|
-
let
|
|
1746
|
+
let arg = args.get_item(0);
|
|
1747
|
+
let module = if let Ok(arg) = &arg {
|
|
1766
1748
|
arg.cast()
|
|
1767
1749
|
.map_err(Into::into)
|
|
1768
1750
|
.and_then(|module| module.to_str())
|
|
1769
|
-
.
|
|
1770
|
-
.unwrap_or(Cow::Borrowed("<unknown module>"))
|
|
1751
|
+
.unwrap_or("<unknown module>")
|
|
1771
1752
|
} else {
|
|
1772
|
-
|
|
1753
|
+
"<no module specified>"
|
|
1773
1754
|
};
|
|
1774
1755
|
|
|
1775
|
-
|
|
1756
|
+
emit(function_trace::FunctionTrace::Import {
|
|
1776
1757
|
time: trace_time(),
|
|
1777
|
-
module_name: module,
|
|
1758
|
+
module_name: Cow::Borrowed(module),
|
|
1778
1759
|
})
|
|
1779
1760
|
},
|
|
1780
1761
|
args,
|
|
@@ -1788,6 +1769,9 @@ mod hooks {
|
|
|
1788
1769
|
/// Anything that exits with `os._exit` (including `multiprocessing` by default) will
|
|
1789
1770
|
/// immediately terminate the process without running cleanup/`atexit()`. Hook this to ensure we
|
|
1790
1771
|
/// can flush as needed.
|
|
1772
|
+
///
|
|
1773
|
+
/// TODO: We should also track any other outstanding threads and flush their buffers, since they
|
|
1774
|
+
/// won't get the chance to cleanly exit.
|
|
1791
1775
|
extern "C" fn process_exit<const HOOK: usize>(
|
|
1792
1776
|
_self: *mut PyObject,
|
|
1793
1777
|
args: *mut PyObject,
|
|
@@ -1798,6 +1782,6 @@ mod hooks {
|
|
|
1798
1782
|
crate::_functiontrace_rs::thread_teardown();
|
|
1799
1783
|
|
|
1800
1784
|
// Directly pass the command on, since we don't need to log/etc for this.
|
|
1801
|
-
proxy(hook, |_args|
|
|
1785
|
+
proxy(hook, |_args, _emit| {}, args, kwargs)
|
|
1802
1786
|
}
|
|
1803
1787
|
}
|
|
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
|
|
File without changes
|