sf-veritas 0.11.10__cp314-cp314-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.
- sf_veritas/__init__.py +46 -0
- sf_veritas/_auto_preload.py +73 -0
- sf_veritas/_sfconfig.c +162 -0
- sf_veritas/_sfconfig.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfcrashhandler.c +267 -0
- sf_veritas/_sfcrashhandler.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastlog.c +953 -0
- sf_veritas/_sffastlog.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnet.c +994 -0
- sf_veritas/_sffastnet.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnetworkrequest.c +727 -0
- sf_veritas/_sffastnetworkrequest.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan.c +2791 -0
- sf_veritas/_sffuncspan.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan_config.c +730 -0
- sf_veritas/_sffuncspan_config.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfheadercheck.c +341 -0
- sf_veritas/_sfheadercheck.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfnetworkhop.c +1454 -0
- sf_veritas/_sfnetworkhop.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfservice.c +1223 -0
- sf_veritas/_sfservice.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfteepreload.c +6227 -0
- sf_veritas/app_config.py +57 -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 +146 -0
- sf_veritas/custom_output_wrapper.py +153 -0
- sf_veritas/custom_print.py +153 -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 +693 -0
- sf_veritas/function_span_profiler.py +1313 -0
- sf_veritas/get_preload_path.py +34 -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 +543 -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/_patch_tracker.py +74 -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 +99 -0
- sf_veritas/patches/network_libraries/aiohttp.py +294 -0
- sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
- sf_veritas/patches/network_libraries/http_client.py +670 -0
- sf_veritas/patches/network_libraries/httpcore.py +580 -0
- sf_veritas/patches/network_libraries/httplib2.py +315 -0
- sf_veritas/patches/network_libraries/httpx.py +557 -0
- sf_veritas/patches/network_libraries/niquests.py +218 -0
- sf_veritas/patches/network_libraries/pycurl.py +399 -0
- sf_veritas/patches/network_libraries/requests.py +595 -0
- sf_veritas/patches/network_libraries/ssl_socket.py +822 -0
- sf_veritas/patches/network_libraries/tornado.py +360 -0
- sf_veritas/patches/network_libraries/treq.py +270 -0
- sf_veritas/patches/network_libraries/urllib_request.py +483 -0
- sf_veritas/patches/network_libraries/utils.py +598 -0
- sf_veritas/patches/os.py +17 -0
- sf_veritas/patches/threading.py +231 -0
- sf_veritas/patches/web_frameworks/__init__.py +54 -0
- sf_veritas/patches/web_frameworks/aiohttp.py +798 -0
- sf_veritas/patches/web_frameworks/async_websocket_consumer.py +337 -0
- sf_veritas/patches/web_frameworks/blacksheep.py +532 -0
- sf_veritas/patches/web_frameworks/bottle.py +513 -0
- sf_veritas/patches/web_frameworks/cherrypy.py +683 -0
- sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
- sf_veritas/patches/web_frameworks/django.py +963 -0
- sf_veritas/patches/web_frameworks/eve.py +401 -0
- sf_veritas/patches/web_frameworks/falcon.py +931 -0
- sf_veritas/patches/web_frameworks/fastapi.py +738 -0
- sf_veritas/patches/web_frameworks/flask.py +526 -0
- sf_veritas/patches/web_frameworks/klein.py +501 -0
- sf_veritas/patches/web_frameworks/litestar.py +616 -0
- sf_veritas/patches/web_frameworks/pyramid.py +440 -0
- sf_veritas/patches/web_frameworks/quart.py +841 -0
- sf_veritas/patches/web_frameworks/robyn.py +708 -0
- sf_veritas/patches/web_frameworks/sanic.py +874 -0
- sf_veritas/patches/web_frameworks/starlette.py +742 -0
- sf_veritas/patches/web_frameworks/strawberry.py +1446 -0
- sf_veritas/patches/web_frameworks/tornado.py +485 -0
- sf_veritas/patches/web_frameworks/utils.py +170 -0
- sf_veritas/print_override.py +13 -0
- sf_veritas/regular_data_transmitter.py +444 -0
- sf_veritas/request_interceptor.py +401 -0
- sf_veritas/request_utils.py +550 -0
- sf_veritas/segfault_handler.py +116 -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 +1319 -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 +1678 -0
- sf_veritas/utils.py +39 -0
- sf_veritas-0.11.10.dist-info/METADATA +97 -0
- sf_veritas-0.11.10.dist-info/RECORD +141 -0
- sf_veritas-0.11.10.dist-info/WHEEL +5 -0
- sf_veritas-0.11.10.dist-info/entry_points.txt +2 -0
- sf_veritas-0.11.10.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
|
@@ -0,0 +1,730 @@
|
|
|
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
|
+
#include "sf_tls.h"
|
|
10
|
+
|
|
11
|
+
// ---------- Configuration Structure ----------
|
|
12
|
+
// Priority levels (lower number = higher priority)
|
|
13
|
+
#define PRIORITY_HTTP_HEADER 1 // Highest priority (per-request override)
|
|
14
|
+
#define PRIORITY_DECORATOR 2 // @capture_function_spans decorator
|
|
15
|
+
#define PRIORITY_SAILFISH_FUNCTION 3 // Function config in .sailfish
|
|
16
|
+
#define PRIORITY_PRAGMA 4 // File pragma # sailfish-funcspan:
|
|
17
|
+
#define PRIORITY_SAILFISH_FILE 5 // File glob patterns in .sailfish
|
|
18
|
+
#define PRIORITY_DIRECTORY 6 // Directory .sailfish (inherited)
|
|
19
|
+
#define PRIORITY_ENV_VAR 8 // Environment variables
|
|
20
|
+
#define PRIORITY_DEFAULT 9 // Hard-coded defaults (lowest priority)
|
|
21
|
+
|
|
22
|
+
// 32 bytes, cache-line friendly
|
|
23
|
+
typedef struct {
|
|
24
|
+
uint8_t include_arguments;
|
|
25
|
+
uint8_t include_return_value;
|
|
26
|
+
uint8_t autocapture_all_children;
|
|
27
|
+
uint8_t priority; // Priority level (lower = higher priority)
|
|
28
|
+
float sample_rate;
|
|
29
|
+
uint32_t arg_limit_mb;
|
|
30
|
+
uint32_t return_limit_mb;
|
|
31
|
+
uint64_t hash; // Pre-computed for validation
|
|
32
|
+
} sf_funcspan_config_t;
|
|
33
|
+
|
|
34
|
+
// Thread-local storage for header overrides (highest priority)
|
|
35
|
+
typedef struct {
|
|
36
|
+
sf_funcspan_config_t config;
|
|
37
|
+
uint8_t has_override;
|
|
38
|
+
uint8_t _padding[7]; // Align to 32 bytes
|
|
39
|
+
} sf_thread_config_t;
|
|
40
|
+
|
|
41
|
+
static _Thread_local sf_thread_config_t g_thread_config = {0};
|
|
42
|
+
|
|
43
|
+
// Python function reference for reading ContextVar (async-safe fallback)
|
|
44
|
+
static PyObject *g_get_funcspan_override_func = NULL;
|
|
45
|
+
|
|
46
|
+
// ---------- Hash Table for O(1) Lookups ----------
|
|
47
|
+
// Using power-of-2 capacity for fast modulo (bitwise AND)
|
|
48
|
+
typedef struct {
|
|
49
|
+
sf_funcspan_config_t* entries; // Dense array of configs
|
|
50
|
+
uint64_t* keys; // File path hashes
|
|
51
|
+
uint32_t* indices; // Hash table -> entries index
|
|
52
|
+
uint32_t capacity; // Power of 2
|
|
53
|
+
uint32_t size; // Number of entries
|
|
54
|
+
} sf_config_table_t;
|
|
55
|
+
|
|
56
|
+
// Global lookup tables (built at startup)
|
|
57
|
+
static sf_config_table_t g_file_configs = {0};
|
|
58
|
+
static sf_config_table_t g_func_configs = {0};
|
|
59
|
+
static sf_funcspan_config_t g_default_config = {
|
|
60
|
+
.include_arguments = 1,
|
|
61
|
+
.include_return_value = 1,
|
|
62
|
+
.autocapture_all_children = 1,
|
|
63
|
+
.priority = PRIORITY_DEFAULT,
|
|
64
|
+
.sample_rate = 1.0f,
|
|
65
|
+
.arg_limit_mb = 1,
|
|
66
|
+
.return_limit_mb = 1,
|
|
67
|
+
.hash = 0
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Lock for table modifications (only used at startup)
|
|
71
|
+
static pthread_mutex_t g_config_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
72
|
+
|
|
73
|
+
// ---------- Fast Hash Function (xxHash-like) ----------
|
|
74
|
+
// Simple, fast hash for string keys (~2ns)
|
|
75
|
+
static inline uint64_t fast_hash(const char *str, size_t len) {
|
|
76
|
+
const uint64_t PRIME64_1 = 11400714785074694791ULL;
|
|
77
|
+
const uint64_t PRIME64_2 = 14029467366897019727ULL;
|
|
78
|
+
const uint64_t PRIME64_3 = 1609587929392839161ULL;
|
|
79
|
+
const uint64_t PRIME64_4 = 9650029242287828579ULL;
|
|
80
|
+
const uint64_t PRIME64_5 = 2870177450012600261ULL;
|
|
81
|
+
|
|
82
|
+
uint64_t h = PRIME64_5 + len;
|
|
83
|
+
const uint8_t *data = (const uint8_t*)str;
|
|
84
|
+
|
|
85
|
+
// Process 8 bytes at a time
|
|
86
|
+
while (len >= 8) {
|
|
87
|
+
h ^= *(const uint64_t*)data * PRIME64_2;
|
|
88
|
+
h = (h << 31) | (h >> 33);
|
|
89
|
+
h *= PRIME64_1;
|
|
90
|
+
data += 8;
|
|
91
|
+
len -= 8;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Process remaining bytes
|
|
95
|
+
while (len--) {
|
|
96
|
+
h ^= (*data++) * PRIME64_5;
|
|
97
|
+
h = (h << 11) | (h >> 53);
|
|
98
|
+
h *= PRIME64_1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Final mix
|
|
102
|
+
h ^= h >> 33;
|
|
103
|
+
h *= PRIME64_2;
|
|
104
|
+
h ^= h >> 29;
|
|
105
|
+
h *= PRIME64_3;
|
|
106
|
+
h ^= h >> 32;
|
|
107
|
+
|
|
108
|
+
return h;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---------- Config Table Operations ----------
|
|
112
|
+
static int table_init(sf_config_table_t *table, uint32_t initial_capacity) {
|
|
113
|
+
// Round up to next power of 2
|
|
114
|
+
uint32_t capacity = 16;
|
|
115
|
+
while (capacity < initial_capacity) capacity <<= 1;
|
|
116
|
+
|
|
117
|
+
table->entries = (sf_funcspan_config_t*)calloc(capacity, sizeof(sf_funcspan_config_t));
|
|
118
|
+
table->keys = (uint64_t*)calloc(capacity, sizeof(uint64_t));
|
|
119
|
+
table->indices = (uint32_t*)malloc(capacity * sizeof(uint32_t));
|
|
120
|
+
|
|
121
|
+
if (!table->entries || !table->keys || !table->indices) {
|
|
122
|
+
free(table->entries);
|
|
123
|
+
free(table->keys);
|
|
124
|
+
free(table->indices);
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Initialize indices to UINT32_MAX (empty marker)
|
|
129
|
+
for (uint32_t i = 0; i < capacity; i++) {
|
|
130
|
+
table->indices[i] = UINT32_MAX;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
table->capacity = capacity;
|
|
134
|
+
table->size = 0;
|
|
135
|
+
return 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static void table_free(sf_config_table_t *table) {
|
|
139
|
+
if (table->entries) {
|
|
140
|
+
free(table->entries);
|
|
141
|
+
table->entries = NULL;
|
|
142
|
+
}
|
|
143
|
+
if (table->keys) {
|
|
144
|
+
free(table->keys);
|
|
145
|
+
table->keys = NULL;
|
|
146
|
+
}
|
|
147
|
+
if (table->indices) {
|
|
148
|
+
free(table->indices);
|
|
149
|
+
table->indices = NULL;
|
|
150
|
+
}
|
|
151
|
+
table->capacity = 0;
|
|
152
|
+
table->size = 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Resize table when load factor > 0.75
|
|
156
|
+
static int table_resize(sf_config_table_t *table) {
|
|
157
|
+
uint32_t new_capacity = table->capacity * 2;
|
|
158
|
+
uint32_t *new_indices = (uint32_t*)malloc(new_capacity * sizeof(uint32_t));
|
|
159
|
+
|
|
160
|
+
if (!new_indices) return 0;
|
|
161
|
+
|
|
162
|
+
// Initialize new indices
|
|
163
|
+
for (uint32_t i = 0; i < new_capacity; i++) {
|
|
164
|
+
new_indices[i] = UINT32_MAX;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Rehash all entries
|
|
168
|
+
for (uint32_t i = 0; i < table->size; i++) {
|
|
169
|
+
uint64_t hash = table->keys[i];
|
|
170
|
+
uint32_t slot = hash & (new_capacity - 1);
|
|
171
|
+
|
|
172
|
+
// Linear probing
|
|
173
|
+
while (new_indices[slot] != UINT32_MAX) {
|
|
174
|
+
slot = (slot + 1) & (new_capacity - 1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
new_indices[slot] = i;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
free(table->indices);
|
|
181
|
+
table->indices = new_indices;
|
|
182
|
+
table->capacity = new_capacity;
|
|
183
|
+
return 1;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
static int table_insert(sf_config_table_t *table, uint64_t hash, const sf_funcspan_config_t *config) {
|
|
187
|
+
pthread_mutex_lock(&g_config_mutex);
|
|
188
|
+
|
|
189
|
+
// Check if we need to resize
|
|
190
|
+
if (table->size >= (table->capacity * 3) / 4) {
|
|
191
|
+
if (!table_resize(table)) {
|
|
192
|
+
pthread_mutex_unlock(&g_config_mutex);
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Find slot using linear probing
|
|
198
|
+
uint32_t slot = hash & (table->capacity - 1);
|
|
199
|
+
while (table->indices[slot] != UINT32_MAX) {
|
|
200
|
+
// Check if key already exists (update case)
|
|
201
|
+
uint32_t idx = table->indices[slot];
|
|
202
|
+
if (table->keys[idx] == hash) {
|
|
203
|
+
// PRIORITY CHECK: Only update if new config has HIGHER priority (lower number)
|
|
204
|
+
// This ensures decorator (priority=2) wins over .sailfish function (priority=3)
|
|
205
|
+
// and pragma (priority=4) wins over .sailfish file (priority=5)
|
|
206
|
+
if (config->priority < table->entries[idx].priority) {
|
|
207
|
+
// New config has higher priority - update the entry
|
|
208
|
+
table->entries[idx] = *config;
|
|
209
|
+
table->entries[idx].hash = hash;
|
|
210
|
+
}
|
|
211
|
+
// Else: existing config has higher/equal priority - keep it
|
|
212
|
+
pthread_mutex_unlock(&g_config_mutex);
|
|
213
|
+
return 1;
|
|
214
|
+
}
|
|
215
|
+
slot = (slot + 1) & (table->capacity - 1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Insert new entry
|
|
219
|
+
uint32_t idx = table->size++;
|
|
220
|
+
table->keys[idx] = hash;
|
|
221
|
+
table->entries[idx] = *config;
|
|
222
|
+
table->entries[idx].hash = hash;
|
|
223
|
+
table->indices[slot] = idx;
|
|
224
|
+
|
|
225
|
+
pthread_mutex_unlock(&g_config_mutex);
|
|
226
|
+
return 1;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Forward declaration for parse_header_override (defined below)
|
|
230
|
+
static int parse_header_override(const char *header, sf_funcspan_config_t *config);
|
|
231
|
+
|
|
232
|
+
// ---------- Ultra-Fast Lookup (<5ns, with async-safe fallback) ----------
|
|
233
|
+
static inline const sf_funcspan_config_t* config_lookup(const char *file_path, const char *func_name) {
|
|
234
|
+
// 1. FAST PATH: Check C thread-local override first (highest priority, ~1-2ns)
|
|
235
|
+
if (g_thread_config.has_override) {
|
|
236
|
+
return &g_thread_config.config;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 2. SLOW PATH: Check Python ContextVar as fallback (async-safe, ~100-200ns)
|
|
240
|
+
// This only happens on first call after async thread switch. We cache the result
|
|
241
|
+
// in g_thread_config so subsequent calls use the fast path above.
|
|
242
|
+
if (g_get_funcspan_override_func) {
|
|
243
|
+
PyObject *result = PyObject_CallObject(g_get_funcspan_override_func, NULL);
|
|
244
|
+
if (result && result != Py_None) {
|
|
245
|
+
// Got a string from ContextVar
|
|
246
|
+
if (PyUnicode_Check(result)) {
|
|
247
|
+
const char *override_str = PyUnicode_AsUTF8(result);
|
|
248
|
+
if (override_str) {
|
|
249
|
+
sf_funcspan_config_t temp_config;
|
|
250
|
+
if (parse_header_override(override_str, &temp_config)) {
|
|
251
|
+
// SUCCESS: Cache it in thread-local for subsequent calls (makes them fast)
|
|
252
|
+
g_thread_config.config = temp_config;
|
|
253
|
+
g_thread_config.has_override = 1;
|
|
254
|
+
Py_DECREF(result);
|
|
255
|
+
return &g_thread_config.config;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
Py_XDECREF(result);
|
|
261
|
+
if (PyErr_Occurred()) {
|
|
262
|
+
PyErr_Clear(); // Don't let ContextVar errors break profiling
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// 3. No override found - continue with file/function config lookups
|
|
267
|
+
// Build combined key for function-level lookup
|
|
268
|
+
// Format: "file_path:func_name"
|
|
269
|
+
char key_buf[512];
|
|
270
|
+
size_t file_len = strlen(file_path);
|
|
271
|
+
size_t func_len = strlen(func_name);
|
|
272
|
+
|
|
273
|
+
if (file_len + func_len + 2 < sizeof(key_buf)) {
|
|
274
|
+
memcpy(key_buf, file_path, file_len);
|
|
275
|
+
key_buf[file_len] = ':';
|
|
276
|
+
memcpy(key_buf + file_len + 1, func_name, func_len);
|
|
277
|
+
key_buf[file_len + 1 + func_len] = '\0';
|
|
278
|
+
|
|
279
|
+
// 4. Compute hash (fast, ~2ns)
|
|
280
|
+
uint64_t hash = fast_hash(key_buf, file_len + 1 + func_len);
|
|
281
|
+
|
|
282
|
+
// 5. Prefetch cache line (parallel with hash)
|
|
283
|
+
__builtin_prefetch(&g_func_configs.indices[hash & (g_func_configs.capacity - 1)]);
|
|
284
|
+
|
|
285
|
+
// 6. Lookup in function table (~1ns)
|
|
286
|
+
uint32_t slot = hash & (g_func_configs.capacity - 1);
|
|
287
|
+
uint32_t idx = g_func_configs.indices[slot];
|
|
288
|
+
|
|
289
|
+
if (idx != UINT32_MAX && g_func_configs.keys[idx] == hash) {
|
|
290
|
+
return &g_func_configs.entries[idx];
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 7. Fallback to file-level lookup
|
|
295
|
+
uint64_t file_hash = fast_hash(file_path, file_len);
|
|
296
|
+
__builtin_prefetch(&g_file_configs.indices[file_hash & (g_file_configs.capacity - 1)]);
|
|
297
|
+
|
|
298
|
+
uint32_t slot = file_hash & (g_file_configs.capacity - 1);
|
|
299
|
+
uint32_t idx = g_file_configs.indices[slot];
|
|
300
|
+
|
|
301
|
+
if (idx != UINT32_MAX && g_file_configs.keys[idx] == file_hash) {
|
|
302
|
+
return &g_file_configs.entries[idx];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// 8. Return default config
|
|
306
|
+
return &g_default_config;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ---------- Header Parsing ----------
|
|
310
|
+
// Parse header format: "include_arguments-include_return_value-arg_limit_mb-return_limit_mb-autocapture_all_children-sample_rate"
|
|
311
|
+
// Example: "1-1-1-1-1-1.0" or "0-0-2-2-1-0.5"
|
|
312
|
+
static int parse_header_override(const char *header, sf_funcspan_config_t *config) {
|
|
313
|
+
if (!header || !config) return 0;
|
|
314
|
+
|
|
315
|
+
// Parse using sscanf (fast enough for header parsing, ~100ns)
|
|
316
|
+
int values[5];
|
|
317
|
+
float sample_rate;
|
|
318
|
+
|
|
319
|
+
int parsed = sscanf(header, "%d-%d-%d-%d-%d-%f",
|
|
320
|
+
&values[0], &values[1], &values[2],
|
|
321
|
+
&values[3], &values[4], &sample_rate);
|
|
322
|
+
|
|
323
|
+
if (parsed != 6) return 0; // Parse error
|
|
324
|
+
|
|
325
|
+
// Validate values
|
|
326
|
+
if (sample_rate < 0.0f || sample_rate > 1.0f) return 0;
|
|
327
|
+
for (int i = 0; i < 5; i++) {
|
|
328
|
+
if (values[i] < 0) return 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Set config
|
|
332
|
+
config->include_arguments = (uint8_t)values[0];
|
|
333
|
+
config->include_return_value = (uint8_t)values[1];
|
|
334
|
+
config->arg_limit_mb = (uint32_t)values[2];
|
|
335
|
+
config->return_limit_mb = (uint32_t)values[3];
|
|
336
|
+
config->autocapture_all_children = (uint8_t)values[4];
|
|
337
|
+
config->sample_rate = sample_rate;
|
|
338
|
+
config->priority = PRIORITY_HTTP_HEADER; // HTTP headers always have highest priority
|
|
339
|
+
config->hash = 0;
|
|
340
|
+
|
|
341
|
+
return 1; // Success
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ---------- Python API ----------
|
|
345
|
+
|
|
346
|
+
static PyObject* py_init(PyObject *self, PyObject *args) {
|
|
347
|
+
PyObject *config_dict;
|
|
348
|
+
|
|
349
|
+
if (!PyArg_ParseTuple(args, "O", &config_dict)) {
|
|
350
|
+
return NULL;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (!PyDict_Check(config_dict)) {
|
|
354
|
+
PyErr_SetString(PyExc_TypeError, "config must be a dictionary");
|
|
355
|
+
return NULL;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Initialize default config from dict
|
|
359
|
+
PyObject *val;
|
|
360
|
+
|
|
361
|
+
val = PyDict_GetItemString(config_dict, "include_arguments");
|
|
362
|
+
if (val && PyBool_Check(val)) {
|
|
363
|
+
g_default_config.include_arguments = (val == Py_True) ? 1 : 0;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
val = PyDict_GetItemString(config_dict, "include_return_value");
|
|
367
|
+
if (val && PyBool_Check(val)) {
|
|
368
|
+
g_default_config.include_return_value = (val == Py_True) ? 1 : 0;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
val = PyDict_GetItemString(config_dict, "autocapture_all_children");
|
|
372
|
+
if (val && PyBool_Check(val)) {
|
|
373
|
+
g_default_config.autocapture_all_children = (val == Py_True) ? 1 : 0;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
val = PyDict_GetItemString(config_dict, "arg_limit_mb");
|
|
377
|
+
if (val && PyLong_Check(val)) {
|
|
378
|
+
g_default_config.arg_limit_mb = (uint32_t)PyLong_AsLong(val);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
val = PyDict_GetItemString(config_dict, "return_limit_mb");
|
|
382
|
+
if (val && PyLong_Check(val)) {
|
|
383
|
+
g_default_config.return_limit_mb = (uint32_t)PyLong_AsLong(val);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
val = PyDict_GetItemString(config_dict, "sample_rate");
|
|
387
|
+
if (val && PyFloat_Check(val)) {
|
|
388
|
+
g_default_config.sample_rate = (float)PyFloat_AsDouble(val);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Initialize hash tables
|
|
392
|
+
if (g_file_configs.capacity == 0) {
|
|
393
|
+
if (!table_init(&g_file_configs, 1024)) {
|
|
394
|
+
PyErr_SetString(PyExc_RuntimeError, "Failed to initialize file config table");
|
|
395
|
+
return NULL;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (g_func_configs.capacity == 0) {
|
|
400
|
+
if (!table_init(&g_func_configs, 4096)) {
|
|
401
|
+
PyErr_SetString(PyExc_RuntimeError, "Failed to initialize function config table");
|
|
402
|
+
return NULL;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
Py_RETURN_NONE;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
static PyObject* py_add_file(PyObject *self, PyObject *args, PyObject *kwargs) {
|
|
410
|
+
const char *file_path;
|
|
411
|
+
PyObject *config_dict;
|
|
412
|
+
int priority = PRIORITY_SAILFISH_FILE; // Default priority
|
|
413
|
+
|
|
414
|
+
static char *kwlist[] = {"file_path", "config", "priority", NULL};
|
|
415
|
+
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|i", kwlist, &file_path, &config_dict, &priority)) {
|
|
416
|
+
return NULL;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (!PyDict_Check(config_dict)) {
|
|
420
|
+
PyErr_SetString(PyExc_TypeError, "config must be a dictionary");
|
|
421
|
+
return NULL;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Build config from dict
|
|
425
|
+
sf_funcspan_config_t config = g_default_config; // Start with defaults
|
|
426
|
+
config.priority = (uint8_t)priority; // Set priority from parameter
|
|
427
|
+
PyObject *val;
|
|
428
|
+
|
|
429
|
+
val = PyDict_GetItemString(config_dict, "include_arguments");
|
|
430
|
+
if (val && PyBool_Check(val)) {
|
|
431
|
+
config.include_arguments = (val == Py_True) ? 1 : 0;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
val = PyDict_GetItemString(config_dict, "include_return_value");
|
|
435
|
+
if (val && PyBool_Check(val)) {
|
|
436
|
+
config.include_return_value = (val == Py_True) ? 1 : 0;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
val = PyDict_GetItemString(config_dict, "autocapture_all_children");
|
|
440
|
+
if (val && PyBool_Check(val)) {
|
|
441
|
+
config.autocapture_all_children = (val == Py_True) ? 1 : 0;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
val = PyDict_GetItemString(config_dict, "arg_limit_mb");
|
|
445
|
+
if (val && PyLong_Check(val)) {
|
|
446
|
+
config.arg_limit_mb = (uint32_t)PyLong_AsLong(val);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
val = PyDict_GetItemString(config_dict, "return_limit_mb");
|
|
450
|
+
if (val && PyLong_Check(val)) {
|
|
451
|
+
config.return_limit_mb = (uint32_t)PyLong_AsLong(val);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
val = PyDict_GetItemString(config_dict, "sample_rate");
|
|
455
|
+
if (val && PyFloat_Check(val)) {
|
|
456
|
+
config.sample_rate = (float)PyFloat_AsDouble(val);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Compute hash and insert
|
|
460
|
+
uint64_t hash = fast_hash(file_path, strlen(file_path));
|
|
461
|
+
|
|
462
|
+
if (!table_insert(&g_file_configs, hash, &config)) {
|
|
463
|
+
PyErr_SetString(PyExc_RuntimeError, "Failed to insert file config");
|
|
464
|
+
return NULL;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
Py_RETURN_NONE;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
static PyObject* py_add_function(PyObject *self, PyObject *args, PyObject *kwargs) {
|
|
471
|
+
const char *file_path;
|
|
472
|
+
const char *func_name;
|
|
473
|
+
PyObject *config_dict;
|
|
474
|
+
int priority = PRIORITY_SAILFISH_FUNCTION; // Default priority
|
|
475
|
+
|
|
476
|
+
static char *kwlist[] = {"file_path", "func_name", "config", "priority", NULL};
|
|
477
|
+
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ssO|i", kwlist, &file_path, &func_name, &config_dict, &priority)) {
|
|
478
|
+
return NULL;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (!PyDict_Check(config_dict)) {
|
|
482
|
+
PyErr_SetString(PyExc_TypeError, "config must be a dictionary");
|
|
483
|
+
return NULL;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Build config from dict (same as add_file)
|
|
487
|
+
sf_funcspan_config_t config = g_default_config;
|
|
488
|
+
config.priority = (uint8_t)priority; // Set priority from parameter
|
|
489
|
+
PyObject *val;
|
|
490
|
+
|
|
491
|
+
val = PyDict_GetItemString(config_dict, "include_arguments");
|
|
492
|
+
if (val && PyBool_Check(val)) {
|
|
493
|
+
config.include_arguments = (val == Py_True) ? 1 : 0;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
val = PyDict_GetItemString(config_dict, "include_return_value");
|
|
497
|
+
if (val && PyBool_Check(val)) {
|
|
498
|
+
config.include_return_value = (val == Py_True) ? 1 : 0;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
val = PyDict_GetItemString(config_dict, "autocapture_all_children");
|
|
502
|
+
if (val && PyBool_Check(val)) {
|
|
503
|
+
config.autocapture_all_children = (val == Py_True) ? 1 : 0;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
val = PyDict_GetItemString(config_dict, "arg_limit_mb");
|
|
507
|
+
if (val && PyLong_Check(val)) {
|
|
508
|
+
config.arg_limit_mb = (uint32_t)PyLong_AsLong(val);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
val = PyDict_GetItemString(config_dict, "return_limit_mb");
|
|
512
|
+
if (val && PyLong_Check(val)) {
|
|
513
|
+
config.return_limit_mb = (uint32_t)PyLong_AsLong(val);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
val = PyDict_GetItemString(config_dict, "sample_rate");
|
|
517
|
+
if (val && PyFloat_Check(val)) {
|
|
518
|
+
config.sample_rate = (float)PyFloat_AsDouble(val);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Build combined key: "file_path:func_name"
|
|
522
|
+
size_t file_len = strlen(file_path);
|
|
523
|
+
size_t func_len = strlen(func_name);
|
|
524
|
+
char *key = (char*)malloc(file_len + func_len + 2);
|
|
525
|
+
|
|
526
|
+
if (!key) {
|
|
527
|
+
PyErr_SetString(PyExc_MemoryError, "Failed to allocate key");
|
|
528
|
+
return NULL;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
memcpy(key, file_path, file_len);
|
|
532
|
+
key[file_len] = ':';
|
|
533
|
+
memcpy(key + file_len + 1, func_name, func_len);
|
|
534
|
+
key[file_len + 1 + func_len] = '\0';
|
|
535
|
+
|
|
536
|
+
uint64_t hash = fast_hash(key, file_len + 1 + func_len);
|
|
537
|
+
free(key);
|
|
538
|
+
|
|
539
|
+
if (!table_insert(&g_func_configs, hash, &config)) {
|
|
540
|
+
PyErr_SetString(PyExc_RuntimeError, "Failed to insert function config");
|
|
541
|
+
return NULL;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
Py_RETURN_NONE;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
static PyObject* py_set_thread_override(PyObject *self, PyObject *args) {
|
|
548
|
+
const char *header;
|
|
549
|
+
|
|
550
|
+
if (!PyArg_ParseTuple(args, "s", &header)) {
|
|
551
|
+
return NULL;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
sf_funcspan_config_t config;
|
|
555
|
+
if (!parse_header_override(header, &config)) {
|
|
556
|
+
PyErr_SetString(PyExc_ValueError, "Invalid header format. Expected: 'include_arguments-include_return_value-arg_limit_mb-return_limit_mb-autocapture_all_children-sample_rate'");
|
|
557
|
+
return NULL;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
g_thread_config.config = config;
|
|
561
|
+
g_thread_config.has_override = 1;
|
|
562
|
+
|
|
563
|
+
Py_RETURN_NONE;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
static PyObject* py_clear_thread_override(PyObject *self, PyObject *args) {
|
|
567
|
+
g_thread_config.has_override = 0;
|
|
568
|
+
memset(&g_thread_config.config, 0, sizeof(sf_funcspan_config_t));
|
|
569
|
+
Py_RETURN_NONE;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
static PyObject* py_has_thread_override(PyObject *self, PyObject *args) {
|
|
573
|
+
// Return True if there's an active HTTP header override for this thread
|
|
574
|
+
if (g_thread_config.has_override) {
|
|
575
|
+
Py_RETURN_TRUE;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Also check ContextVar for async contexts
|
|
579
|
+
if (g_get_funcspan_override_func) {
|
|
580
|
+
PyObject *result = PyObject_CallObject(g_get_funcspan_override_func, NULL);
|
|
581
|
+
if (result && result != Py_None) {
|
|
582
|
+
Py_DECREF(result);
|
|
583
|
+
Py_RETURN_TRUE;
|
|
584
|
+
}
|
|
585
|
+
Py_XDECREF(result);
|
|
586
|
+
if (PyErr_Occurred()) {
|
|
587
|
+
PyErr_Clear();
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
Py_RETURN_FALSE;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
static PyObject* py_get_thread_override(PyObject *self, PyObject *args) {
|
|
595
|
+
// Return current thread-local override as formatted string, or None if not set
|
|
596
|
+
if (!g_thread_config.has_override) {
|
|
597
|
+
Py_RETURN_NONE;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const sf_funcspan_config_t *config = &g_thread_config.config;
|
|
601
|
+
|
|
602
|
+
// Format: "include_arguments-include_return_value-arg_limit_mb-return_limit_mb-autocapture_all_children-sample_rate"
|
|
603
|
+
// Example: "1-1-1-1-1-1.0" or "0-0-2-2-1-0.5"
|
|
604
|
+
char buffer[128];
|
|
605
|
+
int written = snprintf(
|
|
606
|
+
buffer,
|
|
607
|
+
sizeof(buffer),
|
|
608
|
+
"%d-%d-%u-%u-%d-%.2f",
|
|
609
|
+
(int)config->include_arguments,
|
|
610
|
+
(int)config->include_return_value,
|
|
611
|
+
config->arg_limit_mb,
|
|
612
|
+
config->return_limit_mb,
|
|
613
|
+
(int)config->autocapture_all_children,
|
|
614
|
+
config->sample_rate
|
|
615
|
+
);
|
|
616
|
+
|
|
617
|
+
if (written < 0 || written >= (int)sizeof(buffer)) {
|
|
618
|
+
PyErr_SetString(PyExc_RuntimeError, "Failed to format thread override string");
|
|
619
|
+
return NULL;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
return PyUnicode_FromString(buffer);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
static PyObject* py_get(PyObject *self, PyObject *args) {
|
|
626
|
+
const char *file_path;
|
|
627
|
+
const char *func_name;
|
|
628
|
+
|
|
629
|
+
if (!PyArg_ParseTuple(args, "ss", &file_path, &func_name)) {
|
|
630
|
+
return NULL;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const sf_funcspan_config_t *config = config_lookup(file_path, func_name);
|
|
634
|
+
|
|
635
|
+
// Return as dictionary
|
|
636
|
+
PyObject *dict = PyDict_New();
|
|
637
|
+
if (!dict) return NULL;
|
|
638
|
+
|
|
639
|
+
PyDict_SetItemString(dict, "include_arguments", PyBool_FromLong(config->include_arguments));
|
|
640
|
+
PyDict_SetItemString(dict, "include_return_value", PyBool_FromLong(config->include_return_value));
|
|
641
|
+
PyDict_SetItemString(dict, "autocapture_all_children", PyBool_FromLong(config->autocapture_all_children));
|
|
642
|
+
PyDict_SetItemString(dict, "arg_limit_mb", PyLong_FromLong(config->arg_limit_mb));
|
|
643
|
+
PyDict_SetItemString(dict, "return_limit_mb", PyLong_FromLong(config->return_limit_mb));
|
|
644
|
+
PyDict_SetItemString(dict, "sample_rate", PyFloat_FromDouble(config->sample_rate));
|
|
645
|
+
|
|
646
|
+
return dict;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
static PyObject* py_get_raw_pointer(PyObject *self, PyObject *args) {
|
|
650
|
+
const char *file_path;
|
|
651
|
+
const char *func_name;
|
|
652
|
+
|
|
653
|
+
if (!PyArg_ParseTuple(args, "ss", &file_path, &func_name)) {
|
|
654
|
+
return NULL;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const sf_funcspan_config_t *config = config_lookup(file_path, func_name);
|
|
658
|
+
|
|
659
|
+
// Return pointer as PyCapsule (for C-level integration)
|
|
660
|
+
return PyCapsule_New((void*)config, "sf_funcspan_config_ptr", NULL);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
static PyObject* py_shutdown(PyObject *self, PyObject *args) {
|
|
664
|
+
pthread_mutex_lock(&g_config_mutex);
|
|
665
|
+
|
|
666
|
+
table_free(&g_file_configs);
|
|
667
|
+
table_free(&g_func_configs);
|
|
668
|
+
|
|
669
|
+
// Reset default config
|
|
670
|
+
g_default_config.include_arguments = 1;
|
|
671
|
+
g_default_config.include_return_value = 1;
|
|
672
|
+
g_default_config.autocapture_all_children = 1;
|
|
673
|
+
g_default_config.sample_rate = 1.0f;
|
|
674
|
+
g_default_config.arg_limit_mb = 1;
|
|
675
|
+
g_default_config.return_limit_mb = 1;
|
|
676
|
+
|
|
677
|
+
pthread_mutex_unlock(&g_config_mutex);
|
|
678
|
+
|
|
679
|
+
Py_RETURN_NONE;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// ---------- Module Definition ----------
|
|
683
|
+
static PyMethodDef ConfigMethods[] = {
|
|
684
|
+
{"init", py_init, METH_VARARGS, "Initialize config system with default config"},
|
|
685
|
+
{"add_file", (PyCFunction)py_add_file, METH_VARARGS | METH_KEYWORDS, "Add file-level config (with optional priority parameter)"},
|
|
686
|
+
{"add_function", (PyCFunction)py_add_function, METH_VARARGS | METH_KEYWORDS, "Add function-level config (with optional priority parameter)"},
|
|
687
|
+
{"set_thread_override", py_set_thread_override, METH_VARARGS, "Set thread-local override from header"},
|
|
688
|
+
{"clear_thread_override", py_clear_thread_override, METH_NOARGS, "Clear thread-local override"},
|
|
689
|
+
{"has_thread_override", py_has_thread_override, METH_NOARGS, "Check if there's an active HTTP header override"},
|
|
690
|
+
{"get_thread_override", py_get_thread_override, METH_NOARGS, "Get thread-local override as formatted string"},
|
|
691
|
+
{"get", py_get, METH_VARARGS, "Get config for file:func (returns dict)"},
|
|
692
|
+
{"get_raw_pointer", py_get_raw_pointer, METH_VARARGS, "Get raw config pointer (PyCapsule)"},
|
|
693
|
+
{"shutdown", py_shutdown, METH_NOARGS, "Shutdown and free all tables"},
|
|
694
|
+
{NULL, NULL, 0, NULL}
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
static struct PyModuleDef sffuncspanconfigmodule = {
|
|
698
|
+
PyModuleDef_HEAD_INIT,
|
|
699
|
+
"_sffuncspan_config",
|
|
700
|
+
"Ultra-fast configuration system for function span capture (<5ns lookups)",
|
|
701
|
+
-1,
|
|
702
|
+
ConfigMethods
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
PyMODINIT_FUNC PyInit__sffuncspan_config(void) {
|
|
706
|
+
PyObject *module = PyModule_Create(&sffuncspanconfigmodule);
|
|
707
|
+
if (!module) {
|
|
708
|
+
return NULL;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Import the bridge function for reading ContextVar (async-safe fallback)
|
|
712
|
+
// This allows config_lookup() to check ContextVar when thread-local is empty
|
|
713
|
+
PyObject *thread_local_module = PyImport_ImportModule("sf_veritas.thread_local");
|
|
714
|
+
if (thread_local_module) {
|
|
715
|
+
g_get_funcspan_override_func = PyObject_GetAttrString(thread_local_module, "_get_funcspan_override_for_c");
|
|
716
|
+
if (!g_get_funcspan_override_func || !PyCallable_Check(g_get_funcspan_override_func)) {
|
|
717
|
+
// If function not found or not callable, clear it
|
|
718
|
+
Py_XDECREF(g_get_funcspan_override_func);
|
|
719
|
+
g_get_funcspan_override_func = NULL;
|
|
720
|
+
// Not a fatal error - continue without async fallback
|
|
721
|
+
PyErr_Clear();
|
|
722
|
+
}
|
|
723
|
+
Py_DECREF(thread_local_module);
|
|
724
|
+
} else {
|
|
725
|
+
// Module import failed - not fatal, continue without async fallback
|
|
726
|
+
PyErr_Clear();
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
return module;
|
|
730
|
+
}
|