sf-veritas 0.10.5__cp313-cp313-manylinux_2_28_x86_64.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.

Potentially problematic release.


This version of sf-veritas might be problematic. Click here for more details.

Files changed (133) hide show
  1. sf_veritas/__init__.py +20 -0
  2. sf_veritas/_sffastlog.c +889 -0
  3. sf_veritas/_sffastlog.cpython-313-x86_64-linux-gnu.so +0 -0
  4. sf_veritas/_sffastnet.c +924 -0
  5. sf_veritas/_sffastnet.cpython-313-x86_64-linux-gnu.so +0 -0
  6. sf_veritas/_sffastnetworkrequest.c +730 -0
  7. sf_veritas/_sffastnetworkrequest.cpython-313-x86_64-linux-gnu.so +0 -0
  8. sf_veritas/_sffuncspan.c +2155 -0
  9. sf_veritas/_sffuncspan.cpython-313-x86_64-linux-gnu.so +0 -0
  10. sf_veritas/_sffuncspan_config.c +617 -0
  11. sf_veritas/_sffuncspan_config.cpython-313-x86_64-linux-gnu.so +0 -0
  12. sf_veritas/_sfheadercheck.c +341 -0
  13. sf_veritas/_sfheadercheck.cpython-313-x86_64-linux-gnu.so +0 -0
  14. sf_veritas/_sfnetworkhop.c +1451 -0
  15. sf_veritas/_sfnetworkhop.cpython-313-x86_64-linux-gnu.so +0 -0
  16. sf_veritas/_sfservice.c +1175 -0
  17. sf_veritas/_sfservice.cpython-313-x86_64-linux-gnu.so +0 -0
  18. sf_veritas/_sfteepreload.c +5167 -0
  19. sf_veritas/app_config.py +50 -0
  20. sf_veritas/cli.py +336 -0
  21. sf_veritas/constants.py +10 -0
  22. sf_veritas/custom_excepthook.py +304 -0
  23. sf_veritas/custom_log_handler.py +129 -0
  24. sf_veritas/custom_output_wrapper.py +144 -0
  25. sf_veritas/custom_print.py +146 -0
  26. sf_veritas/django_app.py +5 -0
  27. sf_veritas/env_vars.py +186 -0
  28. sf_veritas/exception_handling_middleware.py +18 -0
  29. sf_veritas/exception_metaclass.py +69 -0
  30. sf_veritas/fast_frame_info.py +116 -0
  31. sf_veritas/fast_network_hop.py +293 -0
  32. sf_veritas/frame_tools.py +112 -0
  33. sf_veritas/funcspan_config_loader.py +556 -0
  34. sf_veritas/function_span_profiler.py +1174 -0
  35. sf_veritas/import_hook.py +62 -0
  36. sf_veritas/infra_details/__init__.py +3 -0
  37. sf_veritas/infra_details/get_infra_details.py +24 -0
  38. sf_veritas/infra_details/kubernetes/__init__.py +3 -0
  39. sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
  40. sf_veritas/infra_details/kubernetes/get_details.py +7 -0
  41. sf_veritas/infra_details/running_on/__init__.py +17 -0
  42. sf_veritas/infra_details/running_on/kubernetes.py +11 -0
  43. sf_veritas/interceptors.py +497 -0
  44. sf_veritas/libsfnettee.so +0 -0
  45. sf_veritas/local_env_detect.py +118 -0
  46. sf_veritas/package_metadata.py +6 -0
  47. sf_veritas/patches/__init__.py +0 -0
  48. sf_veritas/patches/_patch_tracker.py +74 -0
  49. sf_veritas/patches/concurrent_futures.py +19 -0
  50. sf_veritas/patches/constants.py +1 -0
  51. sf_veritas/patches/exceptions.py +82 -0
  52. sf_veritas/patches/multiprocessing.py +32 -0
  53. sf_veritas/patches/network_libraries/__init__.py +76 -0
  54. sf_veritas/patches/network_libraries/aiohttp.py +281 -0
  55. sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
  56. sf_veritas/patches/network_libraries/http_client.py +419 -0
  57. sf_veritas/patches/network_libraries/httpcore.py +515 -0
  58. sf_veritas/patches/network_libraries/httplib2.py +204 -0
  59. sf_veritas/patches/network_libraries/httpx.py +544 -0
  60. sf_veritas/patches/network_libraries/niquests.py +211 -0
  61. sf_veritas/patches/network_libraries/pycurl.py +392 -0
  62. sf_veritas/patches/network_libraries/requests.py +639 -0
  63. sf_veritas/patches/network_libraries/tornado.py +341 -0
  64. sf_veritas/patches/network_libraries/treq.py +270 -0
  65. sf_veritas/patches/network_libraries/urllib_request.py +477 -0
  66. sf_veritas/patches/network_libraries/utils.py +398 -0
  67. sf_veritas/patches/os.py +17 -0
  68. sf_veritas/patches/threading.py +218 -0
  69. sf_veritas/patches/web_frameworks/__init__.py +54 -0
  70. sf_veritas/patches/web_frameworks/aiohttp.py +793 -0
  71. sf_veritas/patches/web_frameworks/async_websocket_consumer.py +317 -0
  72. sf_veritas/patches/web_frameworks/blacksheep.py +527 -0
  73. sf_veritas/patches/web_frameworks/bottle.py +502 -0
  74. sf_veritas/patches/web_frameworks/cherrypy.py +678 -0
  75. sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
  76. sf_veritas/patches/web_frameworks/django.py +944 -0
  77. sf_veritas/patches/web_frameworks/eve.py +395 -0
  78. sf_veritas/patches/web_frameworks/falcon.py +926 -0
  79. sf_veritas/patches/web_frameworks/fastapi.py +724 -0
  80. sf_veritas/patches/web_frameworks/flask.py +520 -0
  81. sf_veritas/patches/web_frameworks/klein.py +501 -0
  82. sf_veritas/patches/web_frameworks/litestar.py +551 -0
  83. sf_veritas/patches/web_frameworks/pyramid.py +428 -0
  84. sf_veritas/patches/web_frameworks/quart.py +824 -0
  85. sf_veritas/patches/web_frameworks/robyn.py +697 -0
  86. sf_veritas/patches/web_frameworks/sanic.py +857 -0
  87. sf_veritas/patches/web_frameworks/starlette.py +723 -0
  88. sf_veritas/patches/web_frameworks/strawberry.py +813 -0
  89. sf_veritas/patches/web_frameworks/tornado.py +481 -0
  90. sf_veritas/patches/web_frameworks/utils.py +91 -0
  91. sf_veritas/print_override.py +13 -0
  92. sf_veritas/regular_data_transmitter.py +409 -0
  93. sf_veritas/request_interceptor.py +401 -0
  94. sf_veritas/request_utils.py +550 -0
  95. sf_veritas/server_status.py +1 -0
  96. sf_veritas/shutdown_flag.py +11 -0
  97. sf_veritas/subprocess_startup.py +3 -0
  98. sf_veritas/test_cli.py +145 -0
  99. sf_veritas/thread_local.py +970 -0
  100. sf_veritas/timeutil.py +114 -0
  101. sf_veritas/transmit_exception_to_sailfish.py +28 -0
  102. sf_veritas/transmitter.py +132 -0
  103. sf_veritas/types.py +47 -0
  104. sf_veritas/unified_interceptor.py +1586 -0
  105. sf_veritas/utils.py +39 -0
  106. sf_veritas-0.10.5.dist-info/METADATA +97 -0
  107. sf_veritas-0.10.5.dist-info/RECORD +133 -0
  108. sf_veritas-0.10.5.dist-info/WHEEL +5 -0
  109. sf_veritas-0.10.5.dist-info/entry_points.txt +2 -0
  110. sf_veritas-0.10.5.dist-info/top_level.txt +1 -0
  111. sf_veritas.libs/libbrotlicommon-6ce2a53c.so.1.0.6 +0 -0
  112. sf_veritas.libs/libbrotlidec-811d1be3.so.1.0.6 +0 -0
  113. sf_veritas.libs/libcom_err-730ca923.so.2.1 +0 -0
  114. sf_veritas.libs/libcrypt-52aca757.so.1.1.0 +0 -0
  115. sf_veritas.libs/libcrypto-bdaed0ea.so.1.1.1k +0 -0
  116. sf_veritas.libs/libcurl-eaa3cf66.so.4.5.0 +0 -0
  117. sf_veritas.libs/libgssapi_krb5-323bbd21.so.2.2 +0 -0
  118. sf_veritas.libs/libidn2-2f4a5893.so.0.3.6 +0 -0
  119. sf_veritas.libs/libk5crypto-9a74ff38.so.3.1 +0 -0
  120. sf_veritas.libs/libkeyutils-2777d33d.so.1.6 +0 -0
  121. sf_veritas.libs/libkrb5-a55300e8.so.3.3 +0 -0
  122. sf_veritas.libs/libkrb5support-e6594cfc.so.0.1 +0 -0
  123. sf_veritas.libs/liblber-2-d20824ef.4.so.2.10.9 +0 -0
  124. sf_veritas.libs/libldap-2-cea2a960.4.so.2.10.9 +0 -0
  125. sf_veritas.libs/libnghttp2-39367a22.so.14.17.0 +0 -0
  126. sf_veritas.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
  127. sf_veritas.libs/libpsl-99becdd3.so.5.3.1 +0 -0
  128. sf_veritas.libs/libsasl2-7de4d792.so.3.0.0 +0 -0
  129. sf_veritas.libs/libselinux-d0805dcb.so.1 +0 -0
  130. sf_veritas.libs/libssh-c11d285b.so.4.8.7 +0 -0
  131. sf_veritas.libs/libssl-60250281.so.1.1.1k +0 -0
  132. sf_veritas.libs/libunistring-05abdd40.so.2.1.0 +0 -0
  133. sf_veritas.libs/libuuid-95b83d40.so.1.3.0 +0 -0
@@ -0,0 +1,617 @@
1
+ // sf_veritas/_sffuncspan_config.c
2
+ // Ultra-fast configuration system for function span capture (<5ns lookups)
3
+ #define PY_SSIZE_T_CLEAN
4
+ #include <Python.h>
5
+ #include <stdint.h>
6
+ #include <stdlib.h>
7
+ #include <string.h>
8
+ #include <pthread.h>
9
+
10
+ // ---------- Configuration Structure ----------
11
+ // 32 bytes, cache-line friendly
12
+ typedef struct {
13
+ uint8_t include_arguments;
14
+ uint8_t include_return_value;
15
+ uint8_t autocapture_all_children;
16
+ uint8_t _padding;
17
+ float sample_rate;
18
+ uint32_t arg_limit_mb;
19
+ uint32_t return_limit_mb;
20
+ uint64_t hash; // Pre-computed for validation
21
+ } sf_funcspan_config_t;
22
+
23
+ // Thread-local storage for header overrides (highest priority)
24
+ typedef struct {
25
+ sf_funcspan_config_t config;
26
+ uint8_t has_override;
27
+ uint8_t _padding[7]; // Align to 32 bytes
28
+ } sf_thread_config_t;
29
+
30
+ static _Thread_local sf_thread_config_t g_thread_config = {0};
31
+
32
+ // ---------- Hash Table for O(1) Lookups ----------
33
+ // Using power-of-2 capacity for fast modulo (bitwise AND)
34
+ typedef struct {
35
+ sf_funcspan_config_t* entries; // Dense array of configs
36
+ uint64_t* keys; // File path hashes
37
+ uint32_t* indices; // Hash table -> entries index
38
+ uint32_t capacity; // Power of 2
39
+ uint32_t size; // Number of entries
40
+ } sf_config_table_t;
41
+
42
+ // Global lookup tables (built at startup)
43
+ static sf_config_table_t g_file_configs = {0};
44
+ static sf_config_table_t g_func_configs = {0};
45
+ static sf_funcspan_config_t g_default_config = {
46
+ .include_arguments = 1,
47
+ .include_return_value = 1,
48
+ .autocapture_all_children = 1,
49
+ ._padding = 0,
50
+ .sample_rate = 1.0f,
51
+ .arg_limit_mb = 1,
52
+ .return_limit_mb = 1,
53
+ .hash = 0
54
+ };
55
+
56
+ // Lock for table modifications (only used at startup)
57
+ static pthread_mutex_t g_config_mutex = PTHREAD_MUTEX_INITIALIZER;
58
+
59
+ // ---------- Fast Hash Function (xxHash-like) ----------
60
+ // Simple, fast hash for string keys (~2ns)
61
+ static inline uint64_t fast_hash(const char *str, size_t len) {
62
+ const uint64_t PRIME64_1 = 11400714785074694791ULL;
63
+ const uint64_t PRIME64_2 = 14029467366897019727ULL;
64
+ const uint64_t PRIME64_3 = 1609587929392839161ULL;
65
+ const uint64_t PRIME64_4 = 9650029242287828579ULL;
66
+ const uint64_t PRIME64_5 = 2870177450012600261ULL;
67
+
68
+ uint64_t h = PRIME64_5 + len;
69
+ const uint8_t *data = (const uint8_t*)str;
70
+
71
+ // Process 8 bytes at a time
72
+ while (len >= 8) {
73
+ h ^= *(const uint64_t*)data * PRIME64_2;
74
+ h = (h << 31) | (h >> 33);
75
+ h *= PRIME64_1;
76
+ data += 8;
77
+ len -= 8;
78
+ }
79
+
80
+ // Process remaining bytes
81
+ while (len--) {
82
+ h ^= (*data++) * PRIME64_5;
83
+ h = (h << 11) | (h >> 53);
84
+ h *= PRIME64_1;
85
+ }
86
+
87
+ // Final mix
88
+ h ^= h >> 33;
89
+ h *= PRIME64_2;
90
+ h ^= h >> 29;
91
+ h *= PRIME64_3;
92
+ h ^= h >> 32;
93
+
94
+ return h;
95
+ }
96
+
97
+ // ---------- Config Table Operations ----------
98
+ static int table_init(sf_config_table_t *table, uint32_t initial_capacity) {
99
+ // Round up to next power of 2
100
+ uint32_t capacity = 16;
101
+ while (capacity < initial_capacity) capacity <<= 1;
102
+
103
+ table->entries = (sf_funcspan_config_t*)calloc(capacity, sizeof(sf_funcspan_config_t));
104
+ table->keys = (uint64_t*)calloc(capacity, sizeof(uint64_t));
105
+ table->indices = (uint32_t*)malloc(capacity * sizeof(uint32_t));
106
+
107
+ if (!table->entries || !table->keys || !table->indices) {
108
+ free(table->entries);
109
+ free(table->keys);
110
+ free(table->indices);
111
+ return 0;
112
+ }
113
+
114
+ // Initialize indices to UINT32_MAX (empty marker)
115
+ for (uint32_t i = 0; i < capacity; i++) {
116
+ table->indices[i] = UINT32_MAX;
117
+ }
118
+
119
+ table->capacity = capacity;
120
+ table->size = 0;
121
+ return 1;
122
+ }
123
+
124
+ static void table_free(sf_config_table_t *table) {
125
+ free(table->entries);
126
+ free(table->keys);
127
+ free(table->indices);
128
+ memset(table, 0, sizeof(sf_config_table_t));
129
+ }
130
+
131
+ // Resize table when load factor > 0.75
132
+ static int table_resize(sf_config_table_t *table) {
133
+ uint32_t new_capacity = table->capacity * 2;
134
+ uint32_t *new_indices = (uint32_t*)malloc(new_capacity * sizeof(uint32_t));
135
+
136
+ if (!new_indices) return 0;
137
+
138
+ // Initialize new indices
139
+ for (uint32_t i = 0; i < new_capacity; i++) {
140
+ new_indices[i] = UINT32_MAX;
141
+ }
142
+
143
+ // Rehash all entries
144
+ for (uint32_t i = 0; i < table->size; i++) {
145
+ uint64_t hash = table->keys[i];
146
+ uint32_t slot = hash & (new_capacity - 1);
147
+
148
+ // Linear probing
149
+ while (new_indices[slot] != UINT32_MAX) {
150
+ slot = (slot + 1) & (new_capacity - 1);
151
+ }
152
+
153
+ new_indices[slot] = i;
154
+ }
155
+
156
+ free(table->indices);
157
+ table->indices = new_indices;
158
+ table->capacity = new_capacity;
159
+ return 1;
160
+ }
161
+
162
+ static int table_insert(sf_config_table_t *table, uint64_t hash, const sf_funcspan_config_t *config) {
163
+ pthread_mutex_lock(&g_config_mutex);
164
+
165
+ // Check if we need to resize
166
+ if (table->size >= (table->capacity * 3) / 4) {
167
+ if (!table_resize(table)) {
168
+ pthread_mutex_unlock(&g_config_mutex);
169
+ return 0;
170
+ }
171
+ }
172
+
173
+ // Find slot using linear probing
174
+ uint32_t slot = hash & (table->capacity - 1);
175
+ while (table->indices[slot] != UINT32_MAX) {
176
+ // Check if key already exists (update case)
177
+ uint32_t idx = table->indices[slot];
178
+ if (table->keys[idx] == hash) {
179
+ // Update existing entry
180
+ table->entries[idx] = *config;
181
+ table->entries[idx].hash = hash;
182
+ pthread_mutex_unlock(&g_config_mutex);
183
+ return 1;
184
+ }
185
+ slot = (slot + 1) & (table->capacity - 1);
186
+ }
187
+
188
+ // Insert new entry
189
+ uint32_t idx = table->size++;
190
+ table->keys[idx] = hash;
191
+ table->entries[idx] = *config;
192
+ table->entries[idx].hash = hash;
193
+ table->indices[slot] = idx;
194
+
195
+ pthread_mutex_unlock(&g_config_mutex);
196
+ return 1;
197
+ }
198
+
199
+ // ---------- Ultra-Fast Lookup (<5ns) ----------
200
+ static inline const sf_funcspan_config_t* config_lookup(const char *file_path, const char *func_name) {
201
+ // 1. Check thread-local override first (highest priority, ~1ns)
202
+ if (g_thread_config.has_override) {
203
+ return &g_thread_config.config;
204
+ }
205
+
206
+ // 2. Build combined key for function-level lookup
207
+ // Format: "file_path:func_name"
208
+ char key_buf[512];
209
+ size_t file_len = strlen(file_path);
210
+ size_t func_len = strlen(func_name);
211
+
212
+ if (file_len + func_len + 2 < sizeof(key_buf)) {
213
+ memcpy(key_buf, file_path, file_len);
214
+ key_buf[file_len] = ':';
215
+ memcpy(key_buf + file_len + 1, func_name, func_len);
216
+ key_buf[file_len + 1 + func_len] = '\0';
217
+
218
+ // 3. Compute hash (fast, ~2ns)
219
+ uint64_t hash = fast_hash(key_buf, file_len + 1 + func_len);
220
+
221
+ // 4. Prefetch cache line (parallel with hash)
222
+ __builtin_prefetch(&g_func_configs.indices[hash & (g_func_configs.capacity - 1)]);
223
+
224
+ // 5. Lookup in function table (~1ns)
225
+ uint32_t slot = hash & (g_func_configs.capacity - 1);
226
+ uint32_t idx = g_func_configs.indices[slot];
227
+
228
+ if (idx != UINT32_MAX && g_func_configs.keys[idx] == hash) {
229
+ return &g_func_configs.entries[idx];
230
+ }
231
+ }
232
+
233
+ // 6. Fallback to file-level lookup
234
+ uint64_t file_hash = fast_hash(file_path, file_len);
235
+ __builtin_prefetch(&g_file_configs.indices[file_hash & (g_file_configs.capacity - 1)]);
236
+
237
+ uint32_t slot = file_hash & (g_file_configs.capacity - 1);
238
+ uint32_t idx = g_file_configs.indices[slot];
239
+
240
+ if (idx != UINT32_MAX && g_file_configs.keys[idx] == file_hash) {
241
+ return &g_file_configs.entries[idx];
242
+ }
243
+
244
+ // 7. Return default config
245
+ return &g_default_config;
246
+ }
247
+
248
+ // ---------- Header Parsing ----------
249
+ // Parse header format: "include_arguments-include_return_value-arg_limit_mb-return_limit_mb-autocapture_all_children-sample_rate"
250
+ // Example: "1-1-1-1-1-1.0" or "0-0-2-2-1-0.5"
251
+ static int parse_header_override(const char *header, sf_funcspan_config_t *config) {
252
+ if (!header || !config) return 0;
253
+
254
+ // Parse using sscanf (fast enough for header parsing, ~100ns)
255
+ int values[5];
256
+ float sample_rate;
257
+
258
+ int parsed = sscanf(header, "%d-%d-%d-%d-%d-%f",
259
+ &values[0], &values[1], &values[2],
260
+ &values[3], &values[4], &sample_rate);
261
+
262
+ if (parsed != 6) return 0; // Parse error
263
+
264
+ // Validate values
265
+ if (sample_rate < 0.0f || sample_rate > 1.0f) return 0;
266
+ for (int i = 0; i < 5; i++) {
267
+ if (values[i] < 0) return 0;
268
+ }
269
+
270
+ // Set config
271
+ config->include_arguments = (uint8_t)values[0];
272
+ config->include_return_value = (uint8_t)values[1];
273
+ config->arg_limit_mb = (uint32_t)values[2];
274
+ config->return_limit_mb = (uint32_t)values[3];
275
+ config->autocapture_all_children = (uint8_t)values[4];
276
+ config->sample_rate = sample_rate;
277
+ config->_padding = 0;
278
+ config->hash = 0;
279
+
280
+ return 1; // Success
281
+ }
282
+
283
+ // ---------- Python API ----------
284
+
285
+ static PyObject* py_init(PyObject *self, PyObject *args) {
286
+ PyObject *config_dict;
287
+
288
+ if (!PyArg_ParseTuple(args, "O", &config_dict)) {
289
+ return NULL;
290
+ }
291
+
292
+ if (!PyDict_Check(config_dict)) {
293
+ PyErr_SetString(PyExc_TypeError, "config must be a dictionary");
294
+ return NULL;
295
+ }
296
+
297
+ // Initialize default config from dict
298
+ PyObject *val;
299
+
300
+ val = PyDict_GetItemString(config_dict, "include_arguments");
301
+ if (val && PyBool_Check(val)) {
302
+ g_default_config.include_arguments = (val == Py_True) ? 1 : 0;
303
+ }
304
+
305
+ val = PyDict_GetItemString(config_dict, "include_return_value");
306
+ if (val && PyBool_Check(val)) {
307
+ g_default_config.include_return_value = (val == Py_True) ? 1 : 0;
308
+ }
309
+
310
+ val = PyDict_GetItemString(config_dict, "autocapture_all_children");
311
+ if (val && PyBool_Check(val)) {
312
+ g_default_config.autocapture_all_children = (val == Py_True) ? 1 : 0;
313
+ }
314
+
315
+ val = PyDict_GetItemString(config_dict, "arg_limit_mb");
316
+ if (val && PyLong_Check(val)) {
317
+ g_default_config.arg_limit_mb = (uint32_t)PyLong_AsLong(val);
318
+ }
319
+
320
+ val = PyDict_GetItemString(config_dict, "return_limit_mb");
321
+ if (val && PyLong_Check(val)) {
322
+ g_default_config.return_limit_mb = (uint32_t)PyLong_AsLong(val);
323
+ }
324
+
325
+ val = PyDict_GetItemString(config_dict, "sample_rate");
326
+ if (val && PyFloat_Check(val)) {
327
+ g_default_config.sample_rate = (float)PyFloat_AsDouble(val);
328
+ }
329
+
330
+ // Initialize hash tables
331
+ if (g_file_configs.capacity == 0) {
332
+ if (!table_init(&g_file_configs, 1024)) {
333
+ PyErr_SetString(PyExc_RuntimeError, "Failed to initialize file config table");
334
+ return NULL;
335
+ }
336
+ }
337
+
338
+ if (g_func_configs.capacity == 0) {
339
+ if (!table_init(&g_func_configs, 4096)) {
340
+ PyErr_SetString(PyExc_RuntimeError, "Failed to initialize function config table");
341
+ return NULL;
342
+ }
343
+ }
344
+
345
+ Py_RETURN_NONE;
346
+ }
347
+
348
+ static PyObject* py_add_file(PyObject *self, PyObject *args) {
349
+ const char *file_path;
350
+ PyObject *config_dict;
351
+
352
+ if (!PyArg_ParseTuple(args, "sO", &file_path, &config_dict)) {
353
+ return NULL;
354
+ }
355
+
356
+ if (!PyDict_Check(config_dict)) {
357
+ PyErr_SetString(PyExc_TypeError, "config must be a dictionary");
358
+ return NULL;
359
+ }
360
+
361
+ // Build config from dict
362
+ sf_funcspan_config_t config = g_default_config; // Start with defaults
363
+ PyObject *val;
364
+
365
+ val = PyDict_GetItemString(config_dict, "include_arguments");
366
+ if (val && PyBool_Check(val)) {
367
+ config.include_arguments = (val == Py_True) ? 1 : 0;
368
+ }
369
+
370
+ val = PyDict_GetItemString(config_dict, "include_return_value");
371
+ if (val && PyBool_Check(val)) {
372
+ config.include_return_value = (val == Py_True) ? 1 : 0;
373
+ }
374
+
375
+ val = PyDict_GetItemString(config_dict, "autocapture_all_children");
376
+ if (val && PyBool_Check(val)) {
377
+ config.autocapture_all_children = (val == Py_True) ? 1 : 0;
378
+ }
379
+
380
+ val = PyDict_GetItemString(config_dict, "arg_limit_mb");
381
+ if (val && PyLong_Check(val)) {
382
+ config.arg_limit_mb = (uint32_t)PyLong_AsLong(val);
383
+ }
384
+
385
+ val = PyDict_GetItemString(config_dict, "return_limit_mb");
386
+ if (val && PyLong_Check(val)) {
387
+ config.return_limit_mb = (uint32_t)PyLong_AsLong(val);
388
+ }
389
+
390
+ val = PyDict_GetItemString(config_dict, "sample_rate");
391
+ if (val && PyFloat_Check(val)) {
392
+ config.sample_rate = (float)PyFloat_AsDouble(val);
393
+ }
394
+
395
+ // Compute hash and insert
396
+ uint64_t hash = fast_hash(file_path, strlen(file_path));
397
+
398
+ if (!table_insert(&g_file_configs, hash, &config)) {
399
+ PyErr_SetString(PyExc_RuntimeError, "Failed to insert file config");
400
+ return NULL;
401
+ }
402
+
403
+ Py_RETURN_NONE;
404
+ }
405
+
406
+ static PyObject* py_add_function(PyObject *self, PyObject *args) {
407
+ const char *file_path;
408
+ const char *func_name;
409
+ PyObject *config_dict;
410
+
411
+ if (!PyArg_ParseTuple(args, "ssO", &file_path, &func_name, &config_dict)) {
412
+ return NULL;
413
+ }
414
+
415
+ if (!PyDict_Check(config_dict)) {
416
+ PyErr_SetString(PyExc_TypeError, "config must be a dictionary");
417
+ return NULL;
418
+ }
419
+
420
+ // Build config from dict (same as add_file)
421
+ sf_funcspan_config_t config = g_default_config;
422
+ PyObject *val;
423
+
424
+ val = PyDict_GetItemString(config_dict, "include_arguments");
425
+ if (val && PyBool_Check(val)) {
426
+ config.include_arguments = (val == Py_True) ? 1 : 0;
427
+ }
428
+
429
+ val = PyDict_GetItemString(config_dict, "include_return_value");
430
+ if (val && PyBool_Check(val)) {
431
+ config.include_return_value = (val == Py_True) ? 1 : 0;
432
+ }
433
+
434
+ val = PyDict_GetItemString(config_dict, "autocapture_all_children");
435
+ if (val && PyBool_Check(val)) {
436
+ config.autocapture_all_children = (val == Py_True) ? 1 : 0;
437
+ }
438
+
439
+ val = PyDict_GetItemString(config_dict, "arg_limit_mb");
440
+ if (val && PyLong_Check(val)) {
441
+ config.arg_limit_mb = (uint32_t)PyLong_AsLong(val);
442
+ }
443
+
444
+ val = PyDict_GetItemString(config_dict, "return_limit_mb");
445
+ if (val && PyLong_Check(val)) {
446
+ config.return_limit_mb = (uint32_t)PyLong_AsLong(val);
447
+ }
448
+
449
+ val = PyDict_GetItemString(config_dict, "sample_rate");
450
+ if (val && PyFloat_Check(val)) {
451
+ config.sample_rate = (float)PyFloat_AsDouble(val);
452
+ }
453
+
454
+ // Build combined key: "file_path:func_name"
455
+ size_t file_len = strlen(file_path);
456
+ size_t func_len = strlen(func_name);
457
+ char *key = (char*)malloc(file_len + func_len + 2);
458
+
459
+ if (!key) {
460
+ PyErr_SetString(PyExc_MemoryError, "Failed to allocate key");
461
+ return NULL;
462
+ }
463
+
464
+ memcpy(key, file_path, file_len);
465
+ key[file_len] = ':';
466
+ memcpy(key + file_len + 1, func_name, func_len);
467
+ key[file_len + 1 + func_len] = '\0';
468
+
469
+ uint64_t hash = fast_hash(key, file_len + 1 + func_len);
470
+ free(key);
471
+
472
+ if (!table_insert(&g_func_configs, hash, &config)) {
473
+ PyErr_SetString(PyExc_RuntimeError, "Failed to insert function config");
474
+ return NULL;
475
+ }
476
+
477
+ Py_RETURN_NONE;
478
+ }
479
+
480
+ static PyObject* py_set_thread_override(PyObject *self, PyObject *args) {
481
+ const char *header;
482
+
483
+ if (!PyArg_ParseTuple(args, "s", &header)) {
484
+ return NULL;
485
+ }
486
+
487
+ sf_funcspan_config_t config;
488
+ if (!parse_header_override(header, &config)) {
489
+ PyErr_SetString(PyExc_ValueError, "Invalid header format. Expected: 'include_arguments-include_return_value-arg_limit_mb-return_limit_mb-autocapture_all_children-sample_rate'");
490
+ return NULL;
491
+ }
492
+
493
+ g_thread_config.config = config;
494
+ g_thread_config.has_override = 1;
495
+
496
+ Py_RETURN_NONE;
497
+ }
498
+
499
+ static PyObject* py_clear_thread_override(PyObject *self, PyObject *args) {
500
+ g_thread_config.has_override = 0;
501
+ memset(&g_thread_config.config, 0, sizeof(sf_funcspan_config_t));
502
+ Py_RETURN_NONE;
503
+ }
504
+
505
+ static PyObject* py_get_thread_override(PyObject *self, PyObject *args) {
506
+ // Return current thread-local override as formatted string, or None if not set
507
+ if (!g_thread_config.has_override) {
508
+ Py_RETURN_NONE;
509
+ }
510
+
511
+ const sf_funcspan_config_t *config = &g_thread_config.config;
512
+
513
+ // Format: "include_arguments-include_return_value-arg_limit_mb-return_limit_mb-autocapture_all_children-sample_rate"
514
+ // Example: "1-1-1-1-1-1.0" or "0-0-2-2-1-0.5"
515
+ char buffer[128];
516
+ int written = snprintf(
517
+ buffer,
518
+ sizeof(buffer),
519
+ "%d-%d-%u-%u-%d-%.2f",
520
+ (int)config->include_arguments,
521
+ (int)config->include_return_value,
522
+ config->arg_limit_mb,
523
+ config->return_limit_mb,
524
+ (int)config->autocapture_all_children,
525
+ config->sample_rate
526
+ );
527
+
528
+ if (written < 0 || written >= (int)sizeof(buffer)) {
529
+ PyErr_SetString(PyExc_RuntimeError, "Failed to format thread override string");
530
+ return NULL;
531
+ }
532
+
533
+ return PyUnicode_FromString(buffer);
534
+ }
535
+
536
+ static PyObject* py_get(PyObject *self, PyObject *args) {
537
+ const char *file_path;
538
+ const char *func_name;
539
+
540
+ if (!PyArg_ParseTuple(args, "ss", &file_path, &func_name)) {
541
+ return NULL;
542
+ }
543
+
544
+ const sf_funcspan_config_t *config = config_lookup(file_path, func_name);
545
+
546
+ // Return as dictionary
547
+ PyObject *dict = PyDict_New();
548
+ if (!dict) return NULL;
549
+
550
+ PyDict_SetItemString(dict, "include_arguments", PyBool_FromLong(config->include_arguments));
551
+ PyDict_SetItemString(dict, "include_return_value", PyBool_FromLong(config->include_return_value));
552
+ PyDict_SetItemString(dict, "autocapture_all_children", PyBool_FromLong(config->autocapture_all_children));
553
+ PyDict_SetItemString(dict, "arg_limit_mb", PyLong_FromLong(config->arg_limit_mb));
554
+ PyDict_SetItemString(dict, "return_limit_mb", PyLong_FromLong(config->return_limit_mb));
555
+ PyDict_SetItemString(dict, "sample_rate", PyFloat_FromDouble(config->sample_rate));
556
+
557
+ return dict;
558
+ }
559
+
560
+ static PyObject* py_get_raw_pointer(PyObject *self, PyObject *args) {
561
+ const char *file_path;
562
+ const char *func_name;
563
+
564
+ if (!PyArg_ParseTuple(args, "ss", &file_path, &func_name)) {
565
+ return NULL;
566
+ }
567
+
568
+ const sf_funcspan_config_t *config = config_lookup(file_path, func_name);
569
+
570
+ // Return pointer as PyCapsule (for C-level integration)
571
+ return PyCapsule_New((void*)config, "sf_funcspan_config_ptr", NULL);
572
+ }
573
+
574
+ static PyObject* py_shutdown(PyObject *self, PyObject *args) {
575
+ pthread_mutex_lock(&g_config_mutex);
576
+
577
+ table_free(&g_file_configs);
578
+ table_free(&g_func_configs);
579
+
580
+ // Reset default config
581
+ g_default_config.include_arguments = 1;
582
+ g_default_config.include_return_value = 1;
583
+ g_default_config.autocapture_all_children = 1;
584
+ g_default_config.sample_rate = 1.0f;
585
+ g_default_config.arg_limit_mb = 1;
586
+ g_default_config.return_limit_mb = 1;
587
+
588
+ pthread_mutex_unlock(&g_config_mutex);
589
+
590
+ Py_RETURN_NONE;
591
+ }
592
+
593
+ // ---------- Module Definition ----------
594
+ static PyMethodDef ConfigMethods[] = {
595
+ {"init", py_init, METH_VARARGS, "Initialize config system with default config"},
596
+ {"add_file", py_add_file, METH_VARARGS, "Add file-level config"},
597
+ {"add_function", py_add_function, METH_VARARGS, "Add function-level config"},
598
+ {"set_thread_override", py_set_thread_override, METH_VARARGS, "Set thread-local override from header"},
599
+ {"clear_thread_override", py_clear_thread_override, METH_NOARGS, "Clear thread-local override"},
600
+ {"get_thread_override", py_get_thread_override, METH_NOARGS, "Get thread-local override as formatted string"},
601
+ {"get", py_get, METH_VARARGS, "Get config for file:func (returns dict)"},
602
+ {"get_raw_pointer", py_get_raw_pointer, METH_VARARGS, "Get raw config pointer (PyCapsule)"},
603
+ {"shutdown", py_shutdown, METH_NOARGS, "Shutdown and free all tables"},
604
+ {NULL, NULL, 0, NULL}
605
+ };
606
+
607
+ static struct PyModuleDef sffuncspanconfigmodule = {
608
+ PyModuleDef_HEAD_INIT,
609
+ "_sffuncspan_config",
610
+ "Ultra-fast configuration system for function span capture (<5ns lookups)",
611
+ -1,
612
+ ConfigMethods
613
+ };
614
+
615
+ PyMODINIT_FUNC PyInit__sffuncspan_config(void) {
616
+ return PyModule_Create(&sffuncspanconfigmodule);
617
+ }