functiontrace 0.4.2__tar.gz → 0.4.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.4.2 → functiontrace-0.4.3}/Cargo.toml +2 -1
- functiontrace-0.4.3/LICENSE.md +57 -0
- {functiontrace-0.4.2/functiontrace.egg-info → functiontrace-0.4.3}/PKG-INFO +5 -4
- functiontrace-0.4.3/_functiontrace.c +160 -0
- {functiontrace-0.4.2 → functiontrace-0.4.3}/build.rs +3 -0
- {functiontrace-0.4.2 → functiontrace-0.4.3/functiontrace.egg-info}/PKG-INFO +5 -4
- {functiontrace-0.4.2 → functiontrace-0.4.3}/functiontrace.egg-info/SOURCES.txt +1 -0
- {functiontrace-0.4.2 → functiontrace-0.4.3}/pyproject.toml +5 -5
- {functiontrace-0.4.2 → functiontrace-0.4.3}/rust/lib.rs +180 -63
- functiontrace-0.4.2/_functiontrace.c +0 -277
- {functiontrace-0.4.2 → functiontrace-0.4.3}/MANIFEST.in +0 -0
- {functiontrace-0.4.2 → functiontrace-0.4.3}/README.rst +0 -0
- {functiontrace-0.4.2 → functiontrace-0.4.3}/functiontrace.egg-info/dependency_links.txt +0 -0
- {functiontrace-0.4.2 → functiontrace-0.4.3}/functiontrace.egg-info/entry_points.txt +0 -0
- {functiontrace-0.4.2 → functiontrace-0.4.3}/functiontrace.egg-info/top_level.txt +0 -0
- {functiontrace-0.4.2 → functiontrace-0.4.3}/functiontrace.py +0 -0
- {functiontrace-0.4.2 → functiontrace-0.4.3}/mpack/LICENSE +0 -0
- {functiontrace-0.4.2 → functiontrace-0.4.3}/mpack/mpack.c +0 -0
- {functiontrace-0.4.2 → functiontrace-0.4.3}/mpack/mpack.h +0 -0
- {functiontrace-0.4.2 → functiontrace-0.4.3}/setup.cfg +0 -0
- {functiontrace-0.4.2 → functiontrace-0.4.3}/setup.py +0 -0
|
@@ -7,12 +7,13 @@ edition = "2021"
|
|
|
7
7
|
color-eyre = { version = "0.6", default-features = false }
|
|
8
8
|
functiontrace-server = { version = "0.8.4", default-features = false, features = ["serialize"] }
|
|
9
9
|
libc = "0.2"
|
|
10
|
-
pyo3 = { version = "0.
|
|
10
|
+
pyo3 = { version = "0.25", features = ["eyre"] }
|
|
11
11
|
rmp-serde = "1.3"
|
|
12
12
|
serde = "1"
|
|
13
13
|
|
|
14
14
|
[build-dependencies]
|
|
15
15
|
toml = { version = "0.8", default-features = false, features = ["parse"] }
|
|
16
|
+
pyo3-build-config = { version = "0.25", features = ["resolve-config"] }
|
|
16
17
|
|
|
17
18
|
[lib]
|
|
18
19
|
# See https://github.com/PyO3/pyo3 for details
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# The Prosperity Public License 3.0.0
|
|
2
|
+
|
|
3
|
+
Contributor: Matt Bryant, Alistair Dobke
|
|
4
|
+
|
|
5
|
+
Source Code: https://gitlab.com/mbryant/functiontrace (https://functiontrace.com)
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
This license allows you to use and share this software for noncommercial purposes for free and to try this software for commercial purposes for thirty days.
|
|
10
|
+
|
|
11
|
+
## Agreement
|
|
12
|
+
|
|
13
|
+
In order to receive this license, you have to agree to its rules. Those rules are both obligations under that agreement and conditions to your license. Don't do anything with this software that triggers a rule you can't or won't follow.
|
|
14
|
+
|
|
15
|
+
## Notices
|
|
16
|
+
|
|
17
|
+
Make sure everyone who gets a copy of any part of this software from you, with or without changes, also gets the text of this license and the contributor and source code lines above.
|
|
18
|
+
|
|
19
|
+
## Commercial Trial
|
|
20
|
+
|
|
21
|
+
Limit your use of this software for commercial purposes to a thirty-day trial period. If you use this software for work, your company gets one trial period for all personnel, not one trial per person.
|
|
22
|
+
|
|
23
|
+
## Contributions Back
|
|
24
|
+
|
|
25
|
+
Developing feedback, changes, or additions that you contribute back to the contributor on the terms of a standardized public software license such as [the Blue Oak Model License 1.0.0](https://blueoakcouncil.org/license/1.0.0), [the Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0.html), [the MIT license](https://spdx.org/licenses/MIT.html), or [the two-clause BSD license](https://spdx.org/licenses/BSD-2-Clause.html) doesn't count as use for a commercial purpose.
|
|
26
|
+
|
|
27
|
+
## Personal Uses
|
|
28
|
+
|
|
29
|
+
Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, doesn't count as use for a commercial purpose.
|
|
30
|
+
|
|
31
|
+
## Noncommercial Organizations
|
|
32
|
+
|
|
33
|
+
Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution doesn't count as use for a commercial purpose regardless of the source of funding or obligations resulting from the funding.
|
|
34
|
+
|
|
35
|
+
## Defense
|
|
36
|
+
|
|
37
|
+
Don't make any legal claim against anyone accusing this software, with or without changes, alone or with other technology, of infringing any patent.
|
|
38
|
+
|
|
39
|
+
## Copyright
|
|
40
|
+
|
|
41
|
+
The contributor licenses you to do everything with this software that would otherwise infringe their copyright in it.
|
|
42
|
+
|
|
43
|
+
## Patent
|
|
44
|
+
|
|
45
|
+
The contributor licenses you to do everything with this software that would otherwise infringe any patents they can license or become able to license.
|
|
46
|
+
|
|
47
|
+
## Reliability
|
|
48
|
+
|
|
49
|
+
The contributor can't revoke this license.
|
|
50
|
+
|
|
51
|
+
## Excuse
|
|
52
|
+
|
|
53
|
+
You're excused for unknowingly breaking [Notices](#notices) if you take all practical steps to comply within thirty days of learning you broke the rule.
|
|
54
|
+
|
|
55
|
+
## No Liability
|
|
56
|
+
|
|
57
|
+
***As far as the law allows, this software comes as is, without any warranty or condition, and the contributor won't be liable to anyone for any damages related to this software or this license, under any kind of legal claim.***
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: functiontrace
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.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>
|
|
7
|
-
License:
|
|
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
|
|
10
10
|
Keywords: profiler,debugger
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: Topic :: Software Development :: Debuggers
|
|
13
|
-
Classifier: License :: Free for non-commercial use
|
|
14
13
|
Classifier: Operating System :: Unix
|
|
15
14
|
Classifier: Environment :: Console
|
|
16
15
|
Classifier: Programming Language :: Python :: 3
|
|
17
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
17
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
19
|
-
Requires-Python: >=3.
|
|
18
|
+
Requires-Python: >=3.9
|
|
20
19
|
Description-Content-Type: text/x-rst
|
|
20
|
+
License-File: LICENSE.md
|
|
21
|
+
Dynamic: license-file
|
|
21
22
|
|
|
22
23
|
Py-Functiontrace
|
|
23
24
|
================
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#include <Python.h>
|
|
2
|
+
#include <frameobject.h>
|
|
3
|
+
#include <sys/types.h>
|
|
4
|
+
#include <pthread.h>
|
|
5
|
+
#include <stdio.h>
|
|
6
|
+
#include <stdbool.h>
|
|
7
|
+
#include <unistd.h>
|
|
8
|
+
|
|
9
|
+
#include "mpack/mpack.h"
|
|
10
|
+
|
|
11
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
12
|
+
// Prototypes
|
|
13
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
14
|
+
static int Fprofile_FunctionTrace(PyObject* obj, PyFrameObject* frame, int what, PyObject* arg);
|
|
15
|
+
|
|
16
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
17
|
+
// Globals
|
|
18
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
19
|
+
// This key is used to fetch the thread local instance of the `Writer`
|
|
20
|
+
// structure.
|
|
21
|
+
static pthread_key_t Tss_Key = 0;
|
|
22
|
+
|
|
23
|
+
// The exposed apis of the Rust extension.
|
|
24
|
+
static struct {
|
|
25
|
+
pthread_key_t (*set_config)(int (*functiontrace)(PyObject*, PyFrameObject*, int, PyObject*));
|
|
26
|
+
}* rust;
|
|
27
|
+
|
|
28
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
29
|
+
// Misc
|
|
30
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
31
|
+
|
|
32
|
+
// Given a PyObject (likely `co_name` or similar), return a UTF-8
|
|
33
|
+
// representation.
|
|
34
|
+
static inline const char* Fprofile_UnicodeToUtf8(PyObject* obj) {
|
|
35
|
+
if (obj == NULL) {
|
|
36
|
+
return "<NULL>";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (PyUnicode_Check(obj)) {
|
|
40
|
+
const char* utf8 = PyUnicode_AsUTF8(obj);
|
|
41
|
+
|
|
42
|
+
return utf8 != NULL ? utf8 : "<DECODE ERROR>";
|
|
43
|
+
} else if (obj == Py_None) {
|
|
44
|
+
return "<NONE>";
|
|
45
|
+
} else {
|
|
46
|
+
return "<UNKNOWN>";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
51
|
+
// Callbacks
|
|
52
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
53
|
+
static int Fprofile_FunctionTrace(PyObject* obj, PyFrameObject* frame, int what, PyObject* arg) {
|
|
54
|
+
struct timespec tsc = { 0 };
|
|
55
|
+
|
|
56
|
+
// This is called from the Rust code, which has already checked that
|
|
57
|
+
// everything is configured to send messages.
|
|
58
|
+
mpack_writer_t* writer = pthread_getspecific(Tss_Key);
|
|
59
|
+
|
|
60
|
+
clock_gettime(CLOCK_MONOTONIC, &tsc);
|
|
61
|
+
|
|
62
|
+
switch (what) {
|
|
63
|
+
case PyTrace_CALL:
|
|
64
|
+
mpack_start_array(writer, 5);
|
|
65
|
+
mpack_write_cstr(writer, "Call");
|
|
66
|
+
|
|
67
|
+
mpack_start_array(writer, 2);
|
|
68
|
+
mpack_write_u32(writer, tsc.tv_sec);
|
|
69
|
+
mpack_write_u32(writer, tsc.tv_nsec);
|
|
70
|
+
mpack_finish_array(writer);
|
|
71
|
+
|
|
72
|
+
{
|
|
73
|
+
PyCodeObject* code = PyFrame_GetCode(frame);
|
|
74
|
+
int lineno = PyFrame_GetLineNumber(frame);
|
|
75
|
+
|
|
76
|
+
#if PY_VERSION_HEX >= 0x030b00a0
|
|
77
|
+
mpack_write_cstr(writer, Fprofile_UnicodeToUtf8(code->co_qualname));
|
|
78
|
+
#else
|
|
79
|
+
mpack_write_cstr(writer, Fprofile_UnicodeToUtf8(code->co_name));
|
|
80
|
+
#endif
|
|
81
|
+
mpack_write_cstr(writer, Fprofile_UnicodeToUtf8(code->co_filename));
|
|
82
|
+
mpack_write_u32(writer, lineno);
|
|
83
|
+
|
|
84
|
+
Py_DECREF(code);
|
|
85
|
+
}
|
|
86
|
+
mpack_finish_array(writer);
|
|
87
|
+
break;
|
|
88
|
+
case PyTrace_RETURN:
|
|
89
|
+
mpack_start_array(writer, 3);
|
|
90
|
+
mpack_write_cstr(writer, "Return");
|
|
91
|
+
|
|
92
|
+
mpack_start_array(writer, 2);
|
|
93
|
+
mpack_write_u32(writer, tsc.tv_sec);
|
|
94
|
+
mpack_write_u32(writer, tsc.tv_nsec);
|
|
95
|
+
mpack_finish_array(writer);
|
|
96
|
+
|
|
97
|
+
{
|
|
98
|
+
PyCodeObject* code = PyFrame_GetCode(frame);
|
|
99
|
+
|
|
100
|
+
#if PY_VERSION_HEX >= 0x030b00a0
|
|
101
|
+
mpack_write_cstr(writer, Fprofile_UnicodeToUtf8(code->co_qualname));
|
|
102
|
+
#else
|
|
103
|
+
mpack_write_cstr(writer, Fprofile_UnicodeToUtf8(code->co_name));
|
|
104
|
+
#endif
|
|
105
|
+
|
|
106
|
+
Py_DECREF(code);
|
|
107
|
+
}
|
|
108
|
+
mpack_finish_array(writer);
|
|
109
|
+
break;
|
|
110
|
+
default:
|
|
111
|
+
perror("Impossible C case");
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
119
|
+
// Module Initialization
|
|
120
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
121
|
+
|
|
122
|
+
// The set of methods exposed by this module to Python.
|
|
123
|
+
static PyMethodDef methods[] = {
|
|
124
|
+
[0] = {NULL, NULL, 0, NULL}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
static PyModuleDef module = {
|
|
128
|
+
PyModuleDef_HEAD_INIT,
|
|
129
|
+
"_functiontrace",
|
|
130
|
+
"",
|
|
131
|
+
-1,
|
|
132
|
+
methods
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
PyMODINIT_FUNC PyInit__functiontrace(void) {
|
|
136
|
+
// Setup our janky Rust dlopen alternative so we can use Rust code.
|
|
137
|
+
PyObject* rust_mod = PyImport_ImportModule("_functiontrace_rs");
|
|
138
|
+
|
|
139
|
+
// Verify that the rust extension loads if we're using it.
|
|
140
|
+
if (rust_mod == NULL) {
|
|
141
|
+
perror("Failed to load internal Rust extension");
|
|
142
|
+
exit(-1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// This returns a pointer to the C API exposed by the Rust module.
|
|
146
|
+
PyObject* py_c_api = PyObject_GetAttrString(rust_mod, "c_api");
|
|
147
|
+
if (py_c_api == NULL) {
|
|
148
|
+
perror("Failed to initialize FunctionTrace Rust<->C interface");
|
|
149
|
+
exit(-1);
|
|
150
|
+
}
|
|
151
|
+
PyObject* c_api = PyObject_CallFunctionObjArgs(py_c_api, NULL);
|
|
152
|
+
|
|
153
|
+
// Save the Rust vtable so everyone can access it
|
|
154
|
+
rust = PyLong_AsVoidPtr(c_api);
|
|
155
|
+
|
|
156
|
+
// Share our globals with Rust
|
|
157
|
+
Tss_Key = rust->set_config(Fprofile_FunctionTrace);
|
|
158
|
+
|
|
159
|
+
return PyModule_Create(&module);
|
|
160
|
+
}
|
|
@@ -13,5 +13,8 @@ fn main() -> std::io::Result<()> {
|
|
|
13
13
|
.expect("version field is present in pyproject.toml");
|
|
14
14
|
println!("cargo::rustc-env=PACKAGE_VERSION={}", version);
|
|
15
15
|
|
|
16
|
+
// Figure out which Python version we're building for
|
|
17
|
+
pyo3_build_config::use_pyo3_cfgs();
|
|
18
|
+
|
|
16
19
|
Ok(())
|
|
17
20
|
}
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: functiontrace
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.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>
|
|
7
|
-
License:
|
|
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
|
|
10
10
|
Keywords: profiler,debugger
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: Topic :: Software Development :: Debuggers
|
|
13
|
-
Classifier: License :: Free for non-commercial use
|
|
14
13
|
Classifier: Operating System :: Unix
|
|
15
14
|
Classifier: Environment :: Console
|
|
16
15
|
Classifier: Programming Language :: Python :: 3
|
|
17
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
17
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
19
|
-
Requires-Python: >=3.
|
|
18
|
+
Requires-Python: >=3.9
|
|
20
19
|
Description-Content-Type: text/x-rst
|
|
20
|
+
License-File: LICENSE.md
|
|
21
|
+
Dynamic: license-file
|
|
21
22
|
|
|
22
23
|
Py-Functiontrace
|
|
23
24
|
================
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "functiontrace"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.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"},
|
|
7
7
|
{ name = "Alistair Dobke" }
|
|
8
8
|
]
|
|
9
|
-
license =
|
|
9
|
+
license = "LicenseRef-Prosperity-Public-License-3.0.0"
|
|
10
|
+
license-files = ["LICENSE.md"]
|
|
10
11
|
readme = "README.rst"
|
|
11
|
-
# NOTE: Actively tested on Python 3.
|
|
12
|
+
# NOTE: Actively tested on Python 3.9 - 3.13 and may plausibly break on greater
|
|
12
13
|
# versions due to C extension hacks. However, setting a maximum version causes
|
|
13
14
|
# new Python versions to simply install very old versions of Functiontrace that
|
|
14
15
|
# didn't have the `requires-python` annotation on them and are also unlikely to
|
|
15
16
|
# work. Best to only set the minimum and let bug reports notify us of issues
|
|
16
17
|
# with new Python releases.
|
|
17
|
-
requires-python = ">=3.
|
|
18
|
+
requires-python = ">=3.9"
|
|
18
19
|
keywords = [
|
|
19
20
|
"profiler",
|
|
20
21
|
"debugger"
|
|
@@ -22,7 +23,6 @@ keywords = [
|
|
|
22
23
|
classifiers = [
|
|
23
24
|
"Intended Audience :: Developers",
|
|
24
25
|
"Topic :: Software Development :: Debuggers",
|
|
25
|
-
"License :: Free for non-commercial use",
|
|
26
26
|
"Operating System :: Unix",
|
|
27
27
|
"Environment :: Console",
|
|
28
28
|
"Programming Language :: Python :: 3",
|
|
@@ -4,7 +4,8 @@ mod _functiontrace_rs {
|
|
|
4
4
|
use functiontrace_server::function_trace;
|
|
5
5
|
use pyo3::prelude::*; // TODO: What's in here?
|
|
6
6
|
use serde::Serialize;
|
|
7
|
-
use std::
|
|
7
|
+
use std::borrow::Cow;
|
|
8
|
+
use std::ffi::{c_void, CStr, OsString};
|
|
8
9
|
use std::io::Write;
|
|
9
10
|
use std::os::unix::net::UnixStream;
|
|
10
11
|
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
|
@@ -46,11 +47,7 @@ mod _functiontrace_rs {
|
|
|
46
47
|
/// A small [`Write`] implementation that allows us to write to an
|
|
47
48
|
/// `mpack_writer_t`'s interior buffer while keeping its state consistent.
|
|
48
49
|
fn write(&mut self, src: &[u8]) -> std::io::Result<usize> {
|
|
49
|
-
if !STARTED
|
|
50
|
-
.get()
|
|
51
|
-
.expect("Set in `set_config`")
|
|
52
|
-
.load(Ordering::Relaxed)
|
|
53
|
-
{
|
|
50
|
+
if !STARTED.load(Ordering::Relaxed) {
|
|
54
51
|
// We haven't started yet, so we shouldn't write anything out. This avoids logging
|
|
55
52
|
// various internal setup things before we're ready.
|
|
56
53
|
return Ok(src.len());
|
|
@@ -140,18 +137,11 @@ mod _functiontrace_rs {
|
|
|
140
137
|
|
|
141
138
|
/// True iff we've started tracing and haven't been marked as terminated. When this is
|
|
142
139
|
/// set, we're allowed to send messages to the profile generation server.
|
|
143
|
-
static STARTED:
|
|
140
|
+
static STARTED: AtomicBool = AtomicBool::new(false);
|
|
144
141
|
|
|
145
142
|
/// The `Fprofile_FunctionTrace` function defined in the C extension. This is needed to finish
|
|
146
143
|
/// moving setup to the Rust code.
|
|
147
|
-
static TRACEFUNC: OnceLock<
|
|
148
|
-
unsafe extern "C" fn(
|
|
149
|
-
*mut pyo3::ffi::PyObject,
|
|
150
|
-
*mut pyo3::ffi::PyFrameObject,
|
|
151
|
-
i32,
|
|
152
|
-
*mut pyo3::ffi::PyObject,
|
|
153
|
-
) -> i32,
|
|
154
|
-
> = OnceLock::new();
|
|
144
|
+
static TRACEFUNC: OnceLock<pyo3::ffi::Py_tracefunc> = OnceLock::new();
|
|
155
145
|
|
|
156
146
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
157
147
|
// Rust <-> C Communication
|
|
@@ -176,16 +166,9 @@ mod _functiontrace_rs {
|
|
|
176
166
|
Box::into_raw(api) as usize
|
|
177
167
|
}
|
|
178
168
|
|
|
169
|
+
/// Called by the C module to pass the `c_functiontrace` handler to us.
|
|
179
170
|
#[unsafe(no_mangle)]
|
|
180
|
-
extern "C" fn set_config(
|
|
181
|
-
started: *const AtomicBool,
|
|
182
|
-
functiontrace: unsafe extern "C" fn(
|
|
183
|
-
*mut pyo3::ffi::PyObject,
|
|
184
|
-
*mut pyo3::ffi::PyFrameObject,
|
|
185
|
-
i32,
|
|
186
|
-
*mut pyo3::ffi::PyObject,
|
|
187
|
-
) -> i32,
|
|
188
|
-
) -> libc::pthread_key_t {
|
|
171
|
+
extern "C" fn set_config(c_functiontrace: pyo3::ffi::Py_tracefunc) -> libc::pthread_key_t {
|
|
189
172
|
// Allocate a Tss_Key for storing thread state
|
|
190
173
|
let mut tss_key = 0;
|
|
191
174
|
if unsafe {
|
|
@@ -196,16 +179,8 @@ mod _functiontrace_rs {
|
|
|
196
179
|
panic!("Failed to create Tss_Key");
|
|
197
180
|
}
|
|
198
181
|
|
|
199
|
-
STARTED
|
|
200
|
-
.set(unsafe {
|
|
201
|
-
started
|
|
202
|
-
.as_ref()
|
|
203
|
-
.expect("started is non-null and has a static lifetime")
|
|
204
|
-
})
|
|
205
|
-
.expect("set_config can only be called once");
|
|
206
|
-
|
|
207
182
|
TRACEFUNC
|
|
208
|
-
.set(
|
|
183
|
+
.set(c_functiontrace)
|
|
209
184
|
.expect("set_config can only be called once");
|
|
210
185
|
|
|
211
186
|
TSS_KEY.store(tss_key, Ordering::Relaxed);
|
|
@@ -339,7 +314,6 @@ mod _functiontrace_rs {
|
|
|
339
314
|
module: &Bound<'_, PyModule>,
|
|
340
315
|
output_directory: OsString,
|
|
341
316
|
) -> Result<()> {
|
|
342
|
-
//fn trace_initialization(args: *mut pyo3::ffi::PyObject) -> Result<()> {
|
|
343
317
|
// The name of the env var we use to notify subprocesses that there's a trace in progress.
|
|
344
318
|
const BREADCRUMB: &str = "FUNCTIONTRACE_LIVE";
|
|
345
319
|
|
|
@@ -544,11 +518,8 @@ mod _functiontrace_rs {
|
|
|
544
518
|
// Register our tracing functions with Python - both the normal one and the multithreaded
|
|
545
519
|
// setup trampoline.
|
|
546
520
|
unsafe {
|
|
547
|
-
// SAFETY:
|
|
548
|
-
pyo3::ffi::PyEval_SetProfile(
|
|
549
|
-
Some(*TRACEFUNC.get().expect("tracefunc is set in set_config")),
|
|
550
|
-
std::ptr::null_mut(),
|
|
551
|
-
)
|
|
521
|
+
// SAFETY: both parameters are of the proper type.
|
|
522
|
+
pyo3::ffi::PyEval_SetProfile(Some(functiontrace), std::ptr::null_mut())
|
|
552
523
|
};
|
|
553
524
|
|
|
554
525
|
Python::with_gil(|py| -> Result<()> {
|
|
@@ -568,10 +539,7 @@ mod _functiontrace_rs {
|
|
|
568
539
|
crate::hooks::install().wrap_err("Failed to install Python hooks")?;
|
|
569
540
|
|
|
570
541
|
// We're now fully setup and allowed to send messages.
|
|
571
|
-
STARTED
|
|
572
|
-
.get()
|
|
573
|
-
.expect("Set in `set_config`")
|
|
574
|
-
.store(true, Ordering::Relaxed);
|
|
542
|
+
STARTED.store(true, Ordering::Relaxed);
|
|
575
543
|
|
|
576
544
|
Ok(())
|
|
577
545
|
}
|
|
@@ -585,10 +553,7 @@ mod _functiontrace_rs {
|
|
|
585
553
|
_kwargs: Option<&Bound<'_, pyo3::types::PyDict>>,
|
|
586
554
|
) -> Result<()> {
|
|
587
555
|
// Stop profiling and don't allow further logging, then flush any remaining messages.
|
|
588
|
-
STARTED
|
|
589
|
-
.get()
|
|
590
|
-
.expect("module_configuration was set by `set_config`")
|
|
591
|
-
.store(false, Ordering::Relaxed);
|
|
556
|
+
STARTED.store(false, Ordering::Relaxed);
|
|
592
557
|
|
|
593
558
|
unsafe {
|
|
594
559
|
// PyEval_SetProfile(NULL, NULL) is the proper way to deregister a profiler.
|
|
@@ -723,11 +688,7 @@ mod _functiontrace_rs {
|
|
|
723
688
|
fn thread_reset() -> Result<()> {
|
|
724
689
|
let tss_key = TSS_KEY.load(Ordering::Relaxed);
|
|
725
690
|
|
|
726
|
-
if !STARTED
|
|
727
|
-
.get()
|
|
728
|
-
.expect("STARTED was set by `set_config`")
|
|
729
|
-
.load(Ordering::Relaxed)
|
|
730
|
-
{
|
|
691
|
+
if !STARTED.load(Ordering::Relaxed) {
|
|
731
692
|
// We haven't actually started yet, but are for some reason being asked
|
|
732
693
|
// to fork. This appears to be OS dependent.
|
|
733
694
|
return Ok(());
|
|
@@ -782,11 +743,8 @@ mod _functiontrace_rs {
|
|
|
782
743
|
|
|
783
744
|
// Replace our setprofile() handler with the real one.
|
|
784
745
|
unsafe {
|
|
785
|
-
// SAFETY:
|
|
786
|
-
pyo3::ffi::PyEval_SetProfile(
|
|
787
|
-
Some(*TRACEFUNC.get().expect("tracefunc is set in set_config")),
|
|
788
|
-
std::ptr::null_mut(),
|
|
789
|
-
)
|
|
746
|
+
// SAFETY: both parameters are of the proper type.
|
|
747
|
+
pyo3::ffi::PyEval_SetProfile(Some(functiontrace), std::ptr::null_mut())
|
|
790
748
|
};
|
|
791
749
|
|
|
792
750
|
// We previously called into `Fprofile_FunctionTrace` here to manually record this call,
|
|
@@ -854,6 +812,167 @@ mod _functiontrace_rs {
|
|
|
854
812
|
|
|
855
813
|
Ok(())
|
|
856
814
|
}
|
|
815
|
+
|
|
816
|
+
/// The core logging function for FunctionTrace. Configured by `PyEval_SetProfile`, and used
|
|
817
|
+
/// to record all function calls/returns.
|
|
818
|
+
extern "C" fn functiontrace(
|
|
819
|
+
obj: *mut pyo3::ffi::PyObject,
|
|
820
|
+
frame: *mut pyo3::ffi::PyFrameObject,
|
|
821
|
+
what: i32,
|
|
822
|
+
arg: *mut pyo3::ffi::PyObject,
|
|
823
|
+
) -> i32 {
|
|
824
|
+
let Some(mut writer) = thread_state() else {
|
|
825
|
+
// No reason to look at the event since we can't log it yet.
|
|
826
|
+
return 0;
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
if !STARTED.load(Ordering::Relaxed) {
|
|
830
|
+
// We aren't running (either starting up or shutting down), so shouldn't write
|
|
831
|
+
// anything.
|
|
832
|
+
return 0;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
let time = trace_time();
|
|
836
|
+
let c_functiontrace = *TRACEFUNC.get().expect("tracefunc is set in set_config");
|
|
837
|
+
|
|
838
|
+
// Return the function name when we're in a C_* context.
|
|
839
|
+
let c_func_name = || {
|
|
840
|
+
let func = arg as *mut pyo3::ffi::PyCFunctionObject;
|
|
841
|
+
|
|
842
|
+
unsafe {
|
|
843
|
+
// SAFETY: In the `C_*` cases, `arg` is a non-NULL `*PyCFunctionObject`, which
|
|
844
|
+
// always contains a valid method.
|
|
845
|
+
let method = (*func).m_ml;
|
|
846
|
+
(*method).ml_name
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
// Given a C string sourced from Python, convert it to a Cow that can be serialized.
|
|
850
|
+
let cstr_to_cow = |ptr: *const i8| {
|
|
851
|
+
if !ptr.is_null() {
|
|
852
|
+
unsafe {
|
|
853
|
+
// SAFETY: `ptr` is non-NULL and contains a valid C string.
|
|
854
|
+
// TODO: Can this change underneath us?
|
|
855
|
+
CStr::from_ptr(ptr)
|
|
856
|
+
}
|
|
857
|
+
.to_str()
|
|
858
|
+
.unwrap_or("<invalid string>")
|
|
859
|
+
.into()
|
|
860
|
+
} else {
|
|
861
|
+
Cow::Borrowed("<null>")
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
|
|
865
|
+
match what {
|
|
866
|
+
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);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
pyo3::ffi::PyTrace_C_CALL => {
|
|
875
|
+
let func = unsafe {
|
|
876
|
+
// SAFETY: In the `C_CALL` case, `arg` is guaranteed to be a non-NULL pointer
|
|
877
|
+
// to a `PyCFunctionObject`.
|
|
878
|
+
&*(arg as *mut pyo3::ffi::PyCFunctionObject)
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
let func_name = cstr_to_cow(c_func_name());
|
|
882
|
+
|
|
883
|
+
// We either belong to a module or are a method on a class.
|
|
884
|
+
if !func.m_module.is_null() {
|
|
885
|
+
// NOTE: _Typically_ `m_module` is a PyModule, but there are no requirements on
|
|
886
|
+
// what type it needs to be! In practice, we only see PyModules and PyStrings.
|
|
887
|
+
Python::with_gil(|py| {
|
|
888
|
+
let m_module = unsafe {
|
|
889
|
+
// SAFETY: m_module is a non-NULL pointer to a valid Python object.
|
|
890
|
+
Bound::from_borrowed_ptr(py, func.m_module)
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
if let Ok(module) = m_module.downcast::<pyo3::types::PyModule>() {
|
|
894
|
+
// This is a PyModule, so get its name if possible.
|
|
895
|
+
let name = module.name();
|
|
896
|
+
let module_name = if let Ok(name) = &name {
|
|
897
|
+
name.to_cow().unwrap_or(Cow::Borrowed("<invalid string>"))
|
|
898
|
+
} else {
|
|
899
|
+
Cow::Borrowed("<unnamed module>")
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
function_trace::FunctionTrace::NativeCall {
|
|
903
|
+
module_name,
|
|
904
|
+
func_name,
|
|
905
|
+
time,
|
|
906
|
+
}
|
|
907
|
+
.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
908
|
+
} else {
|
|
909
|
+
let module_name =
|
|
910
|
+
if let Ok(module) = m_module.downcast::<pyo3::types::PyString>() {
|
|
911
|
+
module.to_cow().unwrap_or(Cow::Borrowed("<invalid string>"))
|
|
912
|
+
} else {
|
|
913
|
+
// TODO: tracing log this
|
|
914
|
+
Cow::Borrowed("<unknown module>")
|
|
915
|
+
};
|
|
916
|
+
function_trace::FunctionTrace::NativeCall {
|
|
917
|
+
module_name,
|
|
918
|
+
func_name,
|
|
919
|
+
time,
|
|
920
|
+
}
|
|
921
|
+
.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
922
|
+
}
|
|
923
|
+
})
|
|
924
|
+
} else {
|
|
925
|
+
// We don't belong to a module, so must be a class method.
|
|
926
|
+
let module_name = if !func.m_self.is_null() {
|
|
927
|
+
cstr_to_cow(unsafe {
|
|
928
|
+
// SAFETY: Since `m_self` is non-NULL, it must point to a valid
|
|
929
|
+
// PyObject, which is required to have a live and fully initialized
|
|
930
|
+
// `ob_type` member.
|
|
931
|
+
(*(*func.m_self).ob_type).tp_name
|
|
932
|
+
})
|
|
933
|
+
} else {
|
|
934
|
+
Cow::Borrowed("<unknown module>")
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
function_trace::FunctionTrace::NativeCall {
|
|
938
|
+
module_name,
|
|
939
|
+
func_name,
|
|
940
|
+
time,
|
|
941
|
+
}
|
|
942
|
+
.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
943
|
+
}
|
|
944
|
+
.expect("Failed to emit NativeCall message");
|
|
945
|
+
}
|
|
946
|
+
pyo3::ffi::PyTrace_C_RETURN | pyo3::ffi::PyTrace_C_EXCEPTION => {
|
|
947
|
+
if what == pyo3::ffi::PyTrace_C_EXCEPTION {
|
|
948
|
+
// C exceptions aren't exposed in a particularly interesting fashion, since they're
|
|
949
|
+
// done using the `PyErr_*` functions plus a NULL return value.
|
|
950
|
+
// However, if a `C_EXCEPTION` event is sent, we won't receive the corresponding
|
|
951
|
+
// `C_RETURN` event, so in practice we treat both of these events the same.
|
|
952
|
+
//
|
|
953
|
+
// TODO: Should we try exposing information about them? We'd probably need to
|
|
954
|
+
// change the `Exception` format in `functiontrace-server`.
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function_trace::FunctionTrace::NativeReturn {
|
|
958
|
+
func_name: cstr_to_cow(c_func_name()),
|
|
959
|
+
time,
|
|
960
|
+
}
|
|
961
|
+
.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
962
|
+
.expect("Failed to emit NativeReturn message");
|
|
963
|
+
}
|
|
964
|
+
pyo3::ffi::PyTrace_EXCEPTION => {
|
|
965
|
+
// TODO: Actually handle exceptions and do something interesting.
|
|
966
|
+
// We unfortunately can't get these from `PyEval_SetProfile`, so we'd need to use a
|
|
967
|
+
// different method to capture them.
|
|
968
|
+
}
|
|
969
|
+
_ => {
|
|
970
|
+
// TODO: this shouldn't be possible - tracing log it so we can handle later.
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
0
|
|
975
|
+
}
|
|
857
976
|
}
|
|
858
977
|
|
|
859
978
|
/// Allocation hooks and helpers for tracing Python memory allocator operations.
|
|
@@ -865,14 +984,12 @@ mod allocation_wrappers {
|
|
|
865
984
|
/// Log the given allocation details.
|
|
866
985
|
fn allocation_log<F: FnOnce() -> function_trace::AllocationDetails>(msg: F) {
|
|
867
986
|
if let Some(mut writer) = crate::_functiontrace_rs::thread_state() {
|
|
868
|
-
|
|
987
|
+
function_trace::FunctionTrace::Allocation {
|
|
869
988
|
time: crate::_functiontrace_rs::trace_time(),
|
|
870
989
|
details: msg(),
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
875
|
-
.expect("Failed to emit Allocation message");
|
|
990
|
+
}
|
|
991
|
+
.serialize(&mut rmp_serde::encode::Serializer::new(&mut writer))
|
|
992
|
+
.expect("Failed to emit Allocation message");
|
|
876
993
|
}
|
|
877
994
|
}
|
|
878
995
|
|
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
#include <Python.h>
|
|
2
|
-
#include <frameobject.h>
|
|
3
|
-
#include <sys/types.h>
|
|
4
|
-
#include <pthread.h>
|
|
5
|
-
#include <stdio.h>
|
|
6
|
-
#include <stdbool.h>
|
|
7
|
-
#include <unistd.h>
|
|
8
|
-
|
|
9
|
-
#include "mpack/mpack.h"
|
|
10
|
-
|
|
11
|
-
//#include <assert.h>
|
|
12
|
-
// By default, python c extensions compile with asserts disable and it is not
|
|
13
|
-
// super easy to disable this behavior (AFAIK). Use this custom assert for now.
|
|
14
|
-
void assert_helper(int lineno, char* expr) {
|
|
15
|
-
printf("Assertion failed on line %d: %s\n", lineno, expr);
|
|
16
|
-
exit(-1);
|
|
17
|
-
}
|
|
18
|
-
#undef assert
|
|
19
|
-
#define assert(expr) \
|
|
20
|
-
if (!(expr)) \
|
|
21
|
-
assert_helper(__LINE__, #expr)
|
|
22
|
-
|
|
23
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
24
|
-
// Types
|
|
25
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
26
|
-
|
|
27
|
-
// The messagepack writer for the current thread.
|
|
28
|
-
typedef mpack_writer_t Writer;
|
|
29
|
-
|
|
30
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
31
|
-
// Prototypes
|
|
32
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
33
|
-
static Writer* Fprofile_Writer(void);
|
|
34
|
-
|
|
35
|
-
static int Fprofile_FunctionTrace(PyObject* obj, PyFrameObject* frame, int what, PyObject* arg);
|
|
36
|
-
|
|
37
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
38
|
-
// Globals
|
|
39
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
40
|
-
// This key is used to fetch the thread local instance of the `Writer`
|
|
41
|
-
// structure.
|
|
42
|
-
static pthread_key_t Tss_Key = 0;
|
|
43
|
-
|
|
44
|
-
// True iff we've started tracing and haven't been marked as terminated. When
|
|
45
|
-
// this is set, we're allowed to send messages to the profile generation
|
|
46
|
-
// server.
|
|
47
|
-
static bool started;
|
|
48
|
-
|
|
49
|
-
// The exposed apis of the Rust extension.
|
|
50
|
-
static struct {
|
|
51
|
-
pthread_key_t (*set_config)(bool* config, int (*functiontrace)(PyObject*, PyFrameObject*, int, PyObject*));
|
|
52
|
-
}* rust;
|
|
53
|
-
|
|
54
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
55
|
-
// Server Communication
|
|
56
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
57
|
-
|
|
58
|
-
static Writer* Fprofile_Writer(void) {
|
|
59
|
-
assert(Tss_Key != 0);
|
|
60
|
-
|
|
61
|
-
if (!started) {
|
|
62
|
-
// We aren't running (either starting up or shutting down), so
|
|
63
|
-
// shouldn't write anything.
|
|
64
|
-
return NULL;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Fetch the Writerr for the current thread. It's created as late as
|
|
68
|
-
// the first function call for the thread, so we can plausibly get messages
|
|
69
|
-
// before having anywhere to store/send them to. Drop them if so - they
|
|
70
|
-
// weren't too important if we aren't running code yet.
|
|
71
|
-
Writer* state = pthread_getspecific(Tss_Key);
|
|
72
|
-
if (state == NULL) {
|
|
73
|
-
// The thread hasn't officially started yet, so drop this message.
|
|
74
|
-
return NULL;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return state;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
81
|
-
// Misc
|
|
82
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
83
|
-
|
|
84
|
-
// Given a PyObject (likely `co_name` or similar), return a UTF-8
|
|
85
|
-
// representation.
|
|
86
|
-
static inline const char* Fprofile_UnicodeToUtf8(PyObject* obj) {
|
|
87
|
-
if (obj == NULL) {
|
|
88
|
-
return "<NULL>";
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (PyUnicode_Check(obj)) {
|
|
92
|
-
const char* utf8 = PyUnicode_AsUTF8(obj);
|
|
93
|
-
|
|
94
|
-
return utf8 != NULL ? utf8 : "<DECODE ERROR>";
|
|
95
|
-
} else if (obj == Py_None) {
|
|
96
|
-
return "<NONE>";
|
|
97
|
-
} else {
|
|
98
|
-
return "<UNKNOWN>";
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
103
|
-
// Callbacks
|
|
104
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
105
|
-
static int Fprofile_FunctionTrace(PyObject* obj, PyFrameObject* frame, int what, PyObject* arg) {
|
|
106
|
-
PyCFunctionObject* fn = (PyCFunctionObject*) arg;
|
|
107
|
-
struct timespec tsc = { 0 };
|
|
108
|
-
|
|
109
|
-
Writer* writer = Fprofile_Writer();
|
|
110
|
-
if (writer == NULL) {
|
|
111
|
-
return 0;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
clock_gettime(CLOCK_MONOTONIC, &tsc);
|
|
115
|
-
|
|
116
|
-
switch (what) {
|
|
117
|
-
case PyTrace_CALL:
|
|
118
|
-
mpack_start_array(writer, 5);
|
|
119
|
-
mpack_write_cstr(writer, "Call");
|
|
120
|
-
|
|
121
|
-
mpack_start_array(writer, 2);
|
|
122
|
-
mpack_write_u32(writer, tsc.tv_sec);
|
|
123
|
-
mpack_write_u32(writer, tsc.tv_nsec);
|
|
124
|
-
mpack_finish_array(writer);
|
|
125
|
-
|
|
126
|
-
{
|
|
127
|
-
// TODO: Check if frame is none or similar
|
|
128
|
-
#if PY_VERSION_HEX >= 0x030a00a0
|
|
129
|
-
// Python >= 3.10 should use the standard accessors
|
|
130
|
-
PyCodeObject* code = PyFrame_GetCode(frame);
|
|
131
|
-
int lineno = PyFrame_GetLineNumber(frame);
|
|
132
|
-
#else
|
|
133
|
-
PyCodeObject* code = frame->f_code;
|
|
134
|
-
int lineno = frame->f_lineno;
|
|
135
|
-
#endif
|
|
136
|
-
|
|
137
|
-
#if PY_VERSION_HEX >= 0x030b00a0
|
|
138
|
-
mpack_write_cstr(writer, Fprofile_UnicodeToUtf8(code->co_qualname));
|
|
139
|
-
#else
|
|
140
|
-
mpack_write_cstr(writer, Fprofile_UnicodeToUtf8(code->co_name));
|
|
141
|
-
#endif
|
|
142
|
-
mpack_write_cstr(writer, Fprofile_UnicodeToUtf8(code->co_filename));
|
|
143
|
-
mpack_write_u32(writer, lineno);
|
|
144
|
-
|
|
145
|
-
#if PY_VERSION_HEX >= 0x030a00a0
|
|
146
|
-
Py_DECREF(code);
|
|
147
|
-
#endif
|
|
148
|
-
}
|
|
149
|
-
mpack_finish_array(writer);
|
|
150
|
-
break;
|
|
151
|
-
case PyTrace_RETURN:
|
|
152
|
-
mpack_start_array(writer, 3);
|
|
153
|
-
mpack_write_cstr(writer, "Return");
|
|
154
|
-
|
|
155
|
-
mpack_start_array(writer, 2);
|
|
156
|
-
mpack_write_u32(writer, tsc.tv_sec);
|
|
157
|
-
mpack_write_u32(writer, tsc.tv_nsec);
|
|
158
|
-
mpack_finish_array(writer);
|
|
159
|
-
|
|
160
|
-
{
|
|
161
|
-
#if PY_VERSION_HEX >= 0x030a00a0
|
|
162
|
-
PyCodeObject* code = PyFrame_GetCode(frame);
|
|
163
|
-
#else
|
|
164
|
-
PyCodeObject* code = frame->f_code;
|
|
165
|
-
#endif
|
|
166
|
-
#if PY_VERSION_HEX >= 0x030b00a0
|
|
167
|
-
mpack_write_cstr(writer, Fprofile_UnicodeToUtf8(code->co_qualname));
|
|
168
|
-
#else
|
|
169
|
-
mpack_write_cstr(writer, Fprofile_UnicodeToUtf8(code->co_name));
|
|
170
|
-
#endif
|
|
171
|
-
|
|
172
|
-
#if PY_VERSION_HEX >= 0x030a00a0
|
|
173
|
-
Py_DECREF(code);
|
|
174
|
-
#endif
|
|
175
|
-
}
|
|
176
|
-
mpack_finish_array(writer);
|
|
177
|
-
break;
|
|
178
|
-
case PyTrace_C_CALL: {
|
|
179
|
-
mpack_start_array(writer, 4);
|
|
180
|
-
mpack_write_cstr(writer, "NativeCall");
|
|
181
|
-
|
|
182
|
-
mpack_start_array(writer, 2);
|
|
183
|
-
mpack_write_u32(writer, tsc.tv_sec);
|
|
184
|
-
mpack_write_u32(writer, tsc.tv_nsec);
|
|
185
|
-
mpack_finish_array(writer);
|
|
186
|
-
|
|
187
|
-
{
|
|
188
|
-
// Attempt to determine what module/class this function belongs
|
|
189
|
-
// to.
|
|
190
|
-
PyObject* self = fn->m_self;
|
|
191
|
-
PyObject* module = fn->m_module;
|
|
192
|
-
const char* name = fn->m_ml->ml_name;
|
|
193
|
-
const char* module_name = NULL;
|
|
194
|
-
|
|
195
|
-
// Check if we belong to a module, and if not we must be a
|
|
196
|
-
// method. We do this order to avoid finding that the object
|
|
197
|
-
// we belong to is of type module.
|
|
198
|
-
if (module != NULL) {
|
|
199
|
-
if (PyModule_Check(module)) {
|
|
200
|
-
module_name = PyModule_GetName(module);
|
|
201
|
-
} else if (PyUnicode_Check(module)) {
|
|
202
|
-
module_name = PyUnicode_AsUTF8(module);
|
|
203
|
-
}
|
|
204
|
-
} else if (self != NULL) {
|
|
205
|
-
// This is a method call on a class.
|
|
206
|
-
module_name = self->ob_type->tp_name;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
mpack_write_cstr(writer, name != NULL ? name : "NULL");
|
|
210
|
-
mpack_write_cstr(writer, module_name != NULL ? module_name : "NULL");
|
|
211
|
-
}
|
|
212
|
-
mpack_finish_array(writer);
|
|
213
|
-
break;
|
|
214
|
-
}
|
|
215
|
-
case PyTrace_C_RETURN:
|
|
216
|
-
mpack_start_array(writer, 3);
|
|
217
|
-
mpack_write_cstr(writer, "NativeReturn");
|
|
218
|
-
|
|
219
|
-
mpack_start_array(writer, 2);
|
|
220
|
-
mpack_write_u32(writer, tsc.tv_sec);
|
|
221
|
-
mpack_write_u32(writer, tsc.tv_nsec);
|
|
222
|
-
mpack_finish_array(writer);
|
|
223
|
-
|
|
224
|
-
{
|
|
225
|
-
const char* name = fn->m_ml->ml_name;
|
|
226
|
-
mpack_write_cstr(writer, name != NULL ? name : "NULL");
|
|
227
|
-
}
|
|
228
|
-
mpack_finish_array(writer);
|
|
229
|
-
break;
|
|
230
|
-
default:
|
|
231
|
-
// TODO: We should handle exceptions here (or somewhere similar).
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return 0;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
239
|
-
// Module Initialization
|
|
240
|
-
////////////////////////////////////////////////////////////////////////////////
|
|
241
|
-
|
|
242
|
-
// The set of methods exposed by this module to Python.
|
|
243
|
-
static PyMethodDef methods[] = {
|
|
244
|
-
[0] = {NULL, NULL, 0, NULL}
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
static PyModuleDef module = {
|
|
248
|
-
PyModuleDef_HEAD_INIT,
|
|
249
|
-
"_functiontrace",
|
|
250
|
-
"",
|
|
251
|
-
-1,
|
|
252
|
-
methods
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
PyMODINIT_FUNC PyInit__functiontrace(void) {
|
|
256
|
-
// Setup our janky Rust dlopen alternative so we can use Rust code.
|
|
257
|
-
PyObject* rust_mod = PyImport_ImportModule("_functiontrace_rs");
|
|
258
|
-
|
|
259
|
-
// Verify that the rust extension loads if we're using it.
|
|
260
|
-
if (rust_mod == NULL) {
|
|
261
|
-
perror("Failed to load internal Rust extension");
|
|
262
|
-
exit(-1);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// This returns a pointer to the C API exposed by the Rust module.
|
|
266
|
-
PyObject* py_c_api = PyObject_GetAttrString(rust_mod, "c_api");
|
|
267
|
-
assert(py_c_api != NULL);
|
|
268
|
-
PyObject* c_api = PyObject_CallFunctionObjArgs(py_c_api, NULL);
|
|
269
|
-
|
|
270
|
-
// Save the Rust vtable so everyone can access it
|
|
271
|
-
rust = PyLong_AsVoidPtr(c_api);
|
|
272
|
-
|
|
273
|
-
// Share our globals with Rust
|
|
274
|
-
Tss_Key = rust->set_config(&started, Fprofile_FunctionTrace);
|
|
275
|
-
|
|
276
|
-
return PyModule_Create(&module);
|
|
277
|
-
}
|
|
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
|