sf-veritas 0.10.3__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.
- sf_veritas/__init__.py +20 -0
- sf_veritas/_sffastlog.c +889 -0
- sf_veritas/_sffastlog.cpython-313-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnet.c +924 -0
- sf_veritas/_sffastnet.cpython-313-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnetworkrequest.c +730 -0
- sf_veritas/_sffastnetworkrequest.cpython-313-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan.c +2155 -0
- sf_veritas/_sffuncspan.cpython-313-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan_config.c +617 -0
- sf_veritas/_sffuncspan_config.cpython-313-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfheadercheck.c +341 -0
- sf_veritas/_sfheadercheck.cpython-313-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfnetworkhop.c +1451 -0
- sf_veritas/_sfnetworkhop.cpython-313-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfservice.c +1175 -0
- sf_veritas/_sfservice.cpython-313-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfteepreload.c +5167 -0
- sf_veritas/app_config.py +49 -0
- sf_veritas/cli.py +336 -0
- sf_veritas/constants.py +10 -0
- sf_veritas/custom_excepthook.py +304 -0
- sf_veritas/custom_log_handler.py +129 -0
- sf_veritas/custom_output_wrapper.py +144 -0
- sf_veritas/custom_print.py +146 -0
- sf_veritas/django_app.py +5 -0
- sf_veritas/env_vars.py +186 -0
- sf_veritas/exception_handling_middleware.py +18 -0
- sf_veritas/exception_metaclass.py +69 -0
- sf_veritas/fast_frame_info.py +116 -0
- sf_veritas/fast_network_hop.py +293 -0
- sf_veritas/frame_tools.py +112 -0
- sf_veritas/funcspan_config_loader.py +556 -0
- sf_veritas/function_span_profiler.py +1174 -0
- sf_veritas/import_hook.py +62 -0
- sf_veritas/infra_details/__init__.py +3 -0
- sf_veritas/infra_details/get_infra_details.py +24 -0
- sf_veritas/infra_details/kubernetes/__init__.py +3 -0
- sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
- sf_veritas/infra_details/kubernetes/get_details.py +7 -0
- sf_veritas/infra_details/running_on/__init__.py +17 -0
- sf_veritas/infra_details/running_on/kubernetes.py +11 -0
- sf_veritas/interceptors.py +497 -0
- sf_veritas/libsfnettee.so +0 -0
- sf_veritas/local_env_detect.py +118 -0
- sf_veritas/package_metadata.py +6 -0
- sf_veritas/patches/__init__.py +0 -0
- sf_veritas/patches/concurrent_futures.py +19 -0
- sf_veritas/patches/constants.py +1 -0
- sf_veritas/patches/exceptions.py +82 -0
- sf_veritas/patches/multiprocessing.py +32 -0
- sf_veritas/patches/network_libraries/__init__.py +76 -0
- sf_veritas/patches/network_libraries/aiohttp.py +281 -0
- sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
- sf_veritas/patches/network_libraries/http_client.py +419 -0
- sf_veritas/patches/network_libraries/httpcore.py +515 -0
- sf_veritas/patches/network_libraries/httplib2.py +204 -0
- sf_veritas/patches/network_libraries/httpx.py +515 -0
- sf_veritas/patches/network_libraries/niquests.py +211 -0
- sf_veritas/patches/network_libraries/pycurl.py +385 -0
- sf_veritas/patches/network_libraries/requests.py +633 -0
- sf_veritas/patches/network_libraries/tornado.py +341 -0
- sf_veritas/patches/network_libraries/treq.py +270 -0
- sf_veritas/patches/network_libraries/urllib_request.py +468 -0
- sf_veritas/patches/network_libraries/utils.py +398 -0
- sf_veritas/patches/os.py +17 -0
- sf_veritas/patches/threading.py +218 -0
- sf_veritas/patches/web_frameworks/__init__.py +54 -0
- sf_veritas/patches/web_frameworks/aiohttp.py +793 -0
- sf_veritas/patches/web_frameworks/async_websocket_consumer.py +317 -0
- sf_veritas/patches/web_frameworks/blacksheep.py +527 -0
- sf_veritas/patches/web_frameworks/bottle.py +502 -0
- sf_veritas/patches/web_frameworks/cherrypy.py +678 -0
- sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
- sf_veritas/patches/web_frameworks/django.py +944 -0
- sf_veritas/patches/web_frameworks/eve.py +395 -0
- sf_veritas/patches/web_frameworks/falcon.py +926 -0
- sf_veritas/patches/web_frameworks/fastapi.py +724 -0
- sf_veritas/patches/web_frameworks/flask.py +520 -0
- sf_veritas/patches/web_frameworks/klein.py +501 -0
- sf_veritas/patches/web_frameworks/litestar.py +551 -0
- sf_veritas/patches/web_frameworks/pyramid.py +428 -0
- sf_veritas/patches/web_frameworks/quart.py +824 -0
- sf_veritas/patches/web_frameworks/robyn.py +697 -0
- sf_veritas/patches/web_frameworks/sanic.py +857 -0
- sf_veritas/patches/web_frameworks/starlette.py +723 -0
- sf_veritas/patches/web_frameworks/strawberry.py +813 -0
- sf_veritas/patches/web_frameworks/tornado.py +481 -0
- sf_veritas/patches/web_frameworks/utils.py +91 -0
- sf_veritas/print_override.py +13 -0
- sf_veritas/regular_data_transmitter.py +409 -0
- sf_veritas/request_interceptor.py +401 -0
- sf_veritas/request_utils.py +550 -0
- sf_veritas/server_status.py +1 -0
- sf_veritas/shutdown_flag.py +11 -0
- sf_veritas/subprocess_startup.py +3 -0
- sf_veritas/test_cli.py +145 -0
- sf_veritas/thread_local.py +970 -0
- sf_veritas/timeutil.py +114 -0
- sf_veritas/transmit_exception_to_sailfish.py +28 -0
- sf_veritas/transmitter.py +132 -0
- sf_veritas/types.py +47 -0
- sf_veritas/unified_interceptor.py +1580 -0
- sf_veritas/utils.py +39 -0
- sf_veritas-0.10.3.dist-info/METADATA +97 -0
- sf_veritas-0.10.3.dist-info/RECORD +132 -0
- sf_veritas-0.10.3.dist-info/WHEEL +5 -0
- sf_veritas-0.10.3.dist-info/entry_points.txt +2 -0
- sf_veritas-0.10.3.dist-info/top_level.txt +1 -0
- sf_veritas.libs/libbrotlicommon-6ce2a53c.so.1.0.6 +0 -0
- sf_veritas.libs/libbrotlidec-811d1be3.so.1.0.6 +0 -0
- sf_veritas.libs/libcom_err-730ca923.so.2.1 +0 -0
- sf_veritas.libs/libcrypt-52aca757.so.1.1.0 +0 -0
- sf_veritas.libs/libcrypto-bdaed0ea.so.1.1.1k +0 -0
- sf_veritas.libs/libcurl-eaa3cf66.so.4.5.0 +0 -0
- sf_veritas.libs/libgssapi_krb5-323bbd21.so.2.2 +0 -0
- sf_veritas.libs/libidn2-2f4a5893.so.0.3.6 +0 -0
- sf_veritas.libs/libk5crypto-9a74ff38.so.3.1 +0 -0
- sf_veritas.libs/libkeyutils-2777d33d.so.1.6 +0 -0
- sf_veritas.libs/libkrb5-a55300e8.so.3.3 +0 -0
- sf_veritas.libs/libkrb5support-e6594cfc.so.0.1 +0 -0
- sf_veritas.libs/liblber-2-d20824ef.4.so.2.10.9 +0 -0
- sf_veritas.libs/libldap-2-cea2a960.4.so.2.10.9 +0 -0
- sf_veritas.libs/libnghttp2-39367a22.so.14.17.0 +0 -0
- sf_veritas.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
- sf_veritas.libs/libpsl-99becdd3.so.5.3.1 +0 -0
- sf_veritas.libs/libsasl2-7de4d792.so.3.0.0 +0 -0
- sf_veritas.libs/libselinux-d0805dcb.so.1 +0 -0
- sf_veritas.libs/libssh-c11d285b.so.4.8.7 +0 -0
- sf_veritas.libs/libssl-60250281.so.1.1.1k +0 -0
- sf_veritas.libs/libunistring-05abdd40.so.2.1.0 +0 -0
- sf_veritas.libs/libuuid-95b83d40.so.1.3.0 +0 -0
|
Binary file
|
|
@@ -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
|
+
}
|
|
Binary file
|