functiontrace 0.5.0__tar.gz → 0.5.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.5.0 → functiontrace-0.5.2}/Cargo.toml +6 -6
- {functiontrace-0.5.0/functiontrace.egg-info → functiontrace-0.5.2}/PKG-INFO +3 -3
- {functiontrace-0.5.0 → functiontrace-0.5.2}/README.rst +1 -1
- {functiontrace-0.5.0 → functiontrace-0.5.2}/build.rs +12 -2
- {functiontrace-0.5.0 → functiontrace-0.5.2/functiontrace.egg-info}/PKG-INFO +3 -3
- {functiontrace-0.5.0 → functiontrace-0.5.2}/functiontrace.py +20 -5
- {functiontrace-0.5.0 → functiontrace-0.5.2}/pyproject.toml +8 -8
- {functiontrace-0.5.0 → functiontrace-0.5.2}/rust/lib.rs +164 -165
- {functiontrace-0.5.0 → functiontrace-0.5.2}/LICENSE.md +0 -0
- {functiontrace-0.5.0 → functiontrace-0.5.2}/MANIFEST.in +0 -0
- {functiontrace-0.5.0 → functiontrace-0.5.2}/functiontrace.egg-info/SOURCES.txt +0 -0
- {functiontrace-0.5.0 → functiontrace-0.5.2}/functiontrace.egg-info/dependency_links.txt +0 -0
- {functiontrace-0.5.0 → functiontrace-0.5.2}/functiontrace.egg-info/entry_points.txt +0 -0
- {functiontrace-0.5.0 → functiontrace-0.5.2}/functiontrace.egg-info/top_level.txt +0 -0
- {functiontrace-0.5.0 → functiontrace-0.5.2}/setup.cfg +0 -0
- {functiontrace-0.5.0 → functiontrace-0.5.2}/setup.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "_functiontrace_rs"
|
|
3
3
|
version = "0.1.0"
|
|
4
|
-
edition = "
|
|
4
|
+
edition = "2024"
|
|
5
5
|
|
|
6
6
|
[dependencies]
|
|
7
7
|
color-eyre = { version = "0.6", default-features = false }
|
|
@@ -10,17 +10,17 @@ 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.
|
|
14
|
-
reqwest = { version = "0.
|
|
13
|
+
pyo3 = { version = "0.29", features = ["eyre"] }
|
|
14
|
+
reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "rustls"] }
|
|
15
15
|
rmp-serde = "1.3"
|
|
16
16
|
serde = "1"
|
|
17
|
-
toml = "
|
|
17
|
+
toml = "1.1"
|
|
18
18
|
tracing = { version = "0.1", features = ["log"] }
|
|
19
19
|
tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "env-filter", "fmt", "std"] }
|
|
20
20
|
|
|
21
21
|
[build-dependencies]
|
|
22
|
-
toml = { version = "
|
|
23
|
-
pyo3-build-config = { version = "0.
|
|
22
|
+
toml = { version = "1.1", default-features = false, features = ["parse", "serde"] }
|
|
23
|
+
pyo3-build-config = { version = "0.29" }
|
|
24
24
|
|
|
25
25
|
[lib]
|
|
26
26
|
# See https://github.com/PyO3/pyo3 for details
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: functiontrace
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
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>
|
|
@@ -15,7 +15,7 @@ Classifier: Environment :: Console
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
17
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
18
|
-
Requires-Python: >=3.
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
19
|
Description-Content-Type: text/x-rst
|
|
20
20
|
License-File: LICENSE.md
|
|
21
21
|
Dynamic: license-file
|
|
@@ -28,7 +28,7 @@ Dependencies
|
|
|
28
28
|
------------
|
|
29
29
|
|
|
30
30
|
The FunctionTrace server must be installed for this package to work.
|
|
31
|
-
See installation instructions at https://functiontrace.com
|
|
31
|
+
See installation instructions at https://functiontrace.com/installation/.
|
|
32
32
|
|
|
33
33
|
Usage
|
|
34
34
|
-----
|
|
@@ -6,7 +6,7 @@ Dependencies
|
|
|
6
6
|
------------
|
|
7
7
|
|
|
8
8
|
The FunctionTrace server must be installed for this package to work.
|
|
9
|
-
See installation instructions at https://functiontrace.com
|
|
9
|
+
See installation instructions at https://functiontrace.com/installation/.
|
|
10
10
|
|
|
11
11
|
Usage
|
|
12
12
|
-----
|
|
@@ -5,14 +5,24 @@ fn main() -> std::io::Result<()> {
|
|
|
5
5
|
let pyproject = std::fs::read_to_string("pyproject.toml")?
|
|
6
6
|
.parse::<toml::Table>()
|
|
7
7
|
.expect("pyproject.toml is valid toml");
|
|
8
|
-
let
|
|
8
|
+
let project = pyproject
|
|
9
9
|
.get("project")
|
|
10
10
|
.and_then(|p| p.as_table())
|
|
11
|
-
.
|
|
11
|
+
.expect("project field is present in pyproject.toml");
|
|
12
|
+
|
|
13
|
+
let version = project
|
|
14
|
+
.get("version")
|
|
12
15
|
.and_then(|v| v.as_str())
|
|
13
16
|
.expect("version field is present in pyproject.toml");
|
|
14
17
|
println!("cargo::rustc-env=PACKAGE_VERSION={}", version);
|
|
15
18
|
|
|
19
|
+
let min_python = project
|
|
20
|
+
.get("requires-python")
|
|
21
|
+
.and_then(|v| v.as_str())
|
|
22
|
+
.and_then(|v| v.strip_prefix(">=3."))
|
|
23
|
+
.expect("requires-python field is present in pyproject.toml");
|
|
24
|
+
println!("cargo::rustc-env=MIN_PYTHON_VERSION={}", min_python);
|
|
25
|
+
|
|
16
26
|
// Figure out which Python version we're building for
|
|
17
27
|
pyo3_build_config::use_pyo3_cfgs();
|
|
18
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: functiontrace
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
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>
|
|
@@ -15,7 +15,7 @@ Classifier: Environment :: Console
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
17
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
18
|
-
Requires-Python: >=3.
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
19
|
Description-Content-Type: text/x-rst
|
|
20
20
|
License-File: LICENSE.md
|
|
21
21
|
Dynamic: license-file
|
|
@@ -28,7 +28,7 @@ Dependencies
|
|
|
28
28
|
------------
|
|
29
29
|
|
|
30
30
|
The FunctionTrace server must be installed for this package to work.
|
|
31
|
-
See installation instructions at https://functiontrace.com
|
|
31
|
+
See installation instructions at https://functiontrace.com/installation/.
|
|
32
32
|
|
|
33
33
|
Usage
|
|
34
34
|
-----
|
|
@@ -19,7 +19,12 @@ exec $(which {python}) -m functiontrace "$@"
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def setup_dependencies():
|
|
22
|
-
|
|
22
|
+
if f"{tempfile.gettempdir()}/py-functiontrace" in os.environ["PATH"]:
|
|
23
|
+
# We've already set up dependencies. This only needs to happen once, since otherwise we'll
|
|
24
|
+
# just make a bunch of useless temp directories.
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
# Generate a temp directory to store our wrappers in. We'll temporarily
|
|
23
28
|
# add this directory to our path.
|
|
24
29
|
tempdir = tempfile.mkdtemp(prefix="py-functiontrace")
|
|
25
30
|
os.environ["PATH"] = tempdir + os.pathsep + os.environ["PATH"]
|
|
@@ -27,7 +32,8 @@ def setup_dependencies():
|
|
|
27
32
|
# Generate wrappers for the various Python versions we support to ensure
|
|
28
33
|
# they're included in our PATH.
|
|
29
34
|
wrap_pythons = ["python", "python3"] + [
|
|
30
|
-
"python3.{}".format(minor)
|
|
35
|
+
"python3.{}".format(minor)
|
|
36
|
+
for minor in range(rust._min_python_version, rust._max_python_version + 1)
|
|
31
37
|
]
|
|
32
38
|
for python in wrap_pythons:
|
|
33
39
|
with open(os.path.join(tempdir, python), "w") as f:
|
|
@@ -88,10 +94,19 @@ def main():
|
|
|
88
94
|
# below, which needs to operate in the context of our new module.
|
|
89
95
|
# NOTE: We use SourceFileLoader, rather than allowing importlib to infer
|
|
90
96
|
# the proper loader, in order to support files without a .py extension.
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
mod = util.module_from_spec(
|
|
98
|
+
util.spec_from_loader(
|
|
99
|
+
"__main__", machinery.SourceFileLoader("__main__", target_file)
|
|
100
|
+
)
|
|
93
101
|
)
|
|
94
|
-
|
|
102
|
+
|
|
103
|
+
# Clear __spec__ to match Python's standard `python foo.py` loading
|
|
104
|
+
mod.__spec__ = None
|
|
105
|
+
# Record the file we're using so `multiprocessing` can load it (in a non-forked context).
|
|
106
|
+
# Without this, `multiprocessing` in `forkserver` contexts is asked to load functions that
|
|
107
|
+
# it can't find.
|
|
108
|
+
mod.__file__ = target_file
|
|
109
|
+
|
|
95
110
|
sys.modules.update({"__main__": mod})
|
|
96
111
|
|
|
97
112
|
# Ensure we're setup to be able to run.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "functiontrace"
|
|
3
|
-
version = "0.5.
|
|
3
|
+
version = "0.5.2"
|
|
4
4
|
description = "A profiling tool that traces your application's execution"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Matt Bryant", email = "functiontrace@programsareproofs.com"},
|
|
@@ -9,13 +9,13 @@ authors = [
|
|
|
9
9
|
license = "LicenseRef-Prosperity-Public-License-3.0.0"
|
|
10
10
|
license-files = ["LICENSE.md"]
|
|
11
11
|
readme = "README.rst"
|
|
12
|
-
# NOTE: Actively tested on Python 3.
|
|
13
|
-
# versions due to C extension hacks.
|
|
14
|
-
# new Python versions to simply install very old versions of
|
|
15
|
-
# didn't have the `requires-python` annotation on them and
|
|
16
|
-
# work.
|
|
17
|
-
# with new Python releases.
|
|
18
|
-
requires-python = ">=3.
|
|
12
|
+
# NOTE: Actively tested on Python 3.10 - 3.14 and may plausibly break on
|
|
13
|
+
# greater versions due to C extension hacks. However, setting a maximum version
|
|
14
|
+
# causes new Python versions to simply install very old versions of
|
|
15
|
+
# Functiontrace that didn't have the `requires-python` annotation on them and
|
|
16
|
+
# are also unlikely to work. Best to only set the minimum and let bug reports
|
|
17
|
+
# notify us of issues with new Python releases.
|
|
18
|
+
requires-python = ">=3.10"
|
|
19
19
|
keywords = [
|
|
20
20
|
"profiler",
|
|
21
21
|
"debugger"
|
|
@@ -7,7 +7,7 @@ mod _functiontrace_rs {
|
|
|
7
7
|
use serde::{Deserialize, Serialize};
|
|
8
8
|
use std::borrow::Cow;
|
|
9
9
|
use std::cell::Cell;
|
|
10
|
-
use std::ffi::{CStr, OsString
|
|
10
|
+
use std::ffi::{CStr, OsString};
|
|
11
11
|
use std::io::Write;
|
|
12
12
|
use std::os::unix::net::UnixStream;
|
|
13
13
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
@@ -148,7 +148,7 @@ mod _functiontrace_rs {
|
|
|
148
148
|
let state = THREAD_STATE.get();
|
|
149
149
|
|
|
150
150
|
if state.is_null() {
|
|
151
|
-
// We shouldn't be asked to reset
|
|
151
|
+
// We shouldn't be asked to reset a thread that hasn't yet been registered.
|
|
152
152
|
return None;
|
|
153
153
|
}
|
|
154
154
|
|
|
@@ -195,10 +195,20 @@ mod _functiontrace_rs {
|
|
|
195
195
|
/// Initialization code run on this module's import (mostly useful for adding constants)
|
|
196
196
|
#[pymodule_init]
|
|
197
197
|
fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
198
|
-
// Arbitrary code to run at the module initialization
|
|
199
198
|
m.add("__version__", env!("PACKAGE_VERSION"))
|
|
200
199
|
.wrap_err("Failed to expose PACKAGE_VERSION")?;
|
|
201
200
|
|
|
201
|
+
// Expose the minimum and maximum Python 3 versions we support
|
|
202
|
+
m.add(
|
|
203
|
+
"_min_python_version",
|
|
204
|
+
env!("MIN_PYTHON_VERSION")
|
|
205
|
+
.parse::<u32>()
|
|
206
|
+
.wrap_err("Failed to convert MIN_PYTHON_VERSION to int")?,
|
|
207
|
+
)
|
|
208
|
+
.wrap_err("Failed to expose _min_python_version")?;
|
|
209
|
+
m.add("_max_python_version", 14)
|
|
210
|
+
.wrap_err("Failed to expose _max_python_version")?;
|
|
211
|
+
|
|
202
212
|
color_eyre::install().wrap_err("Failed to install color_eyre")?;
|
|
203
213
|
|
|
204
214
|
// If someone else has already installed a tracing subscriber, we'll fail to initialize
|
|
@@ -244,6 +254,57 @@ mod _functiontrace_rs {
|
|
|
244
254
|
return Err(eyre!("FunctionTrace is already initialized!"));
|
|
245
255
|
}
|
|
246
256
|
|
|
257
|
+
// Store the processes' args, since threads will need it as part of the registration message.
|
|
258
|
+
ARGV.set({
|
|
259
|
+
// Parse args out of `sys.argv`, since it excludes the Python executable and shows the
|
|
260
|
+
// script's path first. This is much more complicated than using `std::env::args`, but
|
|
261
|
+
// is more practical.
|
|
262
|
+
Python::attach(|py| -> Result<String> {
|
|
263
|
+
let argv = py
|
|
264
|
+
.import("sys")
|
|
265
|
+
.and_then(|sys| sys.getattr("argv"))
|
|
266
|
+
.wrap_err("Failed to access sys.argv")?;
|
|
267
|
+
|
|
268
|
+
let args = argv
|
|
269
|
+
.cast::<pyo3::types::PyList>()
|
|
270
|
+
.map_err(|e| eyre!("Failed to read sys.argv as list: {}", e))?;
|
|
271
|
+
|
|
272
|
+
let mut full_args = args.iter().fold(String::new(), |s, arg| {
|
|
273
|
+
// Fetch the next arg as a string if possible.
|
|
274
|
+
let arg = arg
|
|
275
|
+
.cast::<pyo3::types::PyString>()
|
|
276
|
+
.map_err(|e| eyre!("Failed to read sys.argv[x] as string: {}", e))
|
|
277
|
+
.and_then(|arg| {
|
|
278
|
+
arg.to_str()
|
|
279
|
+
.wrap_err("Failed to read Rust string out of PyString")
|
|
280
|
+
})
|
|
281
|
+
.unwrap_or("<UNKNOWN>");
|
|
282
|
+
|
|
283
|
+
s + " " + arg
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// We'll display this in the UI, so ensure it's under (the rather
|
|
287
|
+
// arbitrary) 100 chars.
|
|
288
|
+
if full_args.chars().count() > 95 {
|
|
289
|
+
full_args.truncate(95);
|
|
290
|
+
full_args.push_str(" ...");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// We'll be cloning this
|
|
294
|
+
full_args.shrink_to_fit();
|
|
295
|
+
Ok(full_args)
|
|
296
|
+
})
|
|
297
|
+
.unwrap_or_else(|_err| {
|
|
298
|
+
tracing::warn!("Failed to read ARGV");
|
|
299
|
+
|
|
300
|
+
"<UNKNOWN>".into()
|
|
301
|
+
})
|
|
302
|
+
})
|
|
303
|
+
.expect("ARGV is only set in `trace_initialization`");
|
|
304
|
+
|
|
305
|
+
/////////////////////////////////////////////////////////////////////////
|
|
306
|
+
// Server initialization
|
|
307
|
+
/////////////////////////////////////////////////////////////////////////
|
|
247
308
|
// Check if we're in a subprocess of a command being run under functiontrace. If we are,
|
|
248
309
|
// we should connect to the same socket. Otherwise, we should setup the full profiling
|
|
249
310
|
// configuration ourselves.
|
|
@@ -253,155 +314,105 @@ mod _functiontrace_rs {
|
|
|
253
314
|
SOCKADDR
|
|
254
315
|
.set(existing_sockaddr)
|
|
255
316
|
.expect("sockaddr is only set in `trace_initialization`");
|
|
256
|
-
|
|
257
|
-
|
|
317
|
+
} else {
|
|
318
|
+
// Launch the functiontrace-server as a daemon, then record the address of the socket we'll
|
|
319
|
+
// need to communicate with it on.
|
|
320
|
+
let sockaddr = {
|
|
321
|
+
let server = match std::process::Command::new("functiontrace-server")
|
|
322
|
+
.arg("--directory")
|
|
323
|
+
.arg(output_directory)
|
|
324
|
+
.spawn()
|
|
325
|
+
{
|
|
326
|
+
Ok(server) => server,
|
|
327
|
+
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
|
328
|
+
eprintln!("Unable to find `functiontrace-server` in the current $PATH.");
|
|
329
|
+
eprintln!(
|
|
330
|
+
"See https://functiontrace.com/installation/ for installation instructions."
|
|
331
|
+
);
|
|
258
332
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
.spawn()
|
|
266
|
-
{
|
|
267
|
-
Ok(server) => server,
|
|
268
|
-
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
|
269
|
-
eprintln!("Unable to find `functiontrace-server` in the current $PATH.");
|
|
270
|
-
eprintln!(
|
|
271
|
-
"See https://functiontrace.com#installation for installation instructions."
|
|
272
|
-
);
|
|
333
|
+
// Exit here rather than returning an error, since that'll emit a confusing
|
|
334
|
+
// Python stacktrace.
|
|
335
|
+
std::process::exit(1);
|
|
336
|
+
}
|
|
337
|
+
e => e.wrap_err("Failed to launch functiontrace-server")?,
|
|
338
|
+
};
|
|
273
339
|
|
|
274
|
-
|
|
275
|
-
// Python stacktrace.
|
|
276
|
-
std::process::exit(1);
|
|
277
|
-
}
|
|
278
|
-
e => e.wrap_err("Failed to launch functiontrace-server")?,
|
|
340
|
+
format!("/tmp/functiontrace-server.sock.{}", server.id()).into()
|
|
279
341
|
};
|
|
280
342
|
|
|
281
|
-
|
|
282
|
-
|
|
343
|
+
Python::attach(|_| {
|
|
344
|
+
// Register an env var so subprocesses know how to connect to our
|
|
345
|
+
// profile.
|
|
346
|
+
unsafe {
|
|
347
|
+
// SAFETY: `set_var` can only be done while no one else is reading or writing to the
|
|
348
|
+
// environment. We're holding the GIL, so this _should_ be true, but there's no real
|
|
349
|
+
// way to enforce this.
|
|
350
|
+
//
|
|
351
|
+
// This doesn't seem to blow up in C even when we don't have the GIL, so
|
|
352
|
+
// it's hopefully good enough in practice since there's not really an alternative.
|
|
353
|
+
std::env::set_var(BREADCRUMB, &sockaddr);
|
|
354
|
+
}
|
|
355
|
+
});
|
|
283
356
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
// SAFETY: `set_var` can only be done while no one else is reading or writing to the
|
|
289
|
-
// environment. We're holding the GIL, so this _should_ be true, but there's no real
|
|
290
|
-
// way to enforce this.
|
|
291
|
-
//
|
|
292
|
-
// This doesn't seem to blow up in C even when we don't have the GIL, so
|
|
293
|
-
// it's hopefully good enough in practice since there's not really an alternative.
|
|
294
|
-
std::env::set_var(BREADCRUMB, &sockaddr);
|
|
295
|
-
}
|
|
296
|
-
});
|
|
357
|
+
// Store the server's address so our threads know how to talk to it.
|
|
358
|
+
SOCKADDR
|
|
359
|
+
.set(sockaddr)
|
|
360
|
+
.expect("sockaddr is only set in `trace_initialization`");
|
|
297
361
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
// `std::env::args`, but is more practical.
|
|
310
|
-
let args_prefix = Python::with_gil(|py| -> Result<String> {
|
|
311
|
-
let argv = py
|
|
312
|
-
.import("sys")
|
|
313
|
-
.and_then(|sys| sys.getattr("argv"))
|
|
314
|
-
.wrap_err("Failed to access sys.argv")?;
|
|
315
|
-
|
|
316
|
-
let args = argv
|
|
317
|
-
.downcast::<pyo3::types::PyList>()
|
|
318
|
-
.map_err(|e| eyre!("Failed to read sys.argv as list: {}", e))?;
|
|
319
|
-
|
|
320
|
-
let mut full_args = args.iter().fold(String::new(), |s, arg| {
|
|
321
|
-
// Fetch the next arg as a string if possible.
|
|
322
|
-
let arg = arg
|
|
323
|
-
.downcast::<pyo3::types::PyString>()
|
|
324
|
-
.map_err(|e| eyre!("Failed to read sys.argv[x] as string: {}", e))
|
|
325
|
-
.and_then(|arg| {
|
|
326
|
-
arg.to_str()
|
|
327
|
-
.wrap_err("Failed to read Rust string out of PyString")
|
|
328
|
-
})
|
|
329
|
-
.unwrap_or("<UNKNOWN>");
|
|
330
|
-
|
|
331
|
-
s + " " + arg
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
// We'll display this in the UI, so ensure it's under (the rather
|
|
335
|
-
// arbitrary) 100 chars.
|
|
336
|
-
if full_args.chars().count() > 95 {
|
|
337
|
-
full_args.truncate(95);
|
|
338
|
-
full_args.push_str(" ...");
|
|
339
|
-
}
|
|
362
|
+
// Send an initialization message to the server with some information about us. This will
|
|
363
|
+
// only be sent once, and all future threads/subprocesses will be associated underneath us.
|
|
364
|
+
let init = {
|
|
365
|
+
let python_version = Python::attach(|py| {
|
|
366
|
+
let version = py.version_info();
|
|
367
|
+
|
|
368
|
+
format!(
|
|
369
|
+
"Python {}.{}.{}",
|
|
370
|
+
version.major, version.minor, version.patch
|
|
371
|
+
)
|
|
372
|
+
});
|
|
340
373
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
374
|
+
let platform = Python::attach(|py| -> Result<String> {
|
|
375
|
+
let platform = py
|
|
376
|
+
.import("sys")
|
|
377
|
+
.and_then(|sys| sys.getattr("platform"))
|
|
378
|
+
.wrap_err("Failed to access sys.platform")?;
|
|
379
|
+
|
|
380
|
+
Ok(platform
|
|
381
|
+
.cast::<pyo3::types::PyString>()
|
|
382
|
+
.map_err(|e| eyre!("Failed to read sys.platform as string: {}", e))?
|
|
383
|
+
.to_str()
|
|
384
|
+
.wrap_err("Failed to read Rust string out of PyString")?
|
|
385
|
+
.into())
|
|
344
386
|
})
|
|
345
387
|
.unwrap_or_else(|_err| {
|
|
346
|
-
tracing::warn!("Failed to read
|
|
388
|
+
tracing::warn!("Failed to read sys.platform");
|
|
347
389
|
|
|
348
390
|
"<UNKNOWN>".into()
|
|
349
391
|
});
|
|
350
392
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
393
|
+
function_trace::TraceInitialization {
|
|
394
|
+
program_name: ARGV
|
|
395
|
+
.get()
|
|
396
|
+
.expect("ARGV is set earlier in trace_initialization")
|
|
397
|
+
.clone(),
|
|
398
|
+
program_version: format!("py-functiontrace {}", env!("PACKAGE_VERSION")),
|
|
399
|
+
lang_version: python_version,
|
|
400
|
+
platform,
|
|
401
|
+
time: trace_time(),
|
|
402
|
+
}
|
|
356
403
|
};
|
|
357
404
|
|
|
358
|
-
let
|
|
359
|
-
let version = py.version_info();
|
|
405
|
+
let mut socket = message_initialize();
|
|
360
406
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
version.major, version.minor, version.patch
|
|
364
|
-
)
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
let platform = Python::with_gil(|py| -> Result<String> {
|
|
368
|
-
let platform = py
|
|
369
|
-
.import("sys")
|
|
370
|
-
.and_then(|sys| sys.getattr("platform"))
|
|
371
|
-
.wrap_err("Failed to access sys.platform")?;
|
|
372
|
-
|
|
373
|
-
Ok(platform
|
|
374
|
-
.downcast::<pyo3::types::PyString>()
|
|
375
|
-
.map_err(|e| eyre!("Failed to read sys.platform as string: {}", e))?
|
|
376
|
-
.to_str()
|
|
377
|
-
.wrap_err("Failed to read Rust string out of PyString")?
|
|
378
|
-
.into())
|
|
379
|
-
})
|
|
380
|
-
.unwrap_or_else(|_err| {
|
|
381
|
-
tracing::warn!("Failed to read sys.platform");
|
|
382
|
-
|
|
383
|
-
"<UNKNOWN>".into()
|
|
384
|
-
});
|
|
407
|
+
init.serialize(&mut rmp_serde::encode::Serializer::new(&mut socket))
|
|
408
|
+
.wrap_err("Failed to emit TraceInitialization message")?;
|
|
385
409
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
}
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
let mut socket = message_initialize();
|
|
396
|
-
|
|
397
|
-
init.serialize(&mut rmp_serde::encode::Serializer::new(&mut socket))
|
|
398
|
-
.wrap_err("Failed to emit TraceInitialization message")?;
|
|
399
|
-
|
|
400
|
-
// Close the initialization socket to trigger `functiontrace-server` to
|
|
401
|
-
// read our message, since otherwise it'll wait forever for more data.
|
|
402
|
-
socket
|
|
403
|
-
.shutdown(std::net::Shutdown::Both)
|
|
404
|
-
.wrap_err("Failed to close initialization socket")?;
|
|
410
|
+
// Close the initialization socket to trigger `functiontrace-server` to
|
|
411
|
+
// read our message, since otherwise it'll wait forever for more data.
|
|
412
|
+
socket
|
|
413
|
+
.shutdown(std::net::Shutdown::Both)
|
|
414
|
+
.wrap_err("Failed to close initialization socket")?;
|
|
415
|
+
}
|
|
405
416
|
|
|
406
417
|
////////////////////////////////////////////////////////////////////////////
|
|
407
418
|
// Teardown Configuration
|
|
@@ -422,7 +433,7 @@ mod _functiontrace_rs {
|
|
|
422
433
|
//
|
|
423
434
|
// NOTE: We specifically do this rather than `Py_AtExit` since we want to run before all
|
|
424
435
|
// the Python teardown occurs.
|
|
425
|
-
Python::
|
|
436
|
+
Python::attach(|py| -> Result<()> {
|
|
426
437
|
py.import("atexit")
|
|
427
438
|
.and_then(|atexit| atexit.getattr("register"))
|
|
428
439
|
.wrap_err("Failed to access atexit.register")?
|
|
@@ -449,12 +460,13 @@ mod _functiontrace_rs {
|
|
|
449
460
|
|
|
450
461
|
// Register our tracing functions with Python - both the normal one and the multithreaded
|
|
451
462
|
// setup trampoline.
|
|
463
|
+
// TODO: PyEval_SetProfileAllThreads in >=3.12 should simplify this.
|
|
452
464
|
unsafe {
|
|
453
465
|
// SAFETY: both parameters are of the proper type.
|
|
454
466
|
pyo3::ffi::PyEval_SetProfile(Some(functiontrace), std::ptr::null_mut())
|
|
455
467
|
};
|
|
456
468
|
|
|
457
|
-
Python::
|
|
469
|
+
Python::attach(|py| -> Result<()> {
|
|
458
470
|
py.import("threading")
|
|
459
471
|
.and_then(|threading| threading.getattr("setprofile"))
|
|
460
472
|
.wrap_err("Failed to access threading.setprofile")?
|
|
@@ -550,23 +562,6 @@ mod _functiontrace_rs {
|
|
|
550
562
|
Ok(())
|
|
551
563
|
}
|
|
552
564
|
|
|
553
|
-
#[unsafe(no_mangle)]
|
|
554
|
-
extern "C" fn c_thread_teardown(state: *mut c_void) {
|
|
555
|
-
if state.is_null() {
|
|
556
|
-
// We never fully initialized this thread, so skip teardown.
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
let writer = unsafe {
|
|
561
|
-
// SAFETY: writer is a valid pointer to an initialized `ThreadState`.
|
|
562
|
-
&mut (state as *mut ThreadState)
|
|
563
|
-
.as_mut()
|
|
564
|
-
.expect("state is non-null")
|
|
565
|
-
};
|
|
566
|
-
|
|
567
|
-
thread_teardown(writer).expect("Failed to tear down FunctionTrace thread")
|
|
568
|
-
}
|
|
569
|
-
|
|
570
565
|
/// Tear down the given thread by flushing any outstanding messages.
|
|
571
566
|
///
|
|
572
567
|
/// This is called when the thread has shut down, including in multithread/process situations.
|
|
@@ -625,7 +620,11 @@ mod _functiontrace_rs {
|
|
|
625
620
|
/// thread state, before replacing itself with the normal Fprofile_FunctionTrace handler.
|
|
626
621
|
#[pyfunction]
|
|
627
622
|
#[pyo3(name = "_thread_trace_trampoline")]
|
|
628
|
-
fn thread_trace_trampoline(
|
|
623
|
+
fn thread_trace_trampoline(
|
|
624
|
+
_frame: Py<PyAny>,
|
|
625
|
+
_event: Py<PyAny>,
|
|
626
|
+
_arg: Py<PyAny>,
|
|
627
|
+
) -> Result<()> {
|
|
629
628
|
// Register the current thread for tracing
|
|
630
629
|
thread_register().wrap_err("Failed to register new FunctionTrace thread")?;
|
|
631
630
|
|
|
@@ -776,7 +775,7 @@ mod _functiontrace_rs {
|
|
|
776
775
|
pyo3::ffi::PyFrame_GetCode(frame) as *mut pyo3::ffi::PyObject
|
|
777
776
|
};
|
|
778
777
|
|
|
779
|
-
Python::
|
|
778
|
+
Python::attach(|py| {
|
|
780
779
|
let code = unsafe {
|
|
781
780
|
// SAFETY: code is guaranteed to be a valid PyCodeObject since it
|
|
782
781
|
// came from `PyFrame_GetCode`.
|
|
@@ -788,7 +787,7 @@ mod _functiontrace_rs {
|
|
|
788
787
|
let unicode_converter = |pyobj: PyResult<Bound<'_, PyAny>>| {
|
|
789
788
|
pyobj
|
|
790
789
|
.map(|obj| {
|
|
791
|
-
if let Ok(filename) = obj.
|
|
790
|
+
if let Ok(filename) = obj.cast::<pyo3::types::PyString>() {
|
|
792
791
|
Cow::from(filename.to_string_lossy().into_owned())
|
|
793
792
|
} else {
|
|
794
793
|
Cow::Borrowed("<UNKNOWN>")
|
|
@@ -845,13 +844,13 @@ mod _functiontrace_rs {
|
|
|
845
844
|
if !func.m_module.is_null() {
|
|
846
845
|
// NOTE: _Typically_ `m_module` is a PyModule, but there are no requirements on
|
|
847
846
|
// what type it needs to be! In practice, we only see PyModules and PyStrings.
|
|
848
|
-
Python::
|
|
847
|
+
Python::attach(|py| {
|
|
849
848
|
let m_module = unsafe {
|
|
850
849
|
// SAFETY: m_module is a non-NULL pointer to a valid Python object.
|
|
851
850
|
Bound::from_borrowed_ptr(py, func.m_module)
|
|
852
851
|
};
|
|
853
852
|
|
|
854
|
-
if let Ok(module) = m_module.
|
|
853
|
+
if let Ok(module) = m_module.cast::<pyo3::types::PyModule>() {
|
|
855
854
|
// This is a PyModule, so get its name if possible.
|
|
856
855
|
let name = module.name();
|
|
857
856
|
let module_name = if let Ok(name) = &name {
|
|
@@ -868,7 +867,7 @@ mod _functiontrace_rs {
|
|
|
868
867
|
.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
869
868
|
} else {
|
|
870
869
|
let module_name =
|
|
871
|
-
if let Ok(module) = m_module.
|
|
870
|
+
if let Ok(module) = m_module.cast::<pyo3::types::PyString>() {
|
|
872
871
|
module.to_cow().unwrap_or(Cow::Borrowed("<invalid string>"))
|
|
873
872
|
} else {
|
|
874
873
|
if let Ok(typ) = m_module.get_type().qualname().map(|x| x.to_string()) {
|
|
@@ -1114,7 +1113,7 @@ mod _functiontrace_rs {
|
|
|
1114
1113
|
if let Some(result) = parser(&input) {
|
|
1115
1114
|
return Ok(result);
|
|
1116
1115
|
}
|
|
1117
|
-
Python::
|
|
1116
|
+
Python::attach(|py| {
|
|
1118
1117
|
// We need to check with the Python interpreter during loops, otherwise we
|
|
1119
1118
|
// might not receive ctrl-c events.
|
|
1120
1119
|
py.check_signals()
|
|
@@ -1496,7 +1495,7 @@ mod hooks {
|
|
|
1496
1495
|
/// function, then attaching enough information about the original function to call it.
|
|
1497
1496
|
pub fn install() -> Result<()> {
|
|
1498
1497
|
for hook in HOOKS.iter() {
|
|
1499
|
-
Python::
|
|
1498
|
+
Python::attach(|py| -> Result<()> {
|
|
1500
1499
|
let (module_name, method_name) = hook
|
|
1501
1500
|
.target
|
|
1502
1501
|
.rsplit_once(".")
|
|
@@ -1638,14 +1637,14 @@ mod hooks {
|
|
|
1638
1637
|
.get()
|
|
1639
1638
|
.expect("Wrapper functions can't be called until hooked");
|
|
1640
1639
|
|
|
1641
|
-
let proxied = Python::
|
|
1640
|
+
let proxied = Python::attach(|py| -> Result<()> {
|
|
1642
1641
|
if let Some(mut writer) = crate::_functiontrace_rs::thread_state() {
|
|
1643
1642
|
let args = unsafe {
|
|
1644
1643
|
// SAFETY: `args` represents a valid argument tuple
|
|
1645
1644
|
Bound::from_borrowed_ptr(py, args)
|
|
1646
1645
|
};
|
|
1647
1646
|
let args = args
|
|
1648
|
-
.
|
|
1647
|
+
.cast()
|
|
1649
1648
|
.map_err(Into::<PyErr>::into)
|
|
1650
1649
|
.wrap_err("Python args must be a tuple")?;
|
|
1651
1650
|
|
|
@@ -1722,7 +1721,7 @@ mod hooks {
|
|
|
1722
1721
|
// `import` takes rather complicated arguments, but in practice we only care about
|
|
1723
1722
|
// the first argument (name)
|
|
1724
1723
|
let module = if let Ok(arg) = args.get_item(0) {
|
|
1725
|
-
arg.
|
|
1724
|
+
arg.cast()
|
|
1726
1725
|
.map_err(Into::into)
|
|
1727
1726
|
.and_then(|module| module.to_str())
|
|
1728
1727
|
.map(|module| Cow::Owned(module.to_string()))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|