sf-veritas 0.10.3__cp311-cp311-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-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnet.c +924 -0
- sf_veritas/_sffastnet.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnetworkrequest.c +730 -0
- sf_veritas/_sffastnetworkrequest.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan.c +2155 -0
- sf_veritas/_sffuncspan.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan_config.c +617 -0
- sf_veritas/_sffuncspan_config.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfheadercheck.c +341 -0
- sf_veritas/_sfheadercheck.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfnetworkhop.c +1451 -0
- sf_veritas/_sfnetworkhop.cpython-311-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfservice.c +1175 -0
- sf_veritas/_sfservice.cpython-311-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
|
@@ -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
|
+
}
|