functiontrace 0.5.1__tar.gz → 0.5.3__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.1 → functiontrace-0.5.3}/Cargo.toml +6 -6
- {functiontrace-0.5.1/functiontrace.egg-info → functiontrace-0.5.3}/PKG-INFO +3 -3
- {functiontrace-0.5.1 → functiontrace-0.5.3}/README.rst +1 -1
- {functiontrace-0.5.1 → functiontrace-0.5.3/functiontrace.egg-info}/PKG-INFO +3 -3
- {functiontrace-0.5.1 → functiontrace-0.5.3}/functiontrace.egg-info/SOURCES.txt +2 -1
- {functiontrace-0.5.1 → functiontrace-0.5.3}/functiontrace.py +18 -4
- {functiontrace-0.5.1 → functiontrace-0.5.3}/pyproject.toml +8 -8
- {functiontrace-0.5.1 → functiontrace-0.5.3}/rust/lib.rs +97 -43
- functiontrace-0.5.3/rust/multiprocessing.sh.tpl +53 -0
- {functiontrace-0.5.1 → functiontrace-0.5.3}/LICENSE.md +0 -0
- {functiontrace-0.5.1 → functiontrace-0.5.3}/MANIFEST.in +0 -0
- {functiontrace-0.5.1 → functiontrace-0.5.3}/build.rs +0 -0
- {functiontrace-0.5.1 → functiontrace-0.5.3}/functiontrace.egg-info/dependency_links.txt +0 -0
- {functiontrace-0.5.1 → functiontrace-0.5.3}/functiontrace.egg-info/entry_points.txt +0 -0
- {functiontrace-0.5.1 → functiontrace-0.5.3}/functiontrace.egg-info/top_level.txt +0 -0
- {functiontrace-0.5.1 → functiontrace-0.5.3}/setup.cfg +0 -0
- {functiontrace-0.5.1 → functiontrace-0.5.3}/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.3
|
|
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
|
-----
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: functiontrace
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
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"]
|
|
@@ -89,10 +94,19 @@ def main():
|
|
|
89
94
|
# below, which needs to operate in the context of our new module.
|
|
90
95
|
# NOTE: We use SourceFileLoader, rather than allowing importlib to infer
|
|
91
96
|
# the proper loader, in order to support files without a .py extension.
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
mod = util.module_from_spec(
|
|
98
|
+
util.spec_from_loader(
|
|
99
|
+
"__main__", machinery.SourceFileLoader("__main__", target_file)
|
|
100
|
+
)
|
|
94
101
|
)
|
|
95
|
-
|
|
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
|
+
|
|
96
110
|
sys.modules.update({"__main__": mod})
|
|
97
111
|
|
|
98
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.3"
|
|
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
|
|
|
@@ -206,7 +206,7 @@ mod _functiontrace_rs {
|
|
|
206
206
|
.wrap_err("Failed to convert MIN_PYTHON_VERSION to int")?,
|
|
207
207
|
)
|
|
208
208
|
.wrap_err("Failed to expose _min_python_version")?;
|
|
209
|
-
m.add("_max_python_version",
|
|
209
|
+
m.add("_max_python_version", 14)
|
|
210
210
|
.wrap_err("Failed to expose _max_python_version")?;
|
|
211
211
|
|
|
212
212
|
color_eyre::install().wrap_err("Failed to install color_eyre")?;
|
|
@@ -241,6 +241,8 @@ mod _functiontrace_rs {
|
|
|
241
241
|
module: &Bound<'_, PyModule>,
|
|
242
242
|
output_directory: OsString,
|
|
243
243
|
) -> Result<()> {
|
|
244
|
+
use std::os::unix::fs::{DirBuilderExt, PermissionsExt};
|
|
245
|
+
|
|
244
246
|
// The name of the env var we use to notify subprocesses that there's a trace in progress.
|
|
245
247
|
const BREADCRUMB: &str = "FUNCTIONTRACE_LIVE";
|
|
246
248
|
|
|
@@ -259,20 +261,20 @@ mod _functiontrace_rs {
|
|
|
259
261
|
// Parse args out of `sys.argv`, since it excludes the Python executable and shows the
|
|
260
262
|
// script's path first. This is much more complicated than using `std::env::args`, but
|
|
261
263
|
// is more practical.
|
|
262
|
-
Python::
|
|
264
|
+
Python::attach(|py| -> Result<String> {
|
|
263
265
|
let argv = py
|
|
264
266
|
.import("sys")
|
|
265
267
|
.and_then(|sys| sys.getattr("argv"))
|
|
266
268
|
.wrap_err("Failed to access sys.argv")?;
|
|
267
269
|
|
|
268
270
|
let args = argv
|
|
269
|
-
.
|
|
271
|
+
.cast::<pyo3::types::PyList>()
|
|
270
272
|
.map_err(|e| eyre!("Failed to read sys.argv as list: {}", e))?;
|
|
271
273
|
|
|
272
274
|
let mut full_args = args.iter().fold(String::new(), |s, arg| {
|
|
273
275
|
// Fetch the next arg as a string if possible.
|
|
274
276
|
let arg = arg
|
|
275
|
-
.
|
|
277
|
+
.cast::<pyo3::types::PyString>()
|
|
276
278
|
.map_err(|e| eyre!("Failed to read sys.argv[x] as string: {}", e))
|
|
277
279
|
.and_then(|arg| {
|
|
278
280
|
arg.to_str()
|
|
@@ -327,7 +329,7 @@ mod _functiontrace_rs {
|
|
|
327
329
|
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
|
328
330
|
eprintln!("Unable to find `functiontrace-server` in the current $PATH.");
|
|
329
331
|
eprintln!(
|
|
330
|
-
"See https://functiontrace.com
|
|
332
|
+
"See https://functiontrace.com/installation/ for installation instructions."
|
|
331
333
|
);
|
|
332
334
|
|
|
333
335
|
// Exit here rather than returning an error, since that'll emit a confusing
|
|
@@ -337,15 +339,24 @@ mod _functiontrace_rs {
|
|
|
337
339
|
e => e.wrap_err("Failed to launch functiontrace-server")?,
|
|
338
340
|
};
|
|
339
341
|
|
|
340
|
-
format!("/tmp/functiontrace-server.sock.{}", server.id())
|
|
342
|
+
let sockaddr = format!("/tmp/functiontrace-server.sock.{}", server.id());
|
|
343
|
+
|
|
344
|
+
// Also allocate a directory for storing wrappers (multiprocessing.set_executable,
|
|
345
|
+
// etc) we may need.
|
|
346
|
+
std::fs::DirBuilder::new()
|
|
347
|
+
.recursive(true)
|
|
348
|
+
.mode(0o700)
|
|
349
|
+
.create(format!("{sockaddr}.wrappers"))
|
|
350
|
+
.wrap_err("Failed to create wrapper directory")?;
|
|
351
|
+
sockaddr.into()
|
|
341
352
|
};
|
|
342
353
|
|
|
343
|
-
Python::
|
|
354
|
+
Python::attach(|_| {
|
|
344
355
|
// Register an env var so subprocesses know how to connect to our
|
|
345
356
|
// profile.
|
|
346
357
|
unsafe {
|
|
347
358
|
// SAFETY: `set_var` can only be done while no one else is reading or writing to the
|
|
348
|
-
// environment.
|
|
359
|
+
// environment. We're holding the GIL, so this _should_ be true, but there's no real
|
|
349
360
|
// way to enforce this.
|
|
350
361
|
//
|
|
351
362
|
// This doesn't seem to blow up in C even when we don't have the GIL, so
|
|
@@ -362,7 +373,7 @@ mod _functiontrace_rs {
|
|
|
362
373
|
// Send an initialization message to the server with some information about us. This will
|
|
363
374
|
// only be sent once, and all future threads/subprocesses will be associated underneath us.
|
|
364
375
|
let init = {
|
|
365
|
-
let python_version = Python::
|
|
376
|
+
let python_version = Python::attach(|py| {
|
|
366
377
|
let version = py.version_info();
|
|
367
378
|
|
|
368
379
|
format!(
|
|
@@ -371,14 +382,14 @@ mod _functiontrace_rs {
|
|
|
371
382
|
)
|
|
372
383
|
});
|
|
373
384
|
|
|
374
|
-
let platform = Python::
|
|
385
|
+
let platform = Python::attach(|py| -> Result<String> {
|
|
375
386
|
let platform = py
|
|
376
387
|
.import("sys")
|
|
377
388
|
.and_then(|sys| sys.getattr("platform"))
|
|
378
389
|
.wrap_err("Failed to access sys.platform")?;
|
|
379
390
|
|
|
380
391
|
Ok(platform
|
|
381
|
-
.
|
|
392
|
+
.cast::<pyo3::types::PyString>()
|
|
382
393
|
.map_err(|e| eyre!("Failed to read sys.platform as string: {}", e))?
|
|
383
394
|
.to_str()
|
|
384
395
|
.wrap_err("Failed to read Rust string out of PyString")?
|
|
@@ -414,6 +425,61 @@ mod _functiontrace_rs {
|
|
|
414
425
|
.wrap_err("Failed to close initialization socket")?;
|
|
415
426
|
}
|
|
416
427
|
|
|
428
|
+
// `multiprocessing` uses an absolute path to an executable to launch forkserver workers
|
|
429
|
+
// using `-c` for the actual code,
|
|
430
|
+
// Override this with our own wrapper that also sets up functiontrace.
|
|
431
|
+
Python::attach(|py| -> Result<()> {
|
|
432
|
+
let executable: String = py.import("sys")?.getattr("executable")?.extract()?;
|
|
433
|
+
let wrapper = {
|
|
434
|
+
// We'll store the wrappers next to the socket.
|
|
435
|
+
// TODO: Ideally we'd have these all in the same tmpdir, but that requires changing
|
|
436
|
+
// client and server code so would be a breaking change.
|
|
437
|
+
let mut directory = SOCKADDR
|
|
438
|
+
.get()
|
|
439
|
+
.expect("sockaddr is set during server initialization")
|
|
440
|
+
.clone();
|
|
441
|
+
directory.push(".wrappers");
|
|
442
|
+
|
|
443
|
+
std::path::PathBuf::from(directory)
|
|
444
|
+
.join("multiprocessing")
|
|
445
|
+
.join(executable.strip_prefix("/").unwrap_or(&executable))
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
// Create a multiprocessing wrapper for the current executable if it doesn't exist yet.
|
|
449
|
+
std::fs::create_dir_all(
|
|
450
|
+
wrapper
|
|
451
|
+
.parent()
|
|
452
|
+
.expect("Wrappers are always in a subdirectory"),
|
|
453
|
+
)
|
|
454
|
+
.wrap_err("Failed to create multiprocessing wrapper directories")?;
|
|
455
|
+
|
|
456
|
+
match std::fs::OpenOptions::new()
|
|
457
|
+
.write(true)
|
|
458
|
+
.create_new(true)
|
|
459
|
+
.open(&wrapper)
|
|
460
|
+
{
|
|
461
|
+
Ok(mut file) => {
|
|
462
|
+
let script = include_str!("multiprocessing.sh.tpl").replace(
|
|
463
|
+
"@PYTHON_INTERPRETER@",
|
|
464
|
+
// NOTE: This is equivalent to shlex.quote
|
|
465
|
+
&format!("'{}'", executable.replace('\'', "'\"'\"'")),
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
file.write_all(script.as_bytes())
|
|
469
|
+
.wrap_err("Failed to write multiprocessing wrapper")?;
|
|
470
|
+
file.set_permissions(std::fs::Permissions::from_mode(0o700))
|
|
471
|
+
.wrap_err("Failed to `chmod +x` multiprocessing wrapper")?;
|
|
472
|
+
}
|
|
473
|
+
Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {}
|
|
474
|
+
Err(err) => return Err(err).wrap_err("Failed to create multiprocessing wrapper"),
|
|
475
|
+
}
|
|
476
|
+
py.import("multiprocessing")?
|
|
477
|
+
.getattr("set_executable")?
|
|
478
|
+
.call1((wrapper,))?;
|
|
479
|
+
Ok(())
|
|
480
|
+
})
|
|
481
|
+
.wrap_err("Failed to configure multiprocessing tracing")?;
|
|
482
|
+
|
|
417
483
|
////////////////////////////////////////////////////////////////////////////
|
|
418
484
|
// Teardown Configuration
|
|
419
485
|
////////////////////////////////////////////////////////////////////////////
|
|
@@ -433,7 +499,7 @@ mod _functiontrace_rs {
|
|
|
433
499
|
//
|
|
434
500
|
// NOTE: We specifically do this rather than `Py_AtExit` since we want to run before all
|
|
435
501
|
// the Python teardown occurs.
|
|
436
|
-
Python::
|
|
502
|
+
Python::attach(|py| -> Result<()> {
|
|
437
503
|
py.import("atexit")
|
|
438
504
|
.and_then(|atexit| atexit.getattr("register"))
|
|
439
505
|
.wrap_err("Failed to access atexit.register")?
|
|
@@ -460,12 +526,13 @@ mod _functiontrace_rs {
|
|
|
460
526
|
|
|
461
527
|
// Register our tracing functions with Python - both the normal one and the multithreaded
|
|
462
528
|
// setup trampoline.
|
|
529
|
+
// TODO: PyEval_SetProfileAllThreads in >=3.12 should simplify this.
|
|
463
530
|
unsafe {
|
|
464
531
|
// SAFETY: both parameters are of the proper type.
|
|
465
532
|
pyo3::ffi::PyEval_SetProfile(Some(functiontrace), std::ptr::null_mut())
|
|
466
533
|
};
|
|
467
534
|
|
|
468
|
-
Python::
|
|
535
|
+
Python::attach(|py| -> Result<()> {
|
|
469
536
|
py.import("threading")
|
|
470
537
|
.and_then(|threading| threading.getattr("setprofile"))
|
|
471
538
|
.wrap_err("Failed to access threading.setprofile")?
|
|
@@ -561,23 +628,6 @@ mod _functiontrace_rs {
|
|
|
561
628
|
Ok(())
|
|
562
629
|
}
|
|
563
630
|
|
|
564
|
-
#[unsafe(no_mangle)]
|
|
565
|
-
extern "C" fn c_thread_teardown(state: *mut c_void) {
|
|
566
|
-
if state.is_null() {
|
|
567
|
-
// We never fully initialized this thread, so skip teardown.
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
let writer = unsafe {
|
|
572
|
-
// SAFETY: writer is a valid pointer to an initialized `ThreadState`.
|
|
573
|
-
&mut (state as *mut ThreadState)
|
|
574
|
-
.as_mut()
|
|
575
|
-
.expect("state is non-null")
|
|
576
|
-
};
|
|
577
|
-
|
|
578
|
-
thread_teardown(writer).expect("Failed to tear down FunctionTrace thread")
|
|
579
|
-
}
|
|
580
|
-
|
|
581
631
|
/// Tear down the given thread by flushing any outstanding messages.
|
|
582
632
|
///
|
|
583
633
|
/// This is called when the thread has shut down, including in multithread/process situations.
|
|
@@ -636,7 +686,11 @@ mod _functiontrace_rs {
|
|
|
636
686
|
/// thread state, before replacing itself with the normal Fprofile_FunctionTrace handler.
|
|
637
687
|
#[pyfunction]
|
|
638
688
|
#[pyo3(name = "_thread_trace_trampoline")]
|
|
639
|
-
fn thread_trace_trampoline(
|
|
689
|
+
fn thread_trace_trampoline(
|
|
690
|
+
_frame: Py<PyAny>,
|
|
691
|
+
_event: Py<PyAny>,
|
|
692
|
+
_arg: Py<PyAny>,
|
|
693
|
+
) -> Result<()> {
|
|
640
694
|
// Register the current thread for tracing
|
|
641
695
|
thread_register().wrap_err("Failed to register new FunctionTrace thread")?;
|
|
642
696
|
|
|
@@ -787,7 +841,7 @@ mod _functiontrace_rs {
|
|
|
787
841
|
pyo3::ffi::PyFrame_GetCode(frame) as *mut pyo3::ffi::PyObject
|
|
788
842
|
};
|
|
789
843
|
|
|
790
|
-
Python::
|
|
844
|
+
Python::attach(|py| {
|
|
791
845
|
let code = unsafe {
|
|
792
846
|
// SAFETY: code is guaranteed to be a valid PyCodeObject since it
|
|
793
847
|
// came from `PyFrame_GetCode`.
|
|
@@ -799,7 +853,7 @@ mod _functiontrace_rs {
|
|
|
799
853
|
let unicode_converter = |pyobj: PyResult<Bound<'_, PyAny>>| {
|
|
800
854
|
pyobj
|
|
801
855
|
.map(|obj| {
|
|
802
|
-
if let Ok(filename) = obj.
|
|
856
|
+
if let Ok(filename) = obj.cast::<pyo3::types::PyString>() {
|
|
803
857
|
Cow::from(filename.to_string_lossy().into_owned())
|
|
804
858
|
} else {
|
|
805
859
|
Cow::Borrowed("<UNKNOWN>")
|
|
@@ -856,13 +910,13 @@ mod _functiontrace_rs {
|
|
|
856
910
|
if !func.m_module.is_null() {
|
|
857
911
|
// NOTE: _Typically_ `m_module` is a PyModule, but there are no requirements on
|
|
858
912
|
// what type it needs to be! In practice, we only see PyModules and PyStrings.
|
|
859
|
-
Python::
|
|
913
|
+
Python::attach(|py| {
|
|
860
914
|
let m_module = unsafe {
|
|
861
915
|
// SAFETY: m_module is a non-NULL pointer to a valid Python object.
|
|
862
916
|
Bound::from_borrowed_ptr(py, func.m_module)
|
|
863
917
|
};
|
|
864
918
|
|
|
865
|
-
if let Ok(module) = m_module.
|
|
919
|
+
if let Ok(module) = m_module.cast::<pyo3::types::PyModule>() {
|
|
866
920
|
// This is a PyModule, so get its name if possible.
|
|
867
921
|
let name = module.name();
|
|
868
922
|
let module_name = if let Ok(name) = &name {
|
|
@@ -879,7 +933,7 @@ mod _functiontrace_rs {
|
|
|
879
933
|
.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
880
934
|
} else {
|
|
881
935
|
let module_name =
|
|
882
|
-
if let Ok(module) = m_module.
|
|
936
|
+
if let Ok(module) = m_module.cast::<pyo3::types::PyString>() {
|
|
883
937
|
module.to_cow().unwrap_or(Cow::Borrowed("<invalid string>"))
|
|
884
938
|
} else {
|
|
885
939
|
if let Ok(typ) = m_module.get_type().qualname().map(|x| x.to_string()) {
|
|
@@ -1125,7 +1179,7 @@ mod _functiontrace_rs {
|
|
|
1125
1179
|
if let Some(result) = parser(&input) {
|
|
1126
1180
|
return Ok(result);
|
|
1127
1181
|
}
|
|
1128
|
-
Python::
|
|
1182
|
+
Python::attach(|py| {
|
|
1129
1183
|
// We need to check with the Python interpreter during loops, otherwise we
|
|
1130
1184
|
// might not receive ctrl-c events.
|
|
1131
1185
|
py.check_signals()
|
|
@@ -1507,7 +1561,7 @@ mod hooks {
|
|
|
1507
1561
|
/// function, then attaching enough information about the original function to call it.
|
|
1508
1562
|
pub fn install() -> Result<()> {
|
|
1509
1563
|
for hook in HOOKS.iter() {
|
|
1510
|
-
Python::
|
|
1564
|
+
Python::attach(|py| -> Result<()> {
|
|
1511
1565
|
let (module_name, method_name) = hook
|
|
1512
1566
|
.target
|
|
1513
1567
|
.rsplit_once(".")
|
|
@@ -1649,14 +1703,14 @@ mod hooks {
|
|
|
1649
1703
|
.get()
|
|
1650
1704
|
.expect("Wrapper functions can't be called until hooked");
|
|
1651
1705
|
|
|
1652
|
-
let proxied = Python::
|
|
1706
|
+
let proxied = Python::attach(|py| -> Result<()> {
|
|
1653
1707
|
if let Some(mut writer) = crate::_functiontrace_rs::thread_state() {
|
|
1654
1708
|
let args = unsafe {
|
|
1655
1709
|
// SAFETY: `args` represents a valid argument tuple
|
|
1656
1710
|
Bound::from_borrowed_ptr(py, args)
|
|
1657
1711
|
};
|
|
1658
1712
|
let args = args
|
|
1659
|
-
.
|
|
1713
|
+
.cast()
|
|
1660
1714
|
.map_err(Into::<PyErr>::into)
|
|
1661
1715
|
.wrap_err("Python args must be a tuple")?;
|
|
1662
1716
|
|
|
@@ -1733,7 +1787,7 @@ mod hooks {
|
|
|
1733
1787
|
// `import` takes rather complicated arguments, but in practice we only care about
|
|
1734
1788
|
// the first argument (name)
|
|
1735
1789
|
let module = if let Ok(arg) = args.get_item(0) {
|
|
1736
|
-
arg.
|
|
1790
|
+
arg.cast()
|
|
1737
1791
|
.map_err(Into::into)
|
|
1738
1792
|
.and_then(|module| module.to_str())
|
|
1739
1793
|
.map(|module| Cow::Owned(module.to_string()))
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
# Multiprocessing passes the code to execute in workers as `-c <code>`.
|
|
4
|
+
# Strip that out and run tracing setup first, maintaining the order of arguments just in case.
|
|
5
|
+
|
|
6
|
+
remaining=$#
|
|
7
|
+
while [ "$remaining" -gt 0 ]; do
|
|
8
|
+
arg=$1
|
|
9
|
+
shift
|
|
10
|
+
remaining=$((remaining - 1))
|
|
11
|
+
case "$arg" in
|
|
12
|
+
-W|-X)
|
|
13
|
+
# These arguments can consume other arguments. Skip them so we don't parse the wrong `-c`.
|
|
14
|
+
value=$1
|
|
15
|
+
shift
|
|
16
|
+
remaining=$((remaining - 1))
|
|
17
|
+
set -- "$@" "$arg" "$value"
|
|
18
|
+
;;
|
|
19
|
+
-c)
|
|
20
|
+
code=$1
|
|
21
|
+
shift
|
|
22
|
+
remaining=$((remaining - 1))
|
|
23
|
+
set -- "$@" -c '
|
|
24
|
+
exec(
|
|
25
|
+
(
|
|
26
|
+
compile(
|
|
27
|
+
__import__("sys").argv.pop(1),
|
|
28
|
+
"<string>",
|
|
29
|
+
"exec",
|
|
30
|
+
dont_inherit=True,
|
|
31
|
+
),
|
|
32
|
+
# Start tracing before exec runs the compiled code.
|
|
33
|
+
__import__("_functiontrace_rs").begin_tracing("."),
|
|
34
|
+
)[0]
|
|
35
|
+
)
|
|
36
|
+
' "$code"
|
|
37
|
+
break
|
|
38
|
+
;;
|
|
39
|
+
*)
|
|
40
|
+
set -- "$@" "$arg"
|
|
41
|
+
;;
|
|
42
|
+
esac
|
|
43
|
+
done
|
|
44
|
+
|
|
45
|
+
# Consume the rest of the args without looking.
|
|
46
|
+
while [ "$remaining" -gt 0 ]; do
|
|
47
|
+
arg=$1
|
|
48
|
+
shift
|
|
49
|
+
remaining=$((remaining - 1))
|
|
50
|
+
set -- "$@" "$arg"
|
|
51
|
+
done
|
|
52
|
+
|
|
53
|
+
exec @PYTHON_INTERPRETER@ "$@"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|