functiontrace 0.3.7__tar.gz → 0.4.0__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.0/Cargo.toml +19 -0
- functiontrace-0.4.0/MANIFEST.in +3 -0
- {functiontrace-0.3.7/functiontrace.egg-info → functiontrace-0.4.0}/PKG-INFO +3 -3
- {functiontrace-0.3.7 → functiontrace-0.4.0}/_functiontrace.c +117 -16
- {functiontrace-0.3.7 → functiontrace-0.4.0/functiontrace.egg-info}/PKG-INFO +3 -3
- {functiontrace-0.3.7 → functiontrace-0.4.0}/functiontrace.egg-info/SOURCES.txt +3 -1
- {functiontrace-0.3.7 → functiontrace-0.4.0}/functiontrace.py +10 -4
- {functiontrace-0.3.7 → functiontrace-0.4.0}/pyproject.toml +13 -3
- functiontrace-0.4.0/rust/lib.rs +100 -0
- functiontrace-0.3.7/MANIFEST.in +0 -1
- {functiontrace-0.3.7 → functiontrace-0.4.0}/README.rst +0 -0
- {functiontrace-0.3.7 → functiontrace-0.4.0}/functiontrace.egg-info/dependency_links.txt +0 -0
- {functiontrace-0.3.7 → functiontrace-0.4.0}/functiontrace.egg-info/entry_points.txt +0 -0
- {functiontrace-0.3.7 → functiontrace-0.4.0}/functiontrace.egg-info/top_level.txt +0 -0
- {functiontrace-0.3.7 → functiontrace-0.4.0}/mpack/LICENSE +0 -0
- {functiontrace-0.3.7 → functiontrace-0.4.0}/mpack/mpack.c +0 -0
- {functiontrace-0.3.7 → functiontrace-0.4.0}/mpack/mpack.h +0 -0
- {functiontrace-0.3.7 → functiontrace-0.4.0}/setup.cfg +0 -0
- {functiontrace-0.3.7 → functiontrace-0.4.0}/setup.py +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "_functiontrace-rs"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
|
|
6
|
+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
7
|
+
|
|
8
|
+
[dependencies]
|
|
9
|
+
pyo3 = "0.24"
|
|
10
|
+
|
|
11
|
+
[profile.release-lto]
|
|
12
|
+
inherits = "release"
|
|
13
|
+
lto = true
|
|
14
|
+
|
|
15
|
+
[lib]
|
|
16
|
+
# See https://github.com/PyO3/pyo3 for details
|
|
17
|
+
name = "_lib" # private module to be nested into Python package
|
|
18
|
+
crate-type = ["cdylib"]
|
|
19
|
+
path = "rust/lib.rs"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: functiontrace
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
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>
|
|
@@ -16,7 +16,7 @@ Classifier: Environment :: Console
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
17
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
18
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
19
|
-
Requires-Python:
|
|
19
|
+
Requires-Python: >=3.7
|
|
20
20
|
Description-Content-Type: text/x-rst
|
|
21
21
|
|
|
22
22
|
Py-Functiontrace
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
#include "mpack/mpack.h"
|
|
12
12
|
|
|
13
|
+
// We use the Rust implementation for all data transport.
|
|
14
|
+
#define RUST_SOCKETS
|
|
15
|
+
|
|
13
16
|
//#include <assert.h>
|
|
14
17
|
// By default, python c extensions compile with asserts disable and it is not
|
|
15
18
|
// super easy to disable this behavior (AFAIK). Use this custom assert for now.
|
|
@@ -56,7 +59,9 @@ typedef struct {
|
|
|
56
59
|
// Prototypes
|
|
57
60
|
////////////////////////////////////////////////////////////////////////////////
|
|
58
61
|
static Writer* Fprofile_Writer(void);
|
|
62
|
+
#if !defined(RUST_SOCKETS)
|
|
59
63
|
static void Mpack_Flush(Writer* writer, const char* buffer, size_t bytes);
|
|
64
|
+
#endif
|
|
60
65
|
|
|
61
66
|
static Timestamp Fprofile_Time(void);
|
|
62
67
|
|
|
@@ -117,6 +122,11 @@ static struct {
|
|
|
117
122
|
// The Unix socket address we're using to communicate with the profile
|
|
118
123
|
// server.
|
|
119
124
|
struct sockaddr_un socket;
|
|
125
|
+
|
|
126
|
+
#if defined(RUST_SOCKETS)
|
|
127
|
+
// The Rust extension.
|
|
128
|
+
PyObject* rust;
|
|
129
|
+
#endif
|
|
120
130
|
} moduleConfiguration;
|
|
121
131
|
|
|
122
132
|
// The set of methods exposed by this module to Python.
|
|
@@ -146,7 +156,7 @@ static PyMethodDef methods[] = {
|
|
|
146
156
|
.ml_flags = METH_VARARGS,
|
|
147
157
|
.ml_doc = "INTERNAL USE ONLY"
|
|
148
158
|
},
|
|
149
|
-
[METHOD_TERMINATE
|
|
159
|
+
[METHOD_TERMINATE] = {
|
|
150
160
|
.ml_name = "terminate",
|
|
151
161
|
.ml_meth = (PyCFunction) Fprofile_Terminate,
|
|
152
162
|
.ml_flags = METH_NOARGS,
|
|
@@ -181,6 +191,7 @@ static Writer* Fprofile_Writer(void) {
|
|
|
181
191
|
return &state->writer;
|
|
182
192
|
}
|
|
183
193
|
|
|
194
|
+
#if !defined(RUST_SOCKETS)
|
|
184
195
|
static void Mpack_Flush(Writer* writer, const char* buffer, size_t bytes) {
|
|
185
196
|
// The context passed to Mpack is the socket to send on.
|
|
186
197
|
intptr_t socket = (intptr_t) writer->context;
|
|
@@ -190,6 +201,7 @@ static void Mpack_Flush(Writer* writer, const char* buffer, size_t bytes) {
|
|
|
190
201
|
exit(-1);
|
|
191
202
|
}
|
|
192
203
|
}
|
|
204
|
+
#endif
|
|
193
205
|
|
|
194
206
|
////////////////////////////////////////////////////////////////////////////////
|
|
195
207
|
// Misc
|
|
@@ -732,6 +744,9 @@ static void Fprofile_ThreadTeardown(void* key) {
|
|
|
732
744
|
}
|
|
733
745
|
|
|
734
746
|
mpack_writer_flush_message(&state->writer);
|
|
747
|
+
|
|
748
|
+
// TODO: We should tear down the socket to avoid leaking O(# threads)
|
|
749
|
+
// resources.
|
|
735
750
|
}
|
|
736
751
|
|
|
737
752
|
static PyObject* Fprofile_MpexitHook(HookedFunction* hook, PyObject* args, PyObject* kwargs) {
|
|
@@ -804,6 +819,14 @@ static PyObject* Fprofile_StartTrace(PyObject* obj, PyObject* args, PyObject* kw
|
|
|
804
819
|
}
|
|
805
820
|
}
|
|
806
821
|
|
|
822
|
+
if (moduleConfiguration.pid != 0) {
|
|
823
|
+
// This can be triggered by running `functiontrace.begin_tracing()`
|
|
824
|
+
// from inside a process that's already being traced.
|
|
825
|
+
// We'll fail in this situation, but might as well print a mildly
|
|
826
|
+
// helpful error message here.
|
|
827
|
+
perror("Functiontrace doubly initialized! Aborting.");
|
|
828
|
+
exit(-1);
|
|
829
|
+
}
|
|
807
830
|
moduleConfiguration.pid = getpid();
|
|
808
831
|
|
|
809
832
|
// Mark that we'll need to forget some information on forks. In
|
|
@@ -828,10 +851,12 @@ static PyObject* Fprofile_StartTrace(PyObject* obj, PyObject* args, PyObject* kw
|
|
|
828
851
|
Timestamp time = Fprofile_Time();
|
|
829
852
|
char traceInitialization[1024] = { 0 };
|
|
830
853
|
Writer initWriter = { 0 };
|
|
831
|
-
intptr_t sock = -1;
|
|
832
854
|
const char* version = Py_GetVersion();
|
|
833
855
|
const char* platform = Py_GetPlatform();
|
|
834
856
|
char* outputDirectory = NULL;
|
|
857
|
+
#if !defined(RUST_SOCKETS)
|
|
858
|
+
intptr_t sock = -1;
|
|
859
|
+
#endif
|
|
835
860
|
|
|
836
861
|
// Parse the args to figure out what our output directory is.
|
|
837
862
|
if (!PyArg_ParseTuple(args, "s", &outputDirectory)) {
|
|
@@ -840,7 +865,22 @@ static PyObject* Fprofile_StartTrace(PyObject* obj, PyObject* args, PyObject* kw
|
|
|
840
865
|
exit(-1);
|
|
841
866
|
}
|
|
842
867
|
|
|
843
|
-
|
|
868
|
+
#if defined(RUST_SOCKETS)
|
|
869
|
+
// NOTE: We only import this module once. Importing while tracing is
|
|
870
|
+
// live will be recorded, which exposes internals users don't care
|
|
871
|
+
// about and breaks the invariant that we send a `RegisterThread`
|
|
872
|
+
// message first when threads are traced.
|
|
873
|
+
moduleConfiguration.rust = PyImport_ImportModule("_functiontrace_rs._lib");
|
|
874
|
+
|
|
875
|
+
// Verify that the rust extension loads if we're using it.
|
|
876
|
+
if (moduleConfiguration.rust == NULL) {
|
|
877
|
+
perror("Failed to load internal Rust extension");
|
|
878
|
+
exit(-1);
|
|
879
|
+
}
|
|
880
|
+
#endif
|
|
881
|
+
|
|
882
|
+
// Spawn the server. We'll wait a bit for it to start accepting
|
|
883
|
+
// sockets.
|
|
844
884
|
pid_t server = fork();
|
|
845
885
|
if (server == 0) {
|
|
846
886
|
// Detach ourselves as a daemon
|
|
@@ -868,6 +908,17 @@ static PyObject* Fprofile_StartTrace(PyObject* obj, PyObject* args, PyObject* kw
|
|
|
868
908
|
sizeof(moduleConfiguration.socket.sun_path),
|
|
869
909
|
"/tmp/functiontrace-server.sock.%d", server);
|
|
870
910
|
|
|
911
|
+
// Register an env var so subprocesses know how to connect to our
|
|
912
|
+
// profile.
|
|
913
|
+
if (setenv(breadcrumb, (char*) &moduleConfiguration.socket.sun_path, 0)) {
|
|
914
|
+
perror("Failed to set recursive breadcrumb");
|
|
915
|
+
exit(-1);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Setup a local Mpack context for this first message.
|
|
919
|
+
mpack_writer_init(&initWriter, (char*) &traceInitialization, sizeof(traceInitialization));
|
|
920
|
+
|
|
921
|
+
#if !defined(RUST_SOCKETS)
|
|
871
922
|
// Generate the TraceInitialization message to the profiler server telling
|
|
872
923
|
// them about the profile we'll be creating.
|
|
873
924
|
if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
|
|
@@ -894,17 +945,26 @@ static PyObject* Fprofile_StartTrace(PyObject* obj, PyObject* args, PyObject* kw
|
|
|
894
945
|
usleep(1000);
|
|
895
946
|
}
|
|
896
947
|
|
|
897
|
-
// Register an env var so subprocesses know how to connect to our
|
|
898
|
-
// profile.
|
|
899
|
-
if (setenv(breadcrumb, (char*) &moduleConfiguration.socket.sun_path, 0)) {
|
|
900
|
-
perror("Failed to set recursive breadcrumb");
|
|
901
|
-
exit(-1);
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
// Setup a local Mpack context for this first message.
|
|
905
|
-
mpack_writer_init(&initWriter, (char*) &traceInitialization, sizeof(traceInitialization));
|
|
906
948
|
mpack_writer_set_context(&initWriter, (void*) sock);
|
|
907
949
|
mpack_writer_set_flush(&initWriter, &Mpack_Flush);
|
|
950
|
+
#else
|
|
951
|
+
{
|
|
952
|
+
PyObject* msg_init = PyObject_GetAttrString(moduleConfiguration.rust, "message_initialization");
|
|
953
|
+
assert(msg_init != NULL);
|
|
954
|
+
|
|
955
|
+
PyObject* arg = PyLong_FromVoidPtr(&moduleConfiguration.socket.sun_path);
|
|
956
|
+
PyObject* context = PyObject_CallFunctionObjArgs(msg_init, arg, NULL);
|
|
957
|
+
assert(context != NULL);
|
|
958
|
+
|
|
959
|
+
PyObject* msg_flush = PyObject_GetAttrString(moduleConfiguration.rust, "message_flush");
|
|
960
|
+
assert(msg_flush != NULL);
|
|
961
|
+
PyObject* mpack_flush = PyObject_CallFunctionObjArgs(msg_flush, NULL);
|
|
962
|
+
assert(mpack_flush != NULL);
|
|
963
|
+
|
|
964
|
+
mpack_writer_set_context(&initWriter, PyLong_AsVoidPtr(context));
|
|
965
|
+
mpack_writer_set_flush(&initWriter, PyLong_AsVoidPtr(mpack_flush));
|
|
966
|
+
}
|
|
967
|
+
#endif
|
|
908
968
|
|
|
909
969
|
mpack_start_map(&initWriter, 5);
|
|
910
970
|
{
|
|
@@ -939,10 +999,22 @@ static PyObject* Fprofile_StartTrace(PyObject* obj, PyObject* args, PyObject* kw
|
|
|
939
999
|
// out first.
|
|
940
1000
|
mpack_writer_flush_message(&initWriter);
|
|
941
1001
|
|
|
1002
|
+
// Close the initialization socket to trigger `functiontrace-server` to
|
|
1003
|
+
// read our message, since otherwise it'll wait forever for more data.
|
|
1004
|
+
#if !defined(RUST_SOCKETS)
|
|
942
1005
|
if (shutdown(sock, SHUT_WR) != 0) {
|
|
943
1006
|
perror("Failed to close TraceInitialization socket");
|
|
944
1007
|
exit(-1);
|
|
945
1008
|
}
|
|
1009
|
+
#else
|
|
1010
|
+
{
|
|
1011
|
+
PyObject* msg_shutdown = PyObject_GetAttrString(moduleConfiguration.rust, "message_shutdown");
|
|
1012
|
+
assert(msg_shutdown != NULL);
|
|
1013
|
+
|
|
1014
|
+
PyObject* socket = PyLong_FromVoidPtr(mpack_writer_context(&initWriter));
|
|
1015
|
+
assert(PyObject_CallFunctionObjArgs(msg_shutdown, socket, NULL) != NULL);
|
|
1016
|
+
}
|
|
1017
|
+
#endif
|
|
946
1018
|
} else {
|
|
947
1019
|
// Read the socket addr out of `existingProfile` so our threads know
|
|
948
1020
|
// how to talk to the profile server.
|
|
@@ -1025,6 +1097,14 @@ static ThreadState* Fprofile_CreateThreadState(void) {
|
|
|
1025
1097
|
exit(-1);
|
|
1026
1098
|
}
|
|
1027
1099
|
|
|
1100
|
+
// Setup msgpack and a socket for this thread to report tracing info on.
|
|
1101
|
+
//
|
|
1102
|
+
// We need to do this before any allocations or Python function calls
|
|
1103
|
+
// occur, since consumers can't tell whether mpack is fully initialized or
|
|
1104
|
+
// not.
|
|
1105
|
+
mpack_writer_init(&state->writer, (char*) &state->buffer, sizeof(state->buffer));
|
|
1106
|
+
|
|
1107
|
+
#if !defined(RUST_SOCKETS)
|
|
1028
1108
|
// Allocate a socket to report this thread's information back to the
|
|
1029
1109
|
// profile server.
|
|
1030
1110
|
if ((state->socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
|
|
@@ -1038,11 +1118,33 @@ static ThreadState* Fprofile_CreateThreadState(void) {
|
|
|
1038
1118
|
exit(-1);
|
|
1039
1119
|
}
|
|
1040
1120
|
|
|
1041
|
-
//
|
|
1042
|
-
//
|
|
1043
|
-
mpack_writer_init(&state->writer, (char*) &state->buffer, sizeof(state->buffer));
|
|
1121
|
+
// Configure Mpack to flush events to this thread's socket once the buffer
|
|
1122
|
+
// fills up.
|
|
1044
1123
|
mpack_writer_set_context(&state->writer, (void*) state->socket);
|
|
1045
1124
|
mpack_writer_set_flush(&state->writer, &Mpack_Flush);
|
|
1125
|
+
#else
|
|
1126
|
+
{
|
|
1127
|
+
PyObject* msg_init = PyObject_GetAttrString(moduleConfiguration.rust, "message_initialization");
|
|
1128
|
+
assert(msg_init != NULL);
|
|
1129
|
+
|
|
1130
|
+
// TODO: Convert this to a shared Rust configuration object so we don't
|
|
1131
|
+
// need to pass it back and forth.
|
|
1132
|
+
PyObject* arg = PyLong_FromVoidPtr(&moduleConfiguration.socket.sun_path);
|
|
1133
|
+
PyObject* context = PyObject_CallFunctionObjArgs(msg_init, arg, NULL);
|
|
1134
|
+
assert(context != NULL);
|
|
1135
|
+
|
|
1136
|
+
PyObject* msg_flush = PyObject_GetAttrString(moduleConfiguration.rust, "message_flush");
|
|
1137
|
+
assert(msg_flush != NULL);
|
|
1138
|
+
PyObject* mpack_flush = PyObject_CallFunctionObjArgs(msg_flush, NULL);
|
|
1139
|
+
assert(mpack_flush != NULL);
|
|
1140
|
+
|
|
1141
|
+
// Re-initialize msgpack, since we've done some allocations and Python
|
|
1142
|
+
// calls that we don't want to actually record.
|
|
1143
|
+
mpack_writer_init(&state->writer, (char*) &state->buffer, sizeof(state->buffer));
|
|
1144
|
+
mpack_writer_set_context(&state->writer, PyLong_AsVoidPtr(context));
|
|
1145
|
+
mpack_writer_set_flush(&state->writer, PyLong_AsVoidPtr(mpack_flush));
|
|
1146
|
+
}
|
|
1147
|
+
#endif
|
|
1046
1148
|
|
|
1047
1149
|
// Send a message about our thread's registration.
|
|
1048
1150
|
Timestamp time = Fprofile_Time();
|
|
@@ -1099,4 +1201,3 @@ static void Fprofile_ResetThreadState(void) {
|
|
|
1099
1201
|
moduleConfiguration.pid = getpid();
|
|
1100
1202
|
Fprofile_CreateThreadState();
|
|
1101
1203
|
}
|
|
1102
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: functiontrace
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
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>
|
|
@@ -16,7 +16,7 @@ Classifier: Environment :: Console
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
17
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
18
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
19
|
-
Requires-Python:
|
|
19
|
+
Requires-Python: >=3.7
|
|
20
20
|
Description-Content-Type: text/x-rst
|
|
21
21
|
|
|
22
22
|
Py-Functiontrace
|
|
@@ -41,7 +41,7 @@ def setup_dependencies():
|
|
|
41
41
|
# Generate wrappers for the various Python versions we support to ensure
|
|
42
42
|
# they're included in our PATH.
|
|
43
43
|
wrap_pythons = ["python", "python3"] + [
|
|
44
|
-
"python3.{}".format(minor) for minor in range(6,
|
|
44
|
+
"python3.{}".format(minor) for minor in range(6, 13)
|
|
45
45
|
]
|
|
46
46
|
for python in wrap_pythons:
|
|
47
47
|
with open(os.path.join(tempdir, python), "w") as f:
|
|
@@ -55,9 +55,7 @@ def main():
|
|
|
55
55
|
"--trace-memory",
|
|
56
56
|
"--trace_memory",
|
|
57
57
|
action="store_true",
|
|
58
|
-
help=""
|
|
59
|
-
memory allocations/frees when enabled. This may add
|
|
60
|
-
tracing overhead, so is disabled by default.""",
|
|
58
|
+
help="Trace memory allocations/frees when enabled. This may add tracing overhead, so is disabled by default.",
|
|
61
59
|
)
|
|
62
60
|
parser.add_argument(
|
|
63
61
|
"--output-dir",
|
|
@@ -66,6 +64,12 @@ def main():
|
|
|
66
64
|
default=os.getcwd(),
|
|
67
65
|
help="The directory to output trace files to",
|
|
68
66
|
)
|
|
67
|
+
parser.add_argument(
|
|
68
|
+
"--no-compression",
|
|
69
|
+
"--no_compression",
|
|
70
|
+
action="store_true",
|
|
71
|
+
help="Disable compression of traces, which can be unnecessarily slow if filesize is not a concern.",
|
|
72
|
+
)
|
|
69
73
|
parser.add_argument(
|
|
70
74
|
"-v", "--version", action="version", version=_functiontrace.__version__
|
|
71
75
|
)
|
|
@@ -107,6 +111,8 @@ def main():
|
|
|
107
111
|
setup_dependencies()
|
|
108
112
|
|
|
109
113
|
# Setup our tracing environment, including configuring tracing features.
|
|
114
|
+
if args.no_compression:
|
|
115
|
+
os.environ["FUNCTIONTRACE_COMPRESSION"] = "false"
|
|
110
116
|
if args.trace_memory:
|
|
111
117
|
_functiontrace.config_tracememory()
|
|
112
118
|
_functiontrace.begin_tracing(args.output_dir)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "functiontrace"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "A profiling tool that traces your application's execution"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Matt Bryant", email = "functiontrace@programsareproofs.com"},
|
|
@@ -8,7 +8,13 @@ authors = [
|
|
|
8
8
|
]
|
|
9
9
|
license = { text = "The Prosperity Public License 3.0.0" }
|
|
10
10
|
readme = "README.rst"
|
|
11
|
-
|
|
11
|
+
# NOTE: Actively tested on Python 3.7 - 3.12 and may plausibly break on greater
|
|
12
|
+
# versions due to C extension hacks. However, setting a maximum version causes
|
|
13
|
+
# new Python versions to simply install very old versions of Functiontrace that
|
|
14
|
+
# didn't have the `requires-python` annotation on them and are also unlikely to
|
|
15
|
+
# work. Best to only set the minimum and let bug reports notify us of issues
|
|
16
|
+
# with new Python releases.
|
|
17
|
+
requires-python = ">=3.7"
|
|
12
18
|
keywords = [
|
|
13
19
|
"profiler",
|
|
14
20
|
"debugger"
|
|
@@ -35,5 +41,9 @@ functiontrace = "functiontrace:main"
|
|
|
35
41
|
functiontrace = "functiontrace"
|
|
36
42
|
|
|
37
43
|
[build-system]
|
|
38
|
-
requires = ["setuptools", "toml"]
|
|
44
|
+
requires = ["setuptools", "toml", "setuptools-rust"]
|
|
39
45
|
build-backend = "setuptools.build_meta"
|
|
46
|
+
|
|
47
|
+
[[tool.setuptools-rust.ext-modules]]
|
|
48
|
+
target = "_functiontrace_rs._lib" # Must match `lib.name` in Cargo.toml
|
|
49
|
+
binding = "PyO3"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#[pyo3::pymodule]
|
|
2
|
+
mod _lib {
|
|
3
|
+
use pyo3::prelude::*;
|
|
4
|
+
use std::ffi::{CStr, OsStr};
|
|
5
|
+
use std::os::raw::c_char;
|
|
6
|
+
use std::os::unix::ffi::OsStrExt;
|
|
7
|
+
use std::os::unix::net::UnixStream;
|
|
8
|
+
|
|
9
|
+
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
10
|
+
// Socket Communication
|
|
11
|
+
////////////////////////////////////////////////////////////////////////////////////////////////
|
|
12
|
+
|
|
13
|
+
/// Initializes and returns a pointer to the [`UnixStream`] we'll communicate to
|
|
14
|
+
/// `functiontrace-server` on.
|
|
15
|
+
///
|
|
16
|
+
/// The returned value will be stored as [`MpackWriter.context`] and used by [`Mpack_Flush`].
|
|
17
|
+
#[pyfunction]
|
|
18
|
+
fn message_initialization(socket: usize) -> usize {
|
|
19
|
+
let c_socket = unsafe {
|
|
20
|
+
// SAFETY: socket is a valid `char*` converted via `PyLong_FromVoidPtr`.
|
|
21
|
+
CStr::from_ptr(socket as *const c_char)
|
|
22
|
+
};
|
|
23
|
+
let sockaddr = OsStr::from_bytes(c_socket.to_bytes());
|
|
24
|
+
|
|
25
|
+
// The functiontrace-server might not be ready to receive connections yet, so we retry for
|
|
26
|
+
// a bit.
|
|
27
|
+
let start = std::time::Instant::now();
|
|
28
|
+
let socket = loop {
|
|
29
|
+
match UnixStream::connect(sockaddr) {
|
|
30
|
+
Ok(s) => {
|
|
31
|
+
break Box::new(s);
|
|
32
|
+
}
|
|
33
|
+
_ => {
|
|
34
|
+
if start.elapsed() > std::time::Duration::from_millis(1000) {
|
|
35
|
+
panic!("Timed out trying to connect to functiontrace-server");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
std::thread::sleep(std::time::Duration::from_millis(10));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// This must be freed by `message_shutdown`
|
|
44
|
+
Box::into_raw(socket) as usize
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Shutdown a socket that was opened by [`message_initialize`], triggering
|
|
48
|
+
/// `functiontrace-server` to read the data sent over it.
|
|
49
|
+
///
|
|
50
|
+
/// If we forget this, we'll leak sockets and a bit of memory.
|
|
51
|
+
#[pyfunction]
|
|
52
|
+
fn message_shutdown(socket: usize) {
|
|
53
|
+
let socket = unsafe {
|
|
54
|
+
// SAFETY: socket is a `UnixStream` emitted from `message_initialization` encoded via
|
|
55
|
+
// `PyLong_FromVoidPtr`.
|
|
56
|
+
Box::from_raw(socket as *mut UnixStream)
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if let Err(e) = socket.shutdown(std::net::Shutdown::Both) {
|
|
60
|
+
panic!("Failed to close socket: {e}");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// The initial subset of the `mpack_writer_t` struct, which `mpack.h` will use to pass us our
|
|
65
|
+
/// per-thread context in.
|
|
66
|
+
#[repr(C)]
|
|
67
|
+
struct MpackWriter {
|
|
68
|
+
flush: *const (),
|
|
69
|
+
error_fn: *const (),
|
|
70
|
+
teardown: *const (),
|
|
71
|
+
context: *mut UnixStream,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Flush events to this socket once the given buffer is full according to `mpack`.
|
|
75
|
+
#[unsafe(no_mangle)]
|
|
76
|
+
extern "C" fn Mpack_Flush(writer: *const MpackWriter, buffer: *const u8, bytes: usize) {
|
|
77
|
+
use std::io::Write;
|
|
78
|
+
|
|
79
|
+
let data = unsafe {
|
|
80
|
+
// SAFETY: `bytes` represents the initialized number of bytes in `buffer` when called
|
|
81
|
+
// by `mpack_writer_flush_message`.
|
|
82
|
+
std::slice::from_raw_parts(buffer, bytes)
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
let mut socket = unsafe {
|
|
86
|
+
// SAFETY: `writer` is valid and `writer->context` contains a `UnixStream` emitted from
|
|
87
|
+
// `message_initialization`.
|
|
88
|
+
&*(*writer).context
|
|
89
|
+
};
|
|
90
|
+
if let Err(e) = socket.write_all(data) {
|
|
91
|
+
panic!("Socket send failed: {e}");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// C accessor for [`Mpack_Flush`]
|
|
96
|
+
#[pyfunction]
|
|
97
|
+
fn message_flush() -> PyResult<usize> {
|
|
98
|
+
Ok(Mpack_Flush as usize)
|
|
99
|
+
}
|
|
100
|
+
}
|
functiontrace-0.3.7/MANIFEST.in
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
graft mpack
|
|
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
|