sf-veritas 0.10.3__cp39-cp39-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 (132) hide show
  1. sf_veritas/__init__.py +20 -0
  2. sf_veritas/_sffastlog.c +889 -0
  3. sf_veritas/_sffastlog.cpython-39-x86_64-linux-gnu.so +0 -0
  4. sf_veritas/_sffastnet.c +924 -0
  5. sf_veritas/_sffastnet.cpython-39-x86_64-linux-gnu.so +0 -0
  6. sf_veritas/_sffastnetworkrequest.c +730 -0
  7. sf_veritas/_sffastnetworkrequest.cpython-39-x86_64-linux-gnu.so +0 -0
  8. sf_veritas/_sffuncspan.c +2155 -0
  9. sf_veritas/_sffuncspan.cpython-39-x86_64-linux-gnu.so +0 -0
  10. sf_veritas/_sffuncspan_config.c +617 -0
  11. sf_veritas/_sffuncspan_config.cpython-39-x86_64-linux-gnu.so +0 -0
  12. sf_veritas/_sfheadercheck.c +341 -0
  13. sf_veritas/_sfheadercheck.cpython-39-x86_64-linux-gnu.so +0 -0
  14. sf_veritas/_sfnetworkhop.c +1451 -0
  15. sf_veritas/_sfnetworkhop.cpython-39-x86_64-linux-gnu.so +0 -0
  16. sf_veritas/_sfservice.c +1175 -0
  17. sf_veritas/_sfservice.cpython-39-x86_64-linux-gnu.so +0 -0
  18. sf_veritas/_sfteepreload.c +5167 -0
  19. sf_veritas/app_config.py +49 -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/concurrent_futures.py +19 -0
  49. sf_veritas/patches/constants.py +1 -0
  50. sf_veritas/patches/exceptions.py +82 -0
  51. sf_veritas/patches/multiprocessing.py +32 -0
  52. sf_veritas/patches/network_libraries/__init__.py +76 -0
  53. sf_veritas/patches/network_libraries/aiohttp.py +281 -0
  54. sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
  55. sf_veritas/patches/network_libraries/http_client.py +419 -0
  56. sf_veritas/patches/network_libraries/httpcore.py +515 -0
  57. sf_veritas/patches/network_libraries/httplib2.py +204 -0
  58. sf_veritas/patches/network_libraries/httpx.py +515 -0
  59. sf_veritas/patches/network_libraries/niquests.py +211 -0
  60. sf_veritas/patches/network_libraries/pycurl.py +385 -0
  61. sf_veritas/patches/network_libraries/requests.py +633 -0
  62. sf_veritas/patches/network_libraries/tornado.py +341 -0
  63. sf_veritas/patches/network_libraries/treq.py +270 -0
  64. sf_veritas/patches/network_libraries/urllib_request.py +468 -0
  65. sf_veritas/patches/network_libraries/utils.py +398 -0
  66. sf_veritas/patches/os.py +17 -0
  67. sf_veritas/patches/threading.py +218 -0
  68. sf_veritas/patches/web_frameworks/__init__.py +54 -0
  69. sf_veritas/patches/web_frameworks/aiohttp.py +793 -0
  70. sf_veritas/patches/web_frameworks/async_websocket_consumer.py +317 -0
  71. sf_veritas/patches/web_frameworks/blacksheep.py +527 -0
  72. sf_veritas/patches/web_frameworks/bottle.py +502 -0
  73. sf_veritas/patches/web_frameworks/cherrypy.py +678 -0
  74. sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
  75. sf_veritas/patches/web_frameworks/django.py +944 -0
  76. sf_veritas/patches/web_frameworks/eve.py +395 -0
  77. sf_veritas/patches/web_frameworks/falcon.py +926 -0
  78. sf_veritas/patches/web_frameworks/fastapi.py +724 -0
  79. sf_veritas/patches/web_frameworks/flask.py +520 -0
  80. sf_veritas/patches/web_frameworks/klein.py +501 -0
  81. sf_veritas/patches/web_frameworks/litestar.py +551 -0
  82. sf_veritas/patches/web_frameworks/pyramid.py +428 -0
  83. sf_veritas/patches/web_frameworks/quart.py +824 -0
  84. sf_veritas/patches/web_frameworks/robyn.py +697 -0
  85. sf_veritas/patches/web_frameworks/sanic.py +857 -0
  86. sf_veritas/patches/web_frameworks/starlette.py +723 -0
  87. sf_veritas/patches/web_frameworks/strawberry.py +813 -0
  88. sf_veritas/patches/web_frameworks/tornado.py +481 -0
  89. sf_veritas/patches/web_frameworks/utils.py +91 -0
  90. sf_veritas/print_override.py +13 -0
  91. sf_veritas/regular_data_transmitter.py +409 -0
  92. sf_veritas/request_interceptor.py +401 -0
  93. sf_veritas/request_utils.py +550 -0
  94. sf_veritas/server_status.py +1 -0
  95. sf_veritas/shutdown_flag.py +11 -0
  96. sf_veritas/subprocess_startup.py +3 -0
  97. sf_veritas/test_cli.py +145 -0
  98. sf_veritas/thread_local.py +970 -0
  99. sf_veritas/timeutil.py +114 -0
  100. sf_veritas/transmit_exception_to_sailfish.py +28 -0
  101. sf_veritas/transmitter.py +132 -0
  102. sf_veritas/types.py +47 -0
  103. sf_veritas/unified_interceptor.py +1580 -0
  104. sf_veritas/utils.py +39 -0
  105. sf_veritas-0.10.3.dist-info/METADATA +97 -0
  106. sf_veritas-0.10.3.dist-info/RECORD +132 -0
  107. sf_veritas-0.10.3.dist-info/WHEEL +5 -0
  108. sf_veritas-0.10.3.dist-info/entry_points.txt +2 -0
  109. sf_veritas-0.10.3.dist-info/top_level.txt +1 -0
  110. sf_veritas.libs/libbrotlicommon-6ce2a53c.so.1.0.6 +0 -0
  111. sf_veritas.libs/libbrotlidec-811d1be3.so.1.0.6 +0 -0
  112. sf_veritas.libs/libcom_err-730ca923.so.2.1 +0 -0
  113. sf_veritas.libs/libcrypt-52aca757.so.1.1.0 +0 -0
  114. sf_veritas.libs/libcrypto-bdaed0ea.so.1.1.1k +0 -0
  115. sf_veritas.libs/libcurl-eaa3cf66.so.4.5.0 +0 -0
  116. sf_veritas.libs/libgssapi_krb5-323bbd21.so.2.2 +0 -0
  117. sf_veritas.libs/libidn2-2f4a5893.so.0.3.6 +0 -0
  118. sf_veritas.libs/libk5crypto-9a74ff38.so.3.1 +0 -0
  119. sf_veritas.libs/libkeyutils-2777d33d.so.1.6 +0 -0
  120. sf_veritas.libs/libkrb5-a55300e8.so.3.3 +0 -0
  121. sf_veritas.libs/libkrb5support-e6594cfc.so.0.1 +0 -0
  122. sf_veritas.libs/liblber-2-d20824ef.4.so.2.10.9 +0 -0
  123. sf_veritas.libs/libldap-2-cea2a960.4.so.2.10.9 +0 -0
  124. sf_veritas.libs/libnghttp2-39367a22.so.14.17.0 +0 -0
  125. sf_veritas.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
  126. sf_veritas.libs/libpsl-99becdd3.so.5.3.1 +0 -0
  127. sf_veritas.libs/libsasl2-7de4d792.so.3.0.0 +0 -0
  128. sf_veritas.libs/libselinux-d0805dcb.so.1 +0 -0
  129. sf_veritas.libs/libssh-c11d285b.so.4.8.7 +0 -0
  130. sf_veritas.libs/libssl-60250281.so.1.1.1k +0 -0
  131. sf_veritas.libs/libunistring-05abdd40.so.2.1.0 +0 -0
  132. sf_veritas.libs/libuuid-95b83d40.so.1.3.0 +0 -0
@@ -0,0 +1,730 @@
1
+ // sf_veritas/_sffastnetworkrequest.c
2
+ // ULTRA-FAST OUTBOUND NETWORK REQUEST TELEMETRY
3
+ // Key optimization: Use g_in_telemetry_send flag (not HTTP headers)
4
+
5
+ #define PY_SSIZE_T_CLEAN
6
+ #include <Python.h>
7
+ #include <pthread.h>
8
+ #include <curl/curl.h>
9
+ #include <stdatomic.h>
10
+ #include <stdint.h>
11
+ #include <stdlib.h>
12
+ #include <string.h>
13
+ #include <time.h>
14
+ #include <sys/time.h>
15
+ #include <unistd.h> // dup/close
16
+ #ifdef __x86_64__
17
+ #include <immintrin.h> // _mm_pause
18
+ #endif
19
+
20
+ // ===================== Thread-local guard flag ====================
21
+ // CRITICAL: Prevents _sfteepreload.c from capturing our telemetry traffic
22
+ __attribute__((visibility("default")))
23
+ __thread int g_in_telemetry_send = 0;
24
+
25
+ // ============================= Tunables =============================
26
+ #ifndef SFF_RING_CAP
27
+ #define SFF_RING_CAP 262144 // power-of-two; raise if you see drops
28
+ #endif
29
+
30
+ // Trim payloads to protect producer latency
31
+ #ifndef SFF_MAX_REQ_BODY
32
+ #define SFF_MAX_REQ_BODY 8192
33
+ #endif
34
+ #ifndef SFF_MAX_RESP_BODY
35
+ #define SFF_MAX_RESP_BODY 0 // 0 = don't capture response body in this path
36
+ #endif
37
+ #ifndef SFF_MAX_HEADER_VALUE
38
+ #define SFF_MAX_HEADER_VALUE 4096
39
+ #endif
40
+
41
+ #ifndef SFF_MAX_INFLIGHT
42
+ #define SFF_MAX_INFLIGHT 32 // concurrent in-flight posts
43
+ #endif
44
+ #ifndef SFF_POLL_TIMEOUT_MS
45
+ #define SFF_POLL_TIMEOUT_MS 10
46
+ #endif
47
+
48
+ // ============================= Types ==============================
49
+ // NEW: Simplified message type - just stores the ready-to-send HTTP body (like _sffastlog.c!)
50
+ typedef struct {
51
+ char *body; // malloc'd HTTP JSON body (READY TO SEND)
52
+ size_t len;
53
+ } sfnr_msg_t;
54
+
55
+ // ======================= Global Module State ======================
56
+ static sfnr_msg_t *g_ring = NULL;
57
+ static size_t g_cap = 0;
58
+ static _Atomic size_t g_head = 0; // consumer index
59
+ static _Atomic size_t g_tail = 0; // producer index
60
+
61
+ // push lock (very short); we signal the cond only on empty->nonempty transition
62
+ static atomic_flag g_push_lock = ATOMIC_FLAG_INIT;
63
+
64
+ // wake/sleep
65
+ static pthread_mutex_t g_cv_mtx = PTHREAD_MUTEX_INITIALIZER;
66
+ static pthread_cond_t g_cv = PTHREAD_COND_INITIALIZER;
67
+ static _Atomic int g_running = 0;
68
+
69
+ // Thread pool for parallel sending (configurable via SF_FASTNETWORKREQUEST_SENDER_THREADS)
70
+ #define MAX_SENDER_THREADS 16
71
+ static pthread_t g_sender_threads[MAX_SENDER_THREADS];
72
+ static int g_num_sender_threads = 0;
73
+ static int g_configured_sender_threads = 1; // Default: 1 thread
74
+
75
+ // curl state (per-thread)
76
+ __thread CURL *g_telem_curl = NULL;
77
+ static struct curl_slist *g_hdrs = NULL;
78
+
79
+ // config (owned strings)
80
+ static char *g_url = NULL;
81
+ static char *g_query_escaped = NULL; // escaped GraphQL "query"
82
+ static char *g_api_key = NULL;
83
+ static char *g_service_uuid = NULL;
84
+ static char *g_library = NULL;
85
+ static char *g_version = NULL;
86
+ static int g_http2 = 1;
87
+
88
+ // JSON prefix: {"query":"<q>","variables":{"apiKey":"...","serviceUuid":"...","library":"...","version":"...
89
+ static char *g_json_prefix = NULL;
90
+ static const char *JSON_SUFFIX = "}}";
91
+
92
+ // ContextVar for trace_id (borrowed reference - don't decref!)
93
+ static PyObject *g_trace_id_ctx = NULL;
94
+
95
+ // Ultra-fast UUID generation (atomic counter + process ID)
96
+ static _Atomic uint64_t g_uuid_counter = 0;
97
+ static uint32_t g_process_id = 0;
98
+
99
+ // ============================ Helpers =============================
100
+ static inline uint64_t now_ms(void) {
101
+ #if defined(CLOCK_REALTIME_COARSE)
102
+ struct timespec ts; clock_gettime(CLOCK_REALTIME_COARSE, &ts);
103
+ return ((uint64_t)ts.tv_sec) * 1000ULL + (uint64_t)(ts.tv_nsec / 1000000ULL);
104
+ #else
105
+ struct timeval tv; gettimeofday(&tv, NULL);
106
+ return ((uint64_t)tv.tv_sec) * 1000ULL + (uint64_t)(tv.tv_usec / 1000ULL);
107
+ #endif
108
+ }
109
+
110
+ static char *str_dup(const char *s) {
111
+ if (!s) return NULL;
112
+ size_t n = strlen(s);
113
+ char *p = (char*)malloc(n + 1);
114
+ if (!p) return NULL;
115
+ memcpy(p, s, n); p[n] = 0;
116
+ return p;
117
+ }
118
+
119
+ // Ultra-fast UUID generation (< 5ns) - atomic counter based, NOT cryptographically random
120
+ // Format: 8 hex chars (pid) + 16 hex chars (timestamp_ms) + 8 hex chars (counter)
121
+ // Example: "12ab34cd-0000018d12345678-00000001"
122
+ static inline void fast_uuid(char *buf) {
123
+ uint64_t counter = atomic_fetch_add_explicit(&g_uuid_counter, 1, memory_order_relaxed);
124
+ uint64_t ts = now_ms();
125
+
126
+ // Format: PPPPPPPP-TTTTTTTTTTTTTTTT-CCCCCCCC (32 chars + 2 dashes = 34 chars + null)
127
+ static const char hex[] = "0123456789abcdef";
128
+ char *p = buf;
129
+
130
+ // Process ID (8 hex chars)
131
+ uint32_t pid = g_process_id;
132
+ for (int i = 7; i >= 0; i--) {
133
+ p[i] = hex[pid & 0xF];
134
+ pid >>= 4;
135
+ }
136
+ p += 8;
137
+ *p++ = '-';
138
+
139
+ // Timestamp (16 hex chars)
140
+ for (int i = 15; i >= 0; i--) {
141
+ p[i] = hex[ts & 0xF];
142
+ ts >>= 4;
143
+ }
144
+ p += 16;
145
+ *p++ = '-';
146
+
147
+ // Counter (8 hex chars)
148
+ for (int i = 7; i >= 0; i--) {
149
+ p[i] = hex[counter & 0xF];
150
+ counter >>= 4;
151
+ }
152
+ p += 8;
153
+ *p = '\0';
154
+ }
155
+
156
+ // Extract parent request ID from trace_id string
157
+ // trace_id format: "NONSESSION_APPLOGS-v3/api_key/uuid"
158
+ // Returns pointer to the UUID part (after last '/'), or NULL if not found
159
+ static inline const char *extract_parent_request_id(const char *trace_id) {
160
+ if (!trace_id) return NULL;
161
+ const char *last_slash = strrchr(trace_id, '/');
162
+ return last_slash ? (last_slash + 1) : NULL;
163
+ }
164
+
165
+ // --- JSON escaping that writes into a growing buffer (no per-field mallocs) ---
166
+ typedef struct {
167
+ char *buf; size_t len; size_t cap;
168
+ } sb_t;
169
+
170
+ static int sb_grow(sb_t *sb, size_t need) {
171
+ if (sb->len + need <= sb->cap) return 1;
172
+ size_t ncap = sb->cap ? sb->cap : 1024;
173
+ while (ncap < sb->len + need) ncap <<= 1;
174
+ char *nb = (char*)realloc(sb->buf, ncap);
175
+ if (!nb) return 0;
176
+ sb->buf = nb; sb->cap = ncap; return 1;
177
+ }
178
+
179
+ static int sb_putc(sb_t *sb, char c) {
180
+ if (!sb_grow(sb, 1)) return 0;
181
+ sb->buf[sb->len++] = c; return 1;
182
+ }
183
+ static int sb_puts(sb_t *sb, const char *s, size_t n) {
184
+ if (!sb_grow(sb, n)) return 0;
185
+ memcpy(sb->buf + sb->len, s, n); sb->len += n; return 1;
186
+ }
187
+
188
+ static int sb_put_escaped_json_string(sb_t *sb, const char *s) {
189
+ // write a JSON string with quotes and escapes
190
+ if (!sb_putc(sb, '"')) return 0;
191
+ for (const unsigned char *p = (const unsigned char*)s; *p; ++p) {
192
+ unsigned char c = *p;
193
+ if (c == '"' || c == '\\') {
194
+ if (!sb_grow(sb, 2)) return 0;
195
+ sb->buf[sb->len++]='\\'; sb->buf[sb->len++]=c;
196
+ } else if (c < 0x20) {
197
+ // \u00XX
198
+ if (!sb_grow(sb, 6)) return 0;
199
+ static const char hex[]="0123456789abcdef";
200
+ sb->buf[sb->len++]='\\'; sb->buf[sb->len++]='u';
201
+ sb->buf[sb->len++]='0'; sb->buf[sb->len++]='0';
202
+ sb->buf[sb->len++]=hex[c>>4];
203
+ sb->buf[sb->len++]=hex[c&0xF];
204
+ } else {
205
+ if (!sb_putc(sb,(char)c)) return 0;
206
+ }
207
+ }
208
+ return sb_putc(sb, '"');
209
+ }
210
+
211
+ static int sb_put_uint(sb_t *sb, uint64_t v) {
212
+ // max 20 digits
213
+ char tmp[32]; int n = 0;
214
+ if (v == 0) { return sb_putc(sb, '0'); }
215
+ while (v) { tmp[n++] = (char)('0' + (v % 10)); v /= 10; }
216
+ if (!sb_grow(sb, (size_t)n)) return 0;
217
+ for (int i=n-1;i>=0;--i) sb->buf[sb->len++]=tmp[i];
218
+ return 1;
219
+ }
220
+
221
+ static int sb_put_int(sb_t *sb, int v) {
222
+ if (v < 0) { if (!sb_putc(sb,'-')) return 0; return sb_put_uint(sb, (uint64_t)(-(int64_t)v)); }
223
+ return sb_put_uint(sb, (uint64_t)v);
224
+ }
225
+
226
+ // =========================== Ring Buffer ==========================
227
+ static inline size_t ring_count(void) {
228
+ size_t h = atomic_load_explicit(&g_head, memory_order_acquire);
229
+ size_t t = atomic_load_explicit(&g_tail, memory_order_acquire);
230
+ return t - h;
231
+ }
232
+ static inline int ring_empty(void) { return ring_count() == 0; }
233
+
234
+ // NEW: Simplified ring push - stores ready-to-send body (like _sffastlog.c!)
235
+ static int ring_push(char *body, size_t len) {
236
+ while (atomic_flag_test_and_set_explicit(&g_push_lock, memory_order_acquire)) {
237
+ // brief spin
238
+ }
239
+ size_t t = atomic_load_explicit(&g_tail, memory_order_relaxed);
240
+ size_t h = atomic_load_explicit(&g_head, memory_order_acquire);
241
+ if ((t - h) >= g_cap) {
242
+ atomic_flag_clear_explicit(&g_push_lock, memory_order_release);
243
+ return 0; // full (drop)
244
+ }
245
+ size_t idx = t % g_cap;
246
+ g_ring[idx].body = body;
247
+ g_ring[idx].len = len;
248
+ atomic_store_explicit(&g_tail, t + 1, memory_order_release);
249
+ atomic_flag_clear_explicit(&g_push_lock, memory_order_release);
250
+
251
+ pthread_mutex_lock(&g_cv_mtx);
252
+ pthread_cond_signal(&g_cv);
253
+ pthread_mutex_unlock(&g_cv_mtx);
254
+ return 1;
255
+ }
256
+
257
+ static int ring_pop(char **body, size_t *len) {
258
+ size_t h = atomic_load_explicit(&g_head, memory_order_relaxed);
259
+ size_t t = atomic_load_explicit(&g_tail, memory_order_acquire);
260
+ if (h == t) return 0;
261
+ size_t idx = h % g_cap;
262
+ *body = g_ring[idx].body;
263
+ *len = g_ring[idx].len;
264
+ g_ring[idx].body = NULL;
265
+ g_ring[idx].len = 0;
266
+ atomic_store_explicit(&g_head, h + 1, memory_order_release);
267
+ return 1;
268
+ }
269
+
270
+ // ========================= CURL Callbacks =========================
271
+ static size_t _sink_write(char *ptr, size_t size, size_t nmemb, void *userdata) {
272
+ (void)ptr; (void)userdata; return size * nmemb;
273
+ }
274
+ static size_t _sink_header(char *ptr, size_t size, size_t nmemb, void *userdata) {
275
+ (void)ptr; (void)userdata; return size * nmemb;
276
+ }
277
+
278
+ // ========================= JSON Escape (needed by builder) ===========
279
+ // escape for generic JSON string fields
280
+ static char *json_escape(const char *s) {
281
+ if (!s) return str_dup("");
282
+ const unsigned char *in = (const unsigned char*)s;
283
+ size_t extra = 0;
284
+ for (const unsigned char *p = in; *p; ++p) {
285
+ switch (*p) {
286
+ case '\\': case '"': extra++; break;
287
+ default:
288
+ if (*p < 0x20) extra += 5; // \u00XX
289
+ }
290
+ }
291
+ size_t inlen = strlen(s);
292
+ char *out = (char*)malloc(inlen + extra + 1);
293
+ if (!out) return NULL;
294
+
295
+ char *o = out;
296
+ for (const unsigned char *p = in; *p; ++p) {
297
+ switch (*p) {
298
+ case '\\': *o++='\\'; *o++='\\'; break;
299
+ case '"': *o++='\\'; *o++='"'; break;
300
+ default:
301
+ if (*p < 0x20) {
302
+ static const char hex[] = "0123456789abcdef";
303
+ *o++='\\'; *o++='u'; *o++='0'; *o++='0';
304
+ *o++=hex[(*p)>>4]; *o++=hex[(*p)&0xF];
305
+ } else {
306
+ *o++ = (char)*p;
307
+ }
308
+ }
309
+ }
310
+ *o = 0;
311
+ return out;
312
+ }
313
+
314
+ // ========================= JSON builder (PRODUCER THREAD - RELEASED GIL) ===========
315
+ // NEW: Build complete GraphQL body in producer thread (ZERO GIL contention!)
316
+ // This is the KEY OPTIMIZATION from _sffastlog.c (lines 189-233)
317
+ static int build_body_networkhop(
318
+ const char *trace_id,
319
+ const char *url,
320
+ const char *method,
321
+ int status,
322
+ int ok,
323
+ uint64_t ts_start,
324
+ uint64_t ts_end,
325
+ const char *req_body, size_t req_len,
326
+ const char *resp_body, size_t resp_len,
327
+ const char *req_hdrs_json,
328
+ const char *resp_hdrs_json,
329
+ char **out_body,
330
+ size_t *out_len
331
+ ) {
332
+ // Build ,"event":{...} - inputs are safe pointers from Python
333
+ sb_t sb = {0};
334
+ if (!sb_puts(&sb, g_json_prefix, strlen(g_json_prefix))) goto fail;
335
+ if (!sb_puts(&sb, ",\"event\":{", 10)) goto fail;
336
+ if (!sb_puts(&sb, "\"traceId\":", 10)) goto fail;
337
+ if (!sb_put_escaped_json_string(&sb, trace_id ? trace_id : "")) goto fail;
338
+ if (!sb_puts(&sb, ",\"url\":", 8)) goto fail;
339
+ if (!sb_put_escaped_json_string(&sb, url ? url : "")) goto fail;
340
+ if (!sb_puts(&sb, ",\"method\":", 11)) goto fail;
341
+ if (!sb_put_escaped_json_string(&sb, method ? method : "")) goto fail;
342
+ if (!sb_puts(&sb, ",\"status\":", 11)) goto fail;
343
+ if (!sb_put_int(&sb, status)) goto fail;
344
+ if (!sb_puts(&sb, ",\"ok\":", 6)) goto fail;
345
+ if (!sb_puts(&sb, ok ? "true" : "false", ok ? 4 : 5)) goto fail;
346
+ if (!sb_puts(&sb, ",\"timestampStartMs\":", 21)) goto fail;
347
+ if (!sb_put_uint(&sb, ts_start)) goto fail;
348
+ if (!sb_puts(&sb, ",\"timestampEndMs\":", 19)) goto fail;
349
+ if (!sb_put_uint(&sb, ts_end)) goto fail;
350
+
351
+ // Headers (already JSON strings from Python orjson!)
352
+ if (!sb_puts(&sb, ",\"requestHeaders\":", 19)) goto fail;
353
+ if (req_hdrs_json && req_hdrs_json[0]) {
354
+ if (!sb_puts(&sb, req_hdrs_json, strlen(req_hdrs_json))) goto fail;
355
+ } else {
356
+ if (!sb_puts(&sb, "{}", 2)) goto fail;
357
+ }
358
+
359
+ if (!sb_puts(&sb, ",\"responseHeaders\":", 20)) goto fail;
360
+ if (resp_hdrs_json && resp_hdrs_json[0]) {
361
+ if (!sb_puts(&sb, resp_hdrs_json, strlen(resp_hdrs_json))) goto fail;
362
+ } else {
363
+ if (!sb_puts(&sb, "{}", 2)) goto fail;
364
+ }
365
+
366
+ // Bodies (escaped)
367
+ if (!sb_puts(&sb, ",\"requestBody\":", 15)) goto fail;
368
+ if (req_body && req_len) {
369
+ char *tmp = (char*)malloc(req_len + 1);
370
+ if (tmp) {
371
+ memcpy(tmp, req_body, req_len); tmp[req_len]=0;
372
+ sb_put_escaped_json_string(&sb, tmp);
373
+ free(tmp);
374
+ } else {
375
+ sb_put_escaped_json_string(&sb, "");
376
+ }
377
+ } else {
378
+ if (!sb_put_escaped_json_string(&sb, "")) goto fail;
379
+ }
380
+
381
+ #if SFF_MAX_RESP_BODY > 0
382
+ if (!sb_puts(&sb, ",\"responseBody\":", 16)) goto fail;
383
+ if (resp_body && resp_len) {
384
+ char *tmp2 = (char*)malloc(resp_len + 1);
385
+ if (tmp2) {
386
+ memcpy(tmp2, resp_body, resp_len); tmp2[resp_len]=0;
387
+ sb_put_escaped_json_string(&sb, tmp2);
388
+ free(tmp2);
389
+ } else {
390
+ sb_put_escaped_json_string(&sb, "");
391
+ }
392
+ } else {
393
+ if (!sb_put_escaped_json_string(&sb, "")) goto fail;
394
+ }
395
+ #endif
396
+
397
+ if (!sb_putc(&sb, '}')) goto fail; // close event
398
+ if (!sb_puts(&sb, JSON_SUFFIX, strlen(JSON_SUFFIX))) goto fail;
399
+ if (!sb_grow(&sb, 1)) goto fail;
400
+ if (sb.buf) sb.buf[sb.len] = 0;
401
+
402
+ *out_body = sb.buf;
403
+ *out_len = sb.len;
404
+ return 1;
405
+
406
+ fail:
407
+ free(sb.buf);
408
+ return 0;
409
+ }
410
+
411
+ static int build_prefix_for_query(const char *query_escaped, char **out_prefix) {
412
+ if (!query_escaped || !g_api_key || !g_service_uuid || !g_library || !g_version) return 0;
413
+
414
+ const char *p1 = "{\"query\":\"";
415
+ const char *p2 = "\",\"variables\":{";
416
+ const char *k1 = "\"apiKey\":\"";
417
+ const char *k2 = "\",\"serviceUuid\":\"";
418
+ const char *k3 = "\",\"library\":\"";
419
+ const char *k4 = "\",\"version\":\"";
420
+
421
+ size_t n = strlen(p1) + strlen(query_escaped) + strlen(p2)
422
+ + strlen(k1) + strlen(g_api_key)
423
+ + strlen(k2) + strlen(g_service_uuid)
424
+ + strlen(k3) + strlen(g_library)
425
+ + strlen(k4) + strlen(g_version) + 5;
426
+
427
+ char *prefix = (char*)malloc(n);
428
+ if (!prefix) return 0;
429
+
430
+ char *o = prefix;
431
+ o += sprintf(o, "%s%s%s", p1, query_escaped, p2);
432
+ o += sprintf(o, "%s%s", k1, g_api_key);
433
+ o += sprintf(o, "%s%s", k2, g_service_uuid);
434
+ o += sprintf(o, "%s%s", k3, g_library);
435
+ o += sprintf(o, "%s%s\"", k4, g_version);
436
+ *o = '\0';
437
+
438
+ *out_prefix = prefix;
439
+ return 1;
440
+ }
441
+
442
+ // escape for the GraphQL "query" string (handle \n, \r, \t too)
443
+ static char *json_escape_query(const char *s) {
444
+ if (!s) return str_dup("");
445
+ const unsigned char *in = (const unsigned char*)s;
446
+ size_t extra = 0;
447
+ for (const unsigned char *p = in; *p; ++p) {
448
+ switch (*p) { case '\\': case '"': case '\n': case '\r': case '\t': extra++; break; default: break; }
449
+ }
450
+ size_t inlen = strlen(s);
451
+ char *out = (char*)malloc(inlen + extra + 1);
452
+ if (!out) return NULL;
453
+ char *o = out;
454
+ for (const unsigned char *p = in; *p; ++p) {
455
+ switch (*p) {
456
+ case '\\': *o++='\\'; *o++='\\'; break;
457
+ case '"': *o++='\\'; *o++='"'; break;
458
+ case '\n': *o++='\\'; *o++='n'; break;
459
+ case '\r': *o++='\\'; *o++='r'; break;
460
+ case '\t': *o++='\\'; *o++='t'; break;
461
+ default: *o++=(char)*p;
462
+ }
463
+ }
464
+ *o=0; return out;
465
+ }
466
+
467
+ // ========================= pthread cleanup handler ==========================
468
+ static void sender_cleanup(void *arg) {
469
+ (void)arg;
470
+
471
+ // Close thread-local curl handle
472
+ if (g_telem_curl) {
473
+ curl_easy_cleanup(g_telem_curl);
474
+ g_telem_curl = NULL;
475
+ }
476
+ }
477
+
478
+ // ========================= Sender Thread ==========================
479
+ static void *sender_main(void *arg) {
480
+ (void)arg;
481
+
482
+ // CRITICAL: Set telemetry guard for this thread (prevents _sfteepreload.c capture)
483
+ g_in_telemetry_send = 1;
484
+
485
+ // Initialize thread-local curl handle
486
+ g_telem_curl = curl_easy_init();
487
+ if (!g_telem_curl) {
488
+ return NULL;
489
+ }
490
+
491
+ // Configure curl handle (copy from global settings)
492
+ curl_easy_setopt(g_telem_curl, CURLOPT_URL, g_url);
493
+ curl_easy_setopt(g_telem_curl, CURLOPT_TCP_KEEPALIVE, 1L);
494
+ curl_easy_setopt(g_telem_curl, CURLOPT_TCP_NODELAY, 1L); // NEW: Eliminate Nagle delay
495
+ curl_easy_setopt(g_telem_curl, CURLOPT_NOSIGNAL, 1L);
496
+ #ifdef CURLOPT_TCP_FASTOPEN
497
+ curl_easy_setopt(g_telem_curl, CURLOPT_TCP_FASTOPEN, 1L);
498
+ #endif
499
+ #ifdef CURL_HTTP_VERSION_2TLS
500
+ if (g_http2) {
501
+ curl_easy_setopt(g_telem_curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
502
+ }
503
+ #endif
504
+ curl_easy_setopt(g_telem_curl, CURLOPT_HTTPHEADER, g_hdrs);
505
+ curl_easy_setopt(g_telem_curl, CURLOPT_WRITEFUNCTION, _sink_write);
506
+ curl_easy_setopt(g_telem_curl, CURLOPT_HEADERFUNCTION, _sink_header);
507
+
508
+ // Register cleanup handler (executes on thread exit)
509
+ pthread_cleanup_push(sender_cleanup, NULL);
510
+
511
+ while (atomic_load(&g_running)) {
512
+ if (ring_empty()) {
513
+ pthread_mutex_lock(&g_cv_mtx);
514
+ if (ring_empty() && atomic_load(&g_running))
515
+ pthread_cond_wait(&g_cv, &g_cv_mtx);
516
+ pthread_mutex_unlock(&g_cv_mtx);
517
+ if (!atomic_load(&g_running)) break;
518
+ }
519
+ char *body = NULL; size_t len = 0;
520
+ while (ring_pop(&body, &len)) {
521
+ if (!body) continue;
522
+
523
+ // Use thread-local curl handle (each thread has its own persistent connection)
524
+ curl_easy_setopt(g_telem_curl, CURLOPT_POSTFIELDS, body);
525
+ curl_easy_setopt(g_telem_curl, CURLOPT_POSTFIELDSIZE, (long)len);
526
+ (void)curl_easy_perform(g_telem_curl); // fire-and-forget
527
+
528
+ free(body);
529
+ if (!atomic_load(&g_running)) break;
530
+ }
531
+ }
532
+
533
+ pthread_cleanup_pop(1); // Execute cleanup handler
534
+ return NULL;
535
+ }
536
+
537
+ // ============================ Python API ==========================
538
+
539
+ // init_networkhop(url, query, api_key, service_uuid, library, version, http2=1)
540
+ static PyObject *py_init_networkhop(PyObject *self, PyObject *args, PyObject *kw) {
541
+ const char *url, *query, *api_key, *service_uuid, *library, *version;
542
+ int http2 = 1;
543
+ static char *kwlist[] = {"url","query","api_key","service_uuid","library","version","http2",NULL};
544
+ if (!PyArg_ParseTupleAndKeywords(args, kw, "ssssssi", kwlist,
545
+ &url, &query, &api_key, &service_uuid, &library, &version, &http2)) {
546
+ return NULL;
547
+ }
548
+ if (g_running) {
549
+ // refresh query/prefix only
550
+ free(g_query_escaped); g_query_escaped = json_escape_query(query);
551
+ free(g_json_prefix); g_json_prefix = NULL;
552
+ if (!g_query_escaped || !build_prefix_for_query(g_query_escaped, &g_json_prefix)) Py_RETURN_FALSE;
553
+ Py_RETURN_TRUE;
554
+ }
555
+
556
+ // copy config
557
+ g_url = str_dup(url);
558
+ g_query_escaped = json_escape_query(query);
559
+ g_api_key = str_dup(api_key);
560
+ g_service_uuid = str_dup(service_uuid);
561
+ g_library = str_dup(library);
562
+ g_version = str_dup(version);
563
+ g_http2 = http2 ? 1 : 0;
564
+
565
+ if (!g_url || !g_query_escaped || !g_api_key || !g_service_uuid || !g_library || !g_version) Py_RETURN_FALSE;
566
+ if (!build_prefix_for_query(g_query_escaped, &g_json_prefix)) Py_RETURN_FALSE;
567
+
568
+ // ring
569
+ g_cap = SFF_RING_CAP;
570
+ g_ring = (sfnr_msg_t*)calloc(g_cap, sizeof(sfnr_msg_t));
571
+ if (!g_ring) Py_RETURN_FALSE;
572
+
573
+ // Parse SF_FASTNETWORKREQUEST_SENDER_THREADS environment variable
574
+ const char *env_threads = getenv("SF_FASTNETWORKREQUEST_SENDER_THREADS");
575
+ if (env_threads) {
576
+ int t = atoi(env_threads);
577
+ if (t > 0 && t <= MAX_SENDER_THREADS) {
578
+ g_configured_sender_threads = t;
579
+ }
580
+ }
581
+
582
+ // curl (shared headers only - handles are per-thread)
583
+ curl_global_init(CURL_GLOBAL_DEFAULT);
584
+ g_hdrs = NULL;
585
+ g_hdrs = curl_slist_append(g_hdrs, "Content-Type: application/json");
586
+
587
+ // Start sender thread pool
588
+ atomic_store(&g_running, 1);
589
+ g_num_sender_threads = g_configured_sender_threads;
590
+ for (int i = 0; i < g_num_sender_threads; i++) {
591
+ if (pthread_create(&g_sender_threads[i], NULL, sender_main, NULL) != 0) {
592
+ atomic_store(&g_running, 0);
593
+ // Clean up already-started threads
594
+ for (int j = 0; j < i; j++) {
595
+ pthread_join(g_sender_threads[j], NULL);
596
+ }
597
+ Py_RETURN_FALSE;
598
+ }
599
+ }
600
+
601
+ Py_RETURN_TRUE;
602
+ }
603
+
604
+ // networkhop(...) - ULTRA-FAST: Build JSON WITH GIL RELEASED (like _sffastlog.c!)
605
+ // This is THE KEY OPTIMIZATION: All JSON work happens WITHOUT GIL held
606
+ static PyObject *py_networkhop(PyObject *self, PyObject *args, PyObject *kw) {
607
+ const char *trace_id, *url, *method;
608
+ int status, ok;
609
+ long long ts_start, ts_end;
610
+ const char *req_body = NULL; Py_ssize_t req_len = 0;
611
+ const char *resp_body = NULL; Py_ssize_t resp_len = 0;
612
+ const char *req_hdrs_json = NULL; // JSON string from Python orjson
613
+ const char *resp_hdrs_json = NULL; // JSON string from Python orjson
614
+
615
+ static char *kwlist[] = {
616
+ "trace_id","url","method","status","ok",
617
+ "timestamp_start","timestamp_end",
618
+ "request_body","response_body","request_headers_json","response_headers_json", NULL
619
+ };
620
+
621
+ if (!PyArg_ParseTupleAndKeywords(
622
+ args, kw, "sssiiLL|y#y#ss", kwlist,
623
+ &trace_id, &url, &method, &status, &ok,
624
+ &ts_start, &ts_end,
625
+ &req_body, &req_len, &resp_body, &resp_len, &req_hdrs_json, &resp_hdrs_json)) {
626
+ Py_RETURN_NONE;
627
+ }
628
+ if (!g_running || !g_json_prefix) Py_RETURN_NONE;
629
+
630
+ // CRITICAL: Copy ONLY the small inputs WITH GIL held (just pointers to Python strings)
631
+ // We'll use them directly in build_body_networkhop() WITHOUT GIL
632
+ // Python guarantees these strings stay alive during Py_BEGIN_ALLOW_THREADS
633
+
634
+ // Build complete JSON body WITHOUT GIL HELD (like _sffastlog.c lines 604-610!)
635
+ char *body = NULL;
636
+ size_t len = 0;
637
+ int ok_result = 0;
638
+
639
+ Py_BEGIN_ALLOW_THREADS
640
+ // Build JSON body (WITHOUT GIL - pure C string operations)
641
+ if (build_body_networkhop(
642
+ trace_id, url, method, status, ok,
643
+ (uint64_t)ts_start, (uint64_t)ts_end,
644
+ req_body, (size_t)req_len,
645
+ resp_body, (size_t)resp_len,
646
+ req_hdrs_json, resp_hdrs_json,
647
+ &body, &len)) {
648
+ // Push to ring buffer (WITHOUT GIL)
649
+ ok_result = ring_push(body, len);
650
+ }
651
+ Py_END_ALLOW_THREADS
652
+
653
+ if (!ok_result) { free(body); } // drop on backpressure
654
+ Py_RETURN_NONE;
655
+ }
656
+
657
+ // is_ready()
658
+ static PyObject *py_is_ready(PyObject *self, PyObject *args) {
659
+ if (g_running && g_json_prefix) Py_RETURN_TRUE;
660
+ Py_RETURN_FALSE;
661
+ }
662
+
663
+ // shutdown()
664
+ static PyObject *py_shutdown(PyObject *self, PyObject *args) {
665
+ if (!g_running) Py_RETURN_NONE;
666
+
667
+ atomic_store(&g_running, 0);
668
+
669
+ // Wake ALL threads with broadcast (not signal)
670
+ pthread_mutex_lock(&g_cv_mtx);
671
+ pthread_cond_broadcast(&g_cv);
672
+ pthread_mutex_unlock(&g_cv_mtx);
673
+
674
+ // Join all sender threads in thread pool
675
+ for (int i = 0; i < g_num_sender_threads; i++) {
676
+ if (g_sender_threads[i]) {
677
+ pthread_join(g_sender_threads[i], NULL);
678
+ g_sender_threads[i] = 0;
679
+ }
680
+ }
681
+ g_num_sender_threads = 0;
682
+
683
+ // Cleanup curl (per-thread handles cleaned by pthread_cleanup_push)
684
+ if (g_hdrs) { curl_slist_free_all(g_hdrs); g_hdrs = NULL; }
685
+ curl_global_cleanup();
686
+
687
+ // Free all config strings and NULL pointers
688
+ free(g_url); g_url = NULL;
689
+ free(g_query_escaped); g_query_escaped = NULL;
690
+ free(g_json_prefix); g_json_prefix = NULL;
691
+ free(g_api_key); g_api_key = NULL;
692
+ free(g_service_uuid); g_service_uuid = NULL;
693
+ free(g_library); g_library = NULL;
694
+ free(g_version); g_version = NULL;
695
+
696
+ // Drain and free ring buffer
697
+ if (g_ring) {
698
+ char *b; size_t l;
699
+ while (ring_pop(&b, &l)) free(b);
700
+ free(g_ring); g_ring = NULL;
701
+ }
702
+
703
+ Py_RETURN_NONE;
704
+ }
705
+
706
+ // (kept for compatibility; just forwards to networkhop)
707
+ static PyObject *py_networkhop_async(PyObject *self, PyObject *args, PyObject *kw) {
708
+ return py_networkhop(self, args, kw);
709
+ }
710
+
711
+ static PyMethodDef SFNReqMethods[] = {
712
+ {"init_networkhop", (PyCFunction)py_init_networkhop, METH_VARARGS | METH_KEYWORDS, "Init transport and prefix for outbound network hops"},
713
+ {"networkhop", (PyCFunction)py_networkhop, METH_VARARGS | METH_KEYWORDS, "Enqueue outbound network hop (non-blocking)"},
714
+ {"networkhop_async", (PyCFunction)py_networkhop_async, METH_VARARGS | METH_KEYWORDS, "Alias of networkhop"},
715
+ {"is_ready", (PyCFunction)py_is_ready, METH_NOARGS, "Return True if sender thread/transport is up"},
716
+ {"shutdown", (PyCFunction)py_shutdown, METH_NOARGS, "Shutdown sender and free state"},
717
+ {NULL, NULL, 0, NULL}
718
+ };
719
+
720
+ static struct PyModuleDef sfnreq_module = {
721
+ PyModuleDef_HEAD_INIT,
722
+ "_sffastnetworkrequest",
723
+ "sf_veritas ultra-fast outbound network request telemetry (producer->ring->sender)",
724
+ -1,
725
+ SFNReqMethods
726
+ };
727
+
728
+ PyMODINIT_FUNC PyInit__sffastnetworkrequest(void) {
729
+ return PyModule_Create(&sfnreq_module);
730
+ }