retracesoftware-proxy 0.1.0__py3-none-any.whl

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.
File without changes
@@ -0,0 +1,382 @@
1
+ {
2
+ "states": [
3
+ "disabled", {"comment": "Default state when retrace is disabled for a thread"},
4
+ "internal", {"comment": "Default state when retrace is disabled for a thread"},
5
+ "external", {"comment": "When target thread is running outside the python system to be recorded"},
6
+ "retrace", {"comment": "When target thread is running outside the retrace system"},
7
+ "bootstrap", {"comment": "When the target thread is running in the low-level module load machinery"},
8
+ "gc", {"comment": "When the target thread is running inside the pyton garbage collector"}
9
+ ],
10
+
11
+ "recording_path": "recordings/%Y%m%d_%H%M%S_%f",
12
+
13
+ "tracing_levels": {
14
+ "none": [],
15
+ "all": [
16
+ "proxy.ext.disabled.call",
17
+ "proxy.int.disabled.call",
18
+ "proxy.wrapping.new",
19
+ "proxy.wrapping.new.method",
20
+ "proxy.int.handler",
21
+ "proxy.ext.handler",
22
+ "proxy.int.newref",
23
+ "proxy.ext.call",
24
+ "proxy.ext.result",
25
+ "proxy.ext.error",
26
+ "proxy.int.call",
27
+ "proxy.int.result",
28
+ "proxy.int.error",
29
+ "proxy.ext.new.extended",
30
+ "proxy.ext.new.extended.method",
31
+ "proxy.ext.new.extended.member",
32
+ "proxy.int.new.extended",
33
+ "proxy.int.new.extended.method",
34
+ "install.module",
35
+ "install.module.phase",
36
+ "install.module.phase.results"
37
+ ],
38
+ "debug": [
39
+ "proxy.wrapping.new",
40
+ "proxy.wrapping.new.method",
41
+ "proxy.int_to_ext.stack",
42
+ "proxy.ext_to_int.wrap",
43
+ "proxy.int.handler",
44
+ "proxy.int.newref",
45
+ "proxy.ext.call",
46
+ "proxy.ext.result",
47
+ "proxy.ext.error",
48
+ "proxy.int.result",
49
+ "proxy.int.error",
50
+ "proxy.ext.new.extended",
51
+ "proxy.ext.new.extended.method",
52
+ "proxy.ext.new.extended.member",
53
+ "proxy.int.new.extended",
54
+ "proxy.int.new.extended.method",
55
+ "install.module",
56
+ "install.module.phase",
57
+ "install.module.phase.results"
58
+ ]
59
+ },
60
+
61
+ "default_tracing_level": "debug",
62
+
63
+ "exclude_paths": [
64
+ "^.*\\.py$",
65
+ "^.*\\.pyc$",
66
+ "^/tmp$",
67
+ "^/tmp/.*$"
68
+ ],
69
+
70
+ "preload": [
71
+
72
+ ],
73
+
74
+ "predicates": {
75
+ "path": {
76
+ "and": [
77
+ {
78
+ "not": "^.*\\.py$"
79
+ },
80
+ {
81
+ "not": "^.*\\.pyc$"
82
+ },
83
+ {
84
+ "not": "^recordings/.*$"
85
+ }
86
+ ]
87
+ }
88
+ },
89
+
90
+ "type_attribute_filter": {
91
+ "and": [
92
+ ".*",
93
+ {
94
+ "not": [
95
+ "__hash__",
96
+ "__getattribute__",
97
+ "__init_subclass__",
98
+ "__subclasshook__",
99
+ "__class__",
100
+ "__sizeof__",
101
+ "__new__",
102
+ "__del__",
103
+ "__dict__"
104
+ ]
105
+ }
106
+ ]
107
+ },
108
+
109
+ "filters": {
110
+ "functions": [
111
+ {
112
+ "or": [
113
+ {
114
+ "type": "types.BuiltinFunctionType"
115
+ },
116
+ {
117
+ "type": "types.FunctionType"
118
+ }
119
+ ]
120
+ }
121
+ ],
122
+ "all": {
123
+ "and": [
124
+ {
125
+ "or": [
126
+ {
127
+ "type": "type"
128
+ },
129
+ {
130
+ "type": "types.BuiltinFunctionType"
131
+ },
132
+ {
133
+ "type": "types.FunctionType"
134
+ }
135
+ ]
136
+ },
137
+ {
138
+ "noneof": [
139
+ {
140
+ "subtype": "BaseException"
141
+ },
142
+ {
143
+ "value": "$immutable"
144
+ },
145
+ {
146
+ "name": "__spec__"
147
+ },
148
+ {
149
+ "name": "__package__"
150
+ },
151
+ {
152
+ "name": "__loader__"
153
+ }
154
+ ]
155
+ }
156
+ ]
157
+ }
158
+ },
159
+
160
+ "modules": {
161
+ "_imp": {
162
+ "patch_extension_exec": ["exec_dynamic", "exec_builtin"]
163
+ },
164
+ "sys": {
165
+ "with_state": {
166
+ "disabled": ["excepthook"]
167
+ }
168
+ },
169
+
170
+ "importlib._bootstrap": {
171
+ "with_state": {
172
+ "bootstrap": ["_load_unlocked", "_find_spec"]
173
+ }
174
+ },
175
+
176
+ "encodings": {
177
+ "with_state": {
178
+ "disabled": ["search_function"]
179
+ }
180
+ },
181
+ "_retrace_utils": {
182
+ "immutable_types": [
183
+ "PyCFunctionProxy"
184
+ ]
185
+ },
186
+ "os": {
187
+ "immutable_types": [
188
+ "stat_result",
189
+ "terminal_size",
190
+ "statvfs_result"
191
+ ]
192
+ },
193
+ "mmap": {
194
+ "proxy": [
195
+ "mmapXXX"
196
+ ]
197
+ },
198
+ "types": {
199
+ "immutable_types": [
200
+ "TracebackType"
201
+ ]
202
+ },
203
+ "builtins": {
204
+ "replace": {
205
+ "set": "retracesoftware_utils.set",
206
+ "frozenset": "retracesoftware_utils.frozenset"
207
+ },
208
+ "immutable_types": [
209
+ "BaseException",
210
+ "memoryview",
211
+ "int",
212
+ "float",
213
+ "complex",
214
+ "str",
215
+ "bytes",
216
+ "bool",
217
+ "bytearray",
218
+ "type",
219
+ "slice"],
220
+
221
+ "patch_exec": "exec"
222
+ },
223
+ "_thread": {
224
+ "proxy": [
225
+ "allocate",
226
+ "allocate_lock",
227
+ "RLock"
228
+ ],
229
+ "patch_start_new_thread": ["start_new_thread", "start_new"]
230
+ },
231
+ "_datetime": {
232
+ "immutable_types": [
233
+ "datetime",
234
+ "tzinfo",
235
+ "timezone"
236
+ ],
237
+ "proxy_type_attributes": {
238
+ "datetime": [
239
+ "now",
240
+ "utcnow",
241
+ "today"
242
+ ]
243
+ }
244
+ },
245
+ "time": {
246
+ "immutable_types": [
247
+ "struct_time"
248
+ ],
249
+ "proxy": [
250
+ "perf_counter",
251
+ "time",
252
+ "gmtime",
253
+ "localtime",
254
+ "monotonic",
255
+ "monotonic_ns",
256
+ "time_ns"
257
+ ]
258
+ },
259
+ "io": {
260
+ "proxy": ["open_code", "open"],
261
+ "path_predicates": {
262
+ "open_code": "path",
263
+ "open": "file"
264
+ },
265
+ "wrap": {
266
+ "FileIO.readinfo": "retracesoftware.install.edgecases.readinto",
267
+ "BufferedReader.readinfo": "retracesoftware.install.edgecases.readinto",
268
+ "BufferedRandom.readinfo": "retracesoftware.install.edgecases.readinto"
269
+ }
270
+ },
271
+ "pathlib": {
272
+ "immutable_types": [
273
+ "PosixPath"
274
+ ]
275
+ },
276
+ "posix": {
277
+ "immutable_types": [
278
+ "times_result",
279
+ "statvfs_result",
280
+ "uname_result",
281
+ "stat_result",
282
+ "terminal_size",
283
+ "DirEntry"
284
+ ],
285
+
286
+ "proxy_all_except": [
287
+ "fork",
288
+ "register_at_fork",
289
+ "basename",
290
+ "readlink",
291
+ "strerror",
292
+ "listdir",
293
+ "_path_normpath"
294
+ ],
295
+
296
+ "wrappers": {
297
+ "fork_exec": "retracesoftware.install.edgecases.fork_exec",
298
+ "posix_spawn": "retracesoftware.install.edgecases.posix_spawn"
299
+ }
300
+ },
301
+ "_posixsubprocess": {
302
+ "proxy_all_except": []
303
+ },
304
+ "fcntl": {
305
+ "proxy_all_except": []
306
+ },
307
+ "_signal": {
308
+ "proxy_all_except": []
309
+ },
310
+ "_socket": {
311
+ "proxy_all_except": [
312
+ "CAPI"
313
+ ],
314
+ "immutable_types": [
315
+ "error",
316
+ "herror",
317
+ "gaierror",
318
+ "timeout"
319
+ ],
320
+ "wrap": {
321
+ "socket.recvfrom_into": "retracesoftware.install.edgecases.recvfrom_into",
322
+ "socket.recv_into": "retracesoftware.install.edgecases.recv_into",
323
+ "socket.recvmsg_into": "retracesoftware.install.edgecases.recvmsg_into"
324
+ }
325
+ },
326
+ "select": {
327
+ "proxy_all_except": []
328
+ },
329
+ "_sqlite3": {
330
+ "proxy_all_except": []
331
+ },
332
+ "_ssl": {
333
+ "proxy_all_except": [],
334
+
335
+ "proxy_functions": ".*",
336
+ "comment": {
337
+ "proxy_types": [
338
+ "MemoryBIO",
339
+ "_SSLSocket",
340
+ "_SSLContext"
341
+ ],
342
+ "wrap": {
343
+ "_SSLSocket.write": "write"
344
+ }
345
+ },
346
+ "wrap": {
347
+ "_SSLSocket.read": "retracesoftware.install.edgecases.read"
348
+ }
349
+ },
350
+ "_random": {
351
+ "proxy_all_except": [],
352
+ "proxy_types": [
353
+ "Random"
354
+ ],
355
+ "proxy_functions": ".*"
356
+ },
357
+ "_multiprocessing": {
358
+ "proxy_functions": ".*"
359
+ },
360
+ "multiprocessing.context": {
361
+ "proxy": ["_default_context"]
362
+ },
363
+
364
+ "PIL._imaging": {
365
+ "proxy_all_except": ["map_buffer"]
366
+ },
367
+ "psycopg2._psycopg": {
368
+ "proxy_all_except": ["Error"]
369
+ },
370
+
371
+ "_collections": {
372
+ "sync_types": [
373
+ "deque"
374
+ ]
375
+ },
376
+ "_queue": {
377
+ "sync_types": [
378
+ "SimpleQueue"
379
+ ]
380
+ }
381
+ }
382
+ }
File without changes
@@ -0,0 +1,53 @@
1
+ import pkgutil
2
+ import json
3
+ import os
4
+ import datetime
5
+
6
+ from pathlib import Path
7
+
8
+ def debug_level(config):
9
+ if 'RETRACE_DEBUG' in os.environ:
10
+ debug = env_int('RETRACE_DEBUG')
11
+ else:
12
+ debug = config.get('record', {}).get('debug', 1)
13
+
14
+ return debug
15
+
16
+ def env_truthy(key, default=False):
17
+ value = os.getenv(key)
18
+ if value is None:
19
+ return default
20
+ return value.strip().lower() in ("1", "true", "yes", "on")
21
+
22
+ def env_int(key, default = 0):
23
+ value = os.getenv(key)
24
+ if value is None:
25
+ return default
26
+ return int(value)
27
+
28
+ def get_recording_path(config):
29
+ return Path(datetime.datetime.now().strftime(config.format(pid = os.getpid())))
30
+
31
+ def recording_path(config):
32
+ if 'RETRACE_RECORDING_PATH' in os.environ:
33
+ return Path(os.environ['RETRACE_RECORDING_PATH'])
34
+ else:
35
+ recording_path = get_recording_path(config.get('record_path', 'recordings'))
36
+ os.environ['RETRACE_RECORDING_PATH'] = str(recording_path)
37
+ return recording_path
38
+
39
+ def load_config(filename):
40
+
41
+ data = pkgutil.get_data("retracesoftware", filename)
42
+ assert data is not None
43
+
44
+ config = json.loads(data.decode("utf-8"))
45
+
46
+ config['debug_level'] = debug_level(config)
47
+
48
+ # config['recording_path'] = recording_path(config)
49
+
50
+ config['verbose'] = env_truthy('RETRACE_VERBOSE')
51
+
52
+ return config
53
+
@@ -0,0 +1,220 @@
1
+ # from .proxytype import *
2
+
3
+ import functools
4
+ import os
5
+
6
+ from retracesoftware.install import globals
7
+
8
+ def recvfrom_into(target):
9
+ @functools.wraps(target)
10
+ def wrapper(self, buffer, nbytes = 0, flags = 0):
11
+ data, address = self.recvfrom(len(buffer) if nbytes == 0 else nbytes, flags)
12
+ buffer[0:len(data)] = data
13
+ return len(data), address
14
+ return wrapper
15
+
16
+ def recv_into(target):
17
+ @functools.wraps(target)
18
+ def wrapper(self, buffer, nbytes = 0, flags = 0):
19
+ data = self.recv(len(buffer) if nbytes == 0 else nbytes, flags)
20
+ buffer[0:len(data)] = data
21
+ return len(data)
22
+ return wrapper
23
+
24
+ def recvmsg_into(target):
25
+ @functools.wraps(target)
26
+ def wrapper(self, buffers, ancbufsize = 0, flags = 0):
27
+ raise NotImplementedError('TODO')
28
+ return wrapper
29
+
30
+ def read(target):
31
+ @functools.wraps(target)
32
+ def wrapper(self, *args):
33
+ # super_type = super(type(self), self)
34
+
35
+ if len(args) == 0:
36
+ return target(self)
37
+ else:
38
+ buflen = args[0]
39
+
40
+ # pdb.set_trace()
41
+
42
+ data = target(self, buflen)
43
+
44
+ if len(args) == 1:
45
+ return data
46
+ else:
47
+ buffer = args[1]
48
+
49
+ buffer[0:len(data)] = data
50
+
51
+ return len(data)
52
+ return wrapper
53
+
54
+ def write(target):
55
+ @functools.wraps(target)
56
+ def wrapper(self, byteslike):
57
+ return target(byteslike.tobytes())
58
+
59
+ return wrapper
60
+
61
+ def readinto(target):
62
+ @functools.wraps(target)
63
+ def wrapper(self, buffer):
64
+ bytes = self.read(buffer.nbytes)
65
+ buffer[:len(bytes)] = bytes
66
+ return len(bytes)
67
+ return wrapper
68
+
69
+ typewrappers = {
70
+ '_socket': {
71
+ 'socket': {
72
+ 'recvfrom_into': recvfrom_into,
73
+ 'recv_into': recv_into,
74
+ 'recvmsg_into': recvmsg_into
75
+ }
76
+ },
77
+ '_ssl': {
78
+ '_SSLSocket': {
79
+ 'read': read,
80
+ # 'write': write
81
+ }
82
+ },
83
+ 'io': {
84
+ 'FileIO': {
85
+ 'readinto': readinto
86
+ },
87
+ 'BufferedReader': {
88
+ 'readinto': readinto
89
+ },
90
+ 'BufferedRandom': {
91
+ 'readinto': readinto
92
+ }
93
+ }
94
+ }
95
+
96
+ def patchtype(module, name, cls : type):
97
+ if module in typewrappers:
98
+ if name in typewrappers[module]:
99
+ for method,patcher in typewrappers[module][name].items():
100
+ setattr(cls, method, patcher(getattr(cls, method)))
101
+
102
+ def transform_argument(target : callable, position : int, name : str, transform : callable, default = None):
103
+ assert callable(transform)
104
+ assert callable(target)
105
+
106
+ @functools.wraps(target)
107
+ def wrapper(*args, **kwargs):
108
+ if name in kwargs:
109
+ kwargs[name] = transform(kwargs[name])
110
+ elif len(args) > position:
111
+ args = list(args)
112
+ args[position] = transform(args[position])
113
+ else:
114
+ kwargs[name] = transform(default)
115
+
116
+ # print(f'Running: {args} {kwargs}')
117
+ return target(*args, **kwargs)
118
+
119
+ return wrapper
120
+
121
+ def retrace_env():
122
+ import threading
123
+
124
+ env = {
125
+ 'RETRACE_RECORDING_PATH': globals.recording_path.next_subprocess_path(),
126
+ # 'RETRACE_PARENT_THREAD_ID': threading.current_thread().__retrace_thread_id__,
127
+ # 'RETRACE_PARENT_EXEC_COUNTER': str(next_exec_counter()
128
+ 'RETRACE_MODE': 'record'
129
+ # 'RETRACE_MODE': 'proxy' if os.getenv('RETRACE_MODE') == 'proxy' else 'record'
130
+ }
131
+ # a timestamped directory off current directory
132
+ # controlled by config
133
+ # 1. if recording_dir is set use that
134
+ # 2. if recording_dir is not set look for
135
+
136
+ # env['RETRACE_EXECUTION_ID'] = hashlib.md5(json.dumps(env).encode('UTF8')).hexdigest()
137
+
138
+ return env
139
+
140
+ def fork_exec(target):
141
+ def transform(env):
142
+ r = [f'{k}={v}'.encode('utf-8') for k,v in retrace_env().items()]
143
+ return env + r if env else r
144
+
145
+ return transform_argument(target = target,
146
+ position = 5,
147
+ name = 'env',
148
+ transform = transform, default = os.environ)
149
+
150
+ def posix_spawn(target):
151
+ def transform(env):
152
+ r = retrace_env()
153
+ return {**env, **r}
154
+
155
+ return transform_argument(target = target,
156
+ position = 2,
157
+ name = 'env',
158
+ transform = transform, default = os.environ)
159
+
160
+ function_patchers = {
161
+ '_posixsubprocess': {
162
+ 'fork_exec': fork_exec
163
+ },
164
+ 'posix': {
165
+ 'posix_spawn': posix_spawn
166
+ }
167
+ }
168
+
169
+ # import traceback
170
+
171
+ def typepatcher(cls : type):
172
+
173
+ # print(f'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! type: {cls} created')
174
+ # traceback.print_stack()
175
+
176
+ return typewrappers.get(cls.__module__, {}).get(cls.__name__, {})
177
+
178
+ # if cls.__module__ in typewrappers:
179
+ # # print(f'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! TYPEWRAPPER for {cls}')
180
+
181
+ # mod = typewrappers[cls.__module__]
182
+
183
+ # return mod.get(cls.__name__, {})
184
+
185
+ # if cls.__name__ in mod:
186
+ # # if cls.__name__ == '_SSLSocket':
187
+ # # breakpoint()
188
+
189
+ # log.info("Applying specialized typewrapper to %s, updated slots: %s", cls, list(mod[cls.__name__].keys()))
190
+
191
+ # for name,value in mod[cls.__name__].items():
192
+ # setattr(cls, name, value(getattr(cls, name)))
193
+
194
+ # # slots = {'__module__': cls.__module__, '__slots__': ()}
195
+ # # slots.update(mod[cls.__name__])
196
+ # # return type(cls.__name__, (cls, ), slots)
197
+
198
+ # return cls
199
+
200
+ def typewrapper(cls : type):
201
+
202
+ # print(f'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! type: {cls} created')
203
+ # traceback.print_stack()
204
+
205
+ if cls.__module__ in typewrappers:
206
+ # print(f'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! TYPEWRAPPER for {cls}')
207
+
208
+ mod = typewrappers[cls.__module__]
209
+
210
+ if cls.__name__ in mod:
211
+ # if cls.__name__ == '_SSLSocket':
212
+ # breakpoint()
213
+
214
+ log.info("Applying specialized typewrapper to %s, updated slots: %s", classname(cls), list(mod[cls.__name__].keys()))
215
+
216
+ slots = {'__module__': cls.__module__, '__slots__': ()}
217
+ slots.update(mod[cls.__name__])
218
+ return type(cls.__name__, (cls, ), slots)
219
+
220
+ return cls
@@ -0,0 +1,17 @@
1
+ class RecordingPath:
2
+ def __init__(self, path):
3
+ self.path = path
4
+ self.subprocess_counter = 0
5
+ self.fork_counter = 0
6
+
7
+ def next_subprocess_path(self):
8
+ path = self.path / f'subprocess-{self.subprocess_counter}'
9
+ self.subprocess_counter = self.subprocess_counter + 1
10
+ return path
11
+
12
+ def next_fork_path(self):
13
+ path = self.path / f'fork-{self.fork_counter}'
14
+ self.fork_counter = self.fork_counter + 1
15
+ return path
16
+
17
+ recording_path = None