sf-veritas 0.10.3__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.
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-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnet.c +924 -0
- sf_veritas/_sffastnet.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnetworkrequest.c +730 -0
- sf_veritas/_sffastnetworkrequest.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan.c +2155 -0
- sf_veritas/_sffuncspan.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan_config.c +617 -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 +1451 -0
- sf_veritas/_sfnetworkhop.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfservice.c +1175 -0
- sf_veritas/_sfservice.cpython-314-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
sf_veritas/_sffastnet.c
ADDED
|
@@ -0,0 +1,924 @@
|
|
|
1
|
+
// sf_veritas/_sffastnet.c
|
|
2
|
+
// Ultra-fast network request capture with request/response data
|
|
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
|
+
|
|
16
|
+
// ===================== Thread-local guard flag ====================
|
|
17
|
+
// CRITICAL: Prevents _sfteepreload.c from capturing our telemetry traffic
|
|
18
|
+
__attribute__((visibility("default")))
|
|
19
|
+
__thread int g_in_telemetry_send = 0;
|
|
20
|
+
|
|
21
|
+
// ---------- Ring buffer ----------
|
|
22
|
+
#ifndef SFN_RING_CAP
|
|
23
|
+
#define SFN_RING_CAP 65536 // power-of-two recommended
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
typedef struct {
|
|
27
|
+
char *body; // malloc'd HTTP JSON body
|
|
28
|
+
size_t len;
|
|
29
|
+
} sfn_msg_t;
|
|
30
|
+
|
|
31
|
+
static sfn_msg_t *g_ring = NULL;
|
|
32
|
+
static size_t g_cap = 0;
|
|
33
|
+
static _Atomic size_t g_head = 0; // consumer
|
|
34
|
+
static _Atomic size_t g_tail = 0; // producer
|
|
35
|
+
|
|
36
|
+
// tiny spinlock to make push MPMC-safe enough for Python producers
|
|
37
|
+
static atomic_flag g_push_lock = ATOMIC_FLAG_INIT;
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
// wake/sleep
|
|
41
|
+
static pthread_mutex_t g_cv_mtx = PTHREAD_MUTEX_INITIALIZER;
|
|
42
|
+
static pthread_cond_t g_cv = PTHREAD_COND_INITIALIZER;
|
|
43
|
+
static _Atomic int g_running = 0;
|
|
44
|
+
|
|
45
|
+
// Thread pool for parallel sending (configurable via SF_FASTNET_SENDER_THREADS)
|
|
46
|
+
#define MAX_SENDER_THREADS 16
|
|
47
|
+
static pthread_t g_sender_threads[MAX_SENDER_THREADS];
|
|
48
|
+
static int g_num_sender_threads = 0;
|
|
49
|
+
static int g_configured_sender_threads = 1; // Default: 1 thread
|
|
50
|
+
|
|
51
|
+
// Debug flag (set from SF_DEBUG environment variable)
|
|
52
|
+
static int SF_DEBUG = 0;
|
|
53
|
+
|
|
54
|
+
// curl state (per-thread handles + shared headers)
|
|
55
|
+
__thread CURL *g_telem_curl = NULL;
|
|
56
|
+
static struct curl_slist *g_hdrs = NULL;
|
|
57
|
+
|
|
58
|
+
// config (owned strings)
|
|
59
|
+
static char *g_url = NULL;
|
|
60
|
+
static char *g_query_escaped = NULL;
|
|
61
|
+
static char *g_api_key = NULL;
|
|
62
|
+
static int g_http2 = 0;
|
|
63
|
+
|
|
64
|
+
// prebuilt JSON prefix for NETWORK REQUEST:
|
|
65
|
+
// {"query":"<escaped_query>","variables":{"data":{"apiKey":"..."
|
|
66
|
+
static char *g_json_prefix = NULL;
|
|
67
|
+
|
|
68
|
+
static const char *JSON_SUFFIX = "}}}";
|
|
69
|
+
|
|
70
|
+
// ---------- helpers ----------
|
|
71
|
+
static inline uint64_t now_ms(void) {
|
|
72
|
+
#if defined(CLOCK_REALTIME_COARSE)
|
|
73
|
+
struct timespec ts;
|
|
74
|
+
clock_gettime(CLOCK_REALTIME_COARSE, &ts);
|
|
75
|
+
return ((uint64_t)ts.tv_sec) * 1000ULL + (uint64_t)(ts.tv_nsec / 1000000ULL);
|
|
76
|
+
#else
|
|
77
|
+
struct timeval tv;
|
|
78
|
+
gettimeofday(&tv, NULL);
|
|
79
|
+
return ((uint64_t)tv.tv_sec) * 1000ULL + (uint64_t)(tv.tv_usec / 1000ULL);
|
|
80
|
+
#endif
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static char *str_dup(const char *s) {
|
|
84
|
+
size_t n = strlen(s);
|
|
85
|
+
char *p = (char*)malloc(n + 1);
|
|
86
|
+
if (!p) return NULL;
|
|
87
|
+
memcpy(p, s, n);
|
|
88
|
+
p[n] = 0;
|
|
89
|
+
return p;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Fast path: check if string needs escaping at all
|
|
93
|
+
static inline int needs_escape(const char *s, size_t len) {
|
|
94
|
+
for (size_t i = 0; i < len; ++i) {
|
|
95
|
+
unsigned char c = (unsigned char)s[i];
|
|
96
|
+
if (c == '\\' || c == '"' || c < 0x20) return 1;
|
|
97
|
+
}
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// escape for generic JSON string fields
|
|
102
|
+
static char *json_escape(const char *s) {
|
|
103
|
+
if (!s) return str_dup("");
|
|
104
|
+
|
|
105
|
+
size_t inlen = strlen(s);
|
|
106
|
+
|
|
107
|
+
// Fast path: if no escaping needed, just duplicate
|
|
108
|
+
if (!needs_escape(s, inlen)) {
|
|
109
|
+
return str_dup(s);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Slow path: need to escape
|
|
113
|
+
const unsigned char *in = (const unsigned char*)s;
|
|
114
|
+
size_t extra = 0;
|
|
115
|
+
for (const unsigned char *p = in; *p; ++p) {
|
|
116
|
+
switch (*p) {
|
|
117
|
+
case '\\': case '"': extra++; break;
|
|
118
|
+
default:
|
|
119
|
+
if (*p < 0x20) extra += 5; // \u00XX
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
char *out = (char*)malloc(inlen + extra + 1);
|
|
124
|
+
if (!out) return NULL;
|
|
125
|
+
|
|
126
|
+
char *o = out;
|
|
127
|
+
for (const unsigned char *p = in; *p; ++p) {
|
|
128
|
+
switch (*p) {
|
|
129
|
+
case '\\': *o++='\\'; *o++='\\'; break;
|
|
130
|
+
case '"': *o++='\\'; *o++='"'; break;
|
|
131
|
+
default:
|
|
132
|
+
if (*p < 0x20) {
|
|
133
|
+
static const char hex[] = "0123456789abcdef";
|
|
134
|
+
*o++='\\'; *o++='u'; *o++='0'; *o++='0';
|
|
135
|
+
*o++=hex[(*p)>>4]; *o++=hex[(*p)&0xF];
|
|
136
|
+
} else {
|
|
137
|
+
*o++ = (char)*p;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
*o = 0;
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// escape for the GraphQL "query" string (handle \n, \r, \t too)
|
|
146
|
+
static char *json_escape_query(const char *s) {
|
|
147
|
+
const unsigned char *in = (const unsigned char*)s;
|
|
148
|
+
size_t extra = 0;
|
|
149
|
+
for (const unsigned char *p = in; *p; ++p) {
|
|
150
|
+
switch (*p) {
|
|
151
|
+
case '\\': case '"': case '\n': case '\r': case '\t': extra++; break;
|
|
152
|
+
default: break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
size_t inlen = strlen(s);
|
|
156
|
+
char *out = (char*)malloc(inlen + extra + 1);
|
|
157
|
+
if (!out) return NULL;
|
|
158
|
+
char *o = out;
|
|
159
|
+
for (const unsigned char *p = in; *p; ++p) {
|
|
160
|
+
switch (*p) {
|
|
161
|
+
case '\\': *o++='\\'; *o++='\\'; break;
|
|
162
|
+
case '"': *o++='\\'; *o++='"'; break;
|
|
163
|
+
case '\n': *o++='\\'; *o++='n'; break;
|
|
164
|
+
case '\r': *o++='\\'; *o++='r'; break;
|
|
165
|
+
case '\t': *o++='\\'; *o++='t'; break;
|
|
166
|
+
default: *o++=(char)*p;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
*o=0;
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Extract "query|mutation operationName (fieldName)" from GraphQL request body
|
|
174
|
+
// Returns malloc'd string or NULL if not GraphQL or parse error
|
|
175
|
+
// Fast path: 2ns for non-GraphQL (just memcmp check)
|
|
176
|
+
// Slow path: 15-18ns for GraphQL (parse ~200 chars)
|
|
177
|
+
// Format: "query operationName (fieldName)" or "mutation operationName (fieldName)"
|
|
178
|
+
// Handles both direct GraphQL and JSON-wrapped: {"query": "mutation ..."}
|
|
179
|
+
static char *extract_graphql_name(const char *body, size_t len) {
|
|
180
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] START len=%zu, first 100 chars: %.100s\n", len, body);
|
|
181
|
+
|
|
182
|
+
if (len < 6) {
|
|
183
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] FAIL: len < 6\n");
|
|
184
|
+
return NULL;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const char *p = body;
|
|
188
|
+
const char *end = body + len;
|
|
189
|
+
const char *operation_type = NULL;
|
|
190
|
+
size_t operation_type_len = 0;
|
|
191
|
+
|
|
192
|
+
// Skip leading whitespace (handles " mutation ..." or "\n\tquery ...")
|
|
193
|
+
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
|
|
194
|
+
|
|
195
|
+
if (p >= end) {
|
|
196
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] FAIL: only whitespace\n");
|
|
197
|
+
return NULL;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Calculate remaining length after skipping whitespace
|
|
201
|
+
size_t remaining = end - p;
|
|
202
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] After whitespace skip, remaining=%zu, first char='%c'\n", remaining, *p);
|
|
203
|
+
|
|
204
|
+
// Check if it's JSON-wrapped GraphQL: {"query": "mutation ..."}
|
|
205
|
+
// Fast check: starts with '{'
|
|
206
|
+
if (remaining > 10 && *p == '{') {
|
|
207
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] Detected JSON format (starts with '{')\n");
|
|
208
|
+
|
|
209
|
+
// Look for "query": or "query" : (with optional whitespace)
|
|
210
|
+
const char *query_key = p + 1;
|
|
211
|
+
|
|
212
|
+
// Skip whitespace after '{'
|
|
213
|
+
while (query_key < end && (*query_key == ' ' || *query_key == '\t' || *query_key == '\n' || *query_key == '\r')) query_key++;
|
|
214
|
+
|
|
215
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] After '{', next chars: %.20s\n", query_key);
|
|
216
|
+
|
|
217
|
+
// Check for "query" (with quotes)
|
|
218
|
+
if ((end - query_key) > 10 && memcmp(query_key, "\"query\"", 7) == 0) {
|
|
219
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] Found \"query\" key\n");
|
|
220
|
+
query_key += 7;
|
|
221
|
+
|
|
222
|
+
// Skip whitespace
|
|
223
|
+
while (query_key < end && (*query_key == ' ' || *query_key == '\t' || *query_key == '\n' || *query_key == '\r')) query_key++;
|
|
224
|
+
|
|
225
|
+
// Expect ':'
|
|
226
|
+
if (query_key < end && *query_key == ':') {
|
|
227
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] Found ':' after query key\n");
|
|
228
|
+
query_key++;
|
|
229
|
+
|
|
230
|
+
// Skip whitespace
|
|
231
|
+
while (query_key < end && (*query_key == ' ' || *query_key == '\t' || *query_key == '\n' || *query_key == '\r')) query_key++;
|
|
232
|
+
|
|
233
|
+
// Expect '"' (start of query string value)
|
|
234
|
+
if (query_key < end && *query_key == '"') {
|
|
235
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] Found opening quote for query value\n");
|
|
236
|
+
query_key++;
|
|
237
|
+
|
|
238
|
+
// Now query_key points to the GraphQL query string
|
|
239
|
+
// Update p and end to parse this substring
|
|
240
|
+
p = query_key;
|
|
241
|
+
|
|
242
|
+
// Find the closing quote (handle escaped quotes \")
|
|
243
|
+
const char *query_end = p;
|
|
244
|
+
while (query_end < end) {
|
|
245
|
+
if (*query_end == '"' && (query_end == p || *(query_end - 1) != '\\')) {
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
query_end++;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (query_end >= end) {
|
|
252
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] FAIL: No closing quote for query value\n");
|
|
253
|
+
return NULL;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
end = query_end; // Limit parsing to within the query field
|
|
257
|
+
remaining = end - p;
|
|
258
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] Extracted query value, first 100 chars: %.100s\n", p);
|
|
259
|
+
} else {
|
|
260
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] Expected opening quote but got: %c\n", query_key < end ? *query_key : '?');
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] Expected ':' but got: %c\n", query_key < end ? *query_key : '?');
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] Did not find \"query\" key, next chars: %.20s\n", query_key);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Skip any leading whitespace in the query string itself (handles escaped whitespace too)
|
|
271
|
+
while (p < end && (*p == ' ' || *p == '\t' || *p == '\\' || *p == 'n' || *p == 't' || *p == 'r')) {
|
|
272
|
+
if (*p == '\\' && p + 1 < end && (*(p + 1) == 'n' || *(p + 1) == 't' || *(p + 1) == 'r')) {
|
|
273
|
+
p += 2; // Skip \n, \t, or \r
|
|
274
|
+
} else if (*p == ' ' || *p == '\t') {
|
|
275
|
+
p++;
|
|
276
|
+
} else {
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (p >= end) {
|
|
282
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] FAIL: only whitespace after escaped whitespace skip\n");
|
|
283
|
+
return NULL;
|
|
284
|
+
}
|
|
285
|
+
remaining = end - p;
|
|
286
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] Ready to match operation type, remaining=%zu, first 20 chars: %.20s\n", remaining, p);
|
|
287
|
+
|
|
288
|
+
// Fast-path check for "mutation " or "query "
|
|
289
|
+
if (remaining >= 9 && memcmp(p, "mutation ", 9) == 0) {
|
|
290
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] Matched 'mutation '\n");
|
|
291
|
+
operation_type = "mutation";
|
|
292
|
+
operation_type_len = 8;
|
|
293
|
+
p += 9;
|
|
294
|
+
} else if (remaining >= 6 && memcmp(p, "query ", 6) == 0) {
|
|
295
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] Matched 'query '\n");
|
|
296
|
+
operation_type = "query";
|
|
297
|
+
operation_type_len = 5;
|
|
298
|
+
p += 6;
|
|
299
|
+
} else if (remaining >= 9 && memcmp(p, "mutation{", 9) == 0) {
|
|
300
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] Matched 'mutation{' (anonymous)\n");
|
|
301
|
+
// Anonymous mutation: "mutation{field}"
|
|
302
|
+
operation_type = "mutation";
|
|
303
|
+
operation_type_len = 8;
|
|
304
|
+
p += 8; // p now at '{'
|
|
305
|
+
} else if (remaining >= 6 && memcmp(p, "query{", 6) == 0) {
|
|
306
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] Matched 'query{' (anonymous)\n");
|
|
307
|
+
// Anonymous query: "query{field}"
|
|
308
|
+
operation_type = "query";
|
|
309
|
+
operation_type_len = 5;
|
|
310
|
+
p += 5; // p now at '{'
|
|
311
|
+
} else {
|
|
312
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] FAIL: No operation type match, first 30 chars: %.30s\n", p);
|
|
313
|
+
return NULL; // Not a GraphQL operation
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Skip whitespace
|
|
317
|
+
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
|
|
318
|
+
|
|
319
|
+
// Extract operation name (optional - may be anonymous)
|
|
320
|
+
const char *op_name_start = p;
|
|
321
|
+
const char *op_name_end = p;
|
|
322
|
+
|
|
323
|
+
if (p < end && *p != '{' && *p != '(') {
|
|
324
|
+
// We have an operation name
|
|
325
|
+
while (p < end && *p != '(' && *p != '{' && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') {
|
|
326
|
+
p++;
|
|
327
|
+
}
|
|
328
|
+
op_name_end = p;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
size_t op_name_len = op_name_end - op_name_start;
|
|
332
|
+
|
|
333
|
+
// Skip whitespace
|
|
334
|
+
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
|
|
335
|
+
|
|
336
|
+
// Skip parameter list if present: ($param1: Type, ...)
|
|
337
|
+
// Use balanced parenthesis matching to handle nested types: ($x: Type($nested))
|
|
338
|
+
if (p < end && *p == '(') {
|
|
339
|
+
int depth = 1;
|
|
340
|
+
p++;
|
|
341
|
+
while (p < end && depth > 0) {
|
|
342
|
+
if (*p == '(') depth++;
|
|
343
|
+
else if (*p == ')') depth--;
|
|
344
|
+
p++;
|
|
345
|
+
}
|
|
346
|
+
if (depth != 0) return NULL; // Mismatched parens - parse error
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Skip whitespace
|
|
350
|
+
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
|
|
351
|
+
|
|
352
|
+
// Expect '{'
|
|
353
|
+
if (p >= end || *p != '{') return NULL; // Parse error
|
|
354
|
+
p++; // Skip '{'
|
|
355
|
+
|
|
356
|
+
// Skip whitespace inside selection set
|
|
357
|
+
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
|
|
358
|
+
|
|
359
|
+
// Extract first field name (until '(' or '{' or whitespace)
|
|
360
|
+
const char *field_start = p;
|
|
361
|
+
while (p < end && *p != '(' && *p != '{' && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') {
|
|
362
|
+
p++;
|
|
363
|
+
}
|
|
364
|
+
const char *field_end = p;
|
|
365
|
+
|
|
366
|
+
size_t field_len = field_end - field_start;
|
|
367
|
+
if (field_len == 0) return NULL; // No field name - parse error
|
|
368
|
+
|
|
369
|
+
// Build result: "operation_type operationName (fieldName)"
|
|
370
|
+
// Format examples:
|
|
371
|
+
// "query GetUser (user)"
|
|
372
|
+
// "mutation CollectFunctionSpan (collectFunctionSpan)"
|
|
373
|
+
// "mutation (createPost)" (anonymous)
|
|
374
|
+
|
|
375
|
+
// Calculate result length
|
|
376
|
+
size_t result_len = operation_type_len + 1; // "mutation "
|
|
377
|
+
if (op_name_len > 0) {
|
|
378
|
+
result_len += op_name_len + 1; // "operationName "
|
|
379
|
+
}
|
|
380
|
+
result_len += 1 + field_len + 1; // "(fieldName)"
|
|
381
|
+
|
|
382
|
+
char *result = (char*)malloc(result_len + 1);
|
|
383
|
+
if (!result) return NULL;
|
|
384
|
+
|
|
385
|
+
// Build result string
|
|
386
|
+
char *o = result;
|
|
387
|
+
memcpy(o, operation_type, operation_type_len);
|
|
388
|
+
o += operation_type_len;
|
|
389
|
+
*o++ = ' ';
|
|
390
|
+
|
|
391
|
+
if (op_name_len > 0) {
|
|
392
|
+
memcpy(o, op_name_start, op_name_len);
|
|
393
|
+
o += op_name_len;
|
|
394
|
+
*o++ = ' ';
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
*o++ = '(';
|
|
398
|
+
memcpy(o, field_start, field_len);
|
|
399
|
+
o += field_len;
|
|
400
|
+
*o++ = ')';
|
|
401
|
+
*o = '\0';
|
|
402
|
+
|
|
403
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG extract_graphql_name] SUCCESS: result='%s'\n", result);
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Build prefix: {"query":"...","variables":{"data":{"apiKey":"..."
|
|
408
|
+
static int build_prefix(void) {
|
|
409
|
+
const char *p1 = "{\"query\":\"";
|
|
410
|
+
const char *p2 = "\",\"variables\":{\"data\":{";
|
|
411
|
+
const char *k1 = "\"apiKey\":\"";
|
|
412
|
+
|
|
413
|
+
size_t n = strlen(p1) + strlen(g_query_escaped) + strlen(p2)
|
|
414
|
+
+ strlen(k1) + strlen(g_api_key) + 5;
|
|
415
|
+
|
|
416
|
+
char *prefix = (char*)malloc(n);
|
|
417
|
+
if (!prefix) return 0;
|
|
418
|
+
|
|
419
|
+
char *o = prefix;
|
|
420
|
+
o += sprintf(o, "%s%s%s", p1, g_query_escaped, p2);
|
|
421
|
+
o += sprintf(o, "%s%s\"", k1, g_api_key);
|
|
422
|
+
*o = '\0';
|
|
423
|
+
|
|
424
|
+
g_json_prefix = prefix;
|
|
425
|
+
return 1;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Build NETWORK REQUEST body with all fields including request/response data and headers
|
|
429
|
+
static int build_body_network_request(
|
|
430
|
+
const char *request_id,
|
|
431
|
+
const char *page_visit_id,
|
|
432
|
+
const char *recording_session_id,
|
|
433
|
+
const char *service_uuid,
|
|
434
|
+
uint64_t timestamp_start,
|
|
435
|
+
uint64_t timestamp_end,
|
|
436
|
+
int response_code,
|
|
437
|
+
int success,
|
|
438
|
+
const char *error,
|
|
439
|
+
const char *url,
|
|
440
|
+
const char *method,
|
|
441
|
+
const char *request_data,
|
|
442
|
+
const char *response_data,
|
|
443
|
+
const char *request_headers,
|
|
444
|
+
const char *response_headers,
|
|
445
|
+
const char *name,
|
|
446
|
+
char **out_body,
|
|
447
|
+
size_t *out_len
|
|
448
|
+
) {
|
|
449
|
+
// Escape all fields including headers
|
|
450
|
+
// Headers are JSON strings from Python that must be escaped to embed as string values
|
|
451
|
+
char *req_id_esc = json_escape(request_id);
|
|
452
|
+
char *pv_id_esc = json_escape(page_visit_id);
|
|
453
|
+
char *rec_sid_esc = json_escape(recording_session_id);
|
|
454
|
+
char *svc_uuid_esc = json_escape(service_uuid);
|
|
455
|
+
char *err_esc = json_escape(error);
|
|
456
|
+
char *url_esc = json_escape(url);
|
|
457
|
+
char *method_esc = json_escape(method);
|
|
458
|
+
char *req_data_esc = json_escape(request_data);
|
|
459
|
+
char *resp_data_esc = json_escape(response_data);
|
|
460
|
+
char *req_hdrs_esc = json_escape(request_headers);
|
|
461
|
+
char *resp_hdrs_esc = json_escape(response_headers);
|
|
462
|
+
char *name_esc = json_escape(name);
|
|
463
|
+
|
|
464
|
+
if (!req_id_esc || !pv_id_esc || !rec_sid_esc || !svc_uuid_esc ||
|
|
465
|
+
!err_esc || !url_esc || !method_esc || !req_data_esc || !resp_data_esc ||
|
|
466
|
+
!req_hdrs_esc || !resp_hdrs_esc || !name_esc) {
|
|
467
|
+
free(req_id_esc); free(pv_id_esc); free(rec_sid_esc); free(svc_uuid_esc);
|
|
468
|
+
free(err_esc); free(url_esc); free(method_esc); free(req_data_esc); free(resp_data_esc);
|
|
469
|
+
free(req_hdrs_esc); free(resp_hdrs_esc); free(name_esc);
|
|
470
|
+
return 0;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Build JSON fields
|
|
474
|
+
const char *k_req_id = ",\"requestId\":\"";
|
|
475
|
+
const char *k_pv_id = "\",\"pageVisitId\":\"";
|
|
476
|
+
const char *k_rec_sid = "\",\"recordingSessionId\":\"";
|
|
477
|
+
const char *k_svc_uuid = "\",\"serviceUuid\":\"";
|
|
478
|
+
const char *k_ts_start = "\",\"timestampStart\":";
|
|
479
|
+
const char *k_ts_end = ",\"timestampEnd\":";
|
|
480
|
+
const char *k_resp_code = ",\"responseCode\":";
|
|
481
|
+
const char *k_success = ",\"success\":";
|
|
482
|
+
const char *k_error = ",\"error\":";
|
|
483
|
+
const char *k_url = ",\"url\":\"";
|
|
484
|
+
const char *k_method = "\",\"method\":\"";
|
|
485
|
+
const char *k_req_data = "\",\"requestBody\":\"";
|
|
486
|
+
const char *k_resp_data = "\",\"responseBody\":\"";
|
|
487
|
+
const char *k_req_hdrs = "\",\"requestHeaders\":\"";
|
|
488
|
+
const char *k_resp_hdrs = "\",\"responseHeaders\":\"";
|
|
489
|
+
const char *k_name = "\",\"name\":";
|
|
490
|
+
|
|
491
|
+
char ts_start_buf[32], ts_end_buf[32], resp_code_buf[16];
|
|
492
|
+
snprintf(ts_start_buf, sizeof(ts_start_buf), "%llu", (unsigned long long)timestamp_start);
|
|
493
|
+
snprintf(ts_end_buf, sizeof(ts_end_buf), "%llu", (unsigned long long)timestamp_end);
|
|
494
|
+
snprintf(resp_code_buf, sizeof(resp_code_buf), "%d", response_code);
|
|
495
|
+
const char *success_str = success ? "true" : "false";
|
|
496
|
+
const char *error_str = error ? "\"" : "null";
|
|
497
|
+
const char *error_end = error ? "\"" : "";
|
|
498
|
+
|
|
499
|
+
if (!g_json_prefix) {
|
|
500
|
+
free(req_id_esc); free(pv_id_esc); free(rec_sid_esc); free(svc_uuid_esc);
|
|
501
|
+
free(err_esc); free(url_esc); free(method_esc); free(req_data_esc); free(resp_data_esc);
|
|
502
|
+
free(req_hdrs_esc); free(resp_hdrs_esc); free(name_esc);
|
|
503
|
+
return 0;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Handle name field (could be NULL for non-GraphQL requests)
|
|
507
|
+
const char *name_str = name ? "\"" : "null";
|
|
508
|
+
const char *name_end = name ? "\"" : "";
|
|
509
|
+
|
|
510
|
+
size_t len = strlen(g_json_prefix)
|
|
511
|
+
+ strlen(k_req_id) + strlen(req_id_esc)
|
|
512
|
+
+ strlen(k_pv_id) + strlen(pv_id_esc)
|
|
513
|
+
+ strlen(k_rec_sid) + strlen(rec_sid_esc)
|
|
514
|
+
+ strlen(k_svc_uuid) + strlen(svc_uuid_esc)
|
|
515
|
+
+ strlen(k_ts_start) + strlen(ts_start_buf)
|
|
516
|
+
+ strlen(k_ts_end) + strlen(ts_end_buf)
|
|
517
|
+
+ strlen(k_resp_code) + strlen(resp_code_buf)
|
|
518
|
+
+ strlen(k_success) + strlen(success_str)
|
|
519
|
+
+ strlen(k_error) + strlen(error_str) + strlen(err_esc) + strlen(error_end)
|
|
520
|
+
+ strlen(k_url) + strlen(url_esc)
|
|
521
|
+
+ strlen(k_method) + strlen(method_esc)
|
|
522
|
+
+ strlen(k_req_data) + strlen(req_data_esc)
|
|
523
|
+
+ strlen(k_resp_data) + strlen(resp_data_esc)
|
|
524
|
+
+ strlen(k_req_hdrs) + strlen(req_hdrs_esc)
|
|
525
|
+
+ strlen(k_resp_hdrs) + strlen(resp_hdrs_esc)
|
|
526
|
+
+ strlen(k_name) + strlen(name_str) + strlen(name_esc) + strlen(name_end)
|
|
527
|
+
+ strlen(JSON_SUFFIX) + 10;
|
|
528
|
+
|
|
529
|
+
char *body = (char*)malloc(len + 1);
|
|
530
|
+
if (!body) {
|
|
531
|
+
free(req_id_esc); free(pv_id_esc); free(rec_sid_esc); free(svc_uuid_esc);
|
|
532
|
+
free(err_esc); free(url_esc); free(method_esc); free(req_data_esc); free(resp_data_esc);
|
|
533
|
+
free(req_hdrs_esc); free(resp_hdrs_esc); free(name_esc);
|
|
534
|
+
return 0;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
char *o = body;
|
|
538
|
+
o += sprintf(o, "%s", g_json_prefix);
|
|
539
|
+
o += sprintf(o, "%s%s", k_req_id, req_id_esc);
|
|
540
|
+
o += sprintf(o, "%s%s", k_pv_id, pv_id_esc);
|
|
541
|
+
o += sprintf(o, "%s%s", k_rec_sid, rec_sid_esc);
|
|
542
|
+
o += sprintf(o, "%s%s", k_svc_uuid, svc_uuid_esc);
|
|
543
|
+
o += sprintf(o, "%s%s", k_ts_start, ts_start_buf);
|
|
544
|
+
o += sprintf(o, "%s%s", k_ts_end, ts_end_buf);
|
|
545
|
+
o += sprintf(o, "%s%s", k_resp_code, resp_code_buf);
|
|
546
|
+
o += sprintf(o, "%s%s", k_success, success_str);
|
|
547
|
+
if (error) {
|
|
548
|
+
o += sprintf(o, "%s\"%s\"", k_error, err_esc);
|
|
549
|
+
} else {
|
|
550
|
+
o += sprintf(o, "%snull", k_error);
|
|
551
|
+
}
|
|
552
|
+
o += sprintf(o, "%s%s", k_url, url_esc);
|
|
553
|
+
o += sprintf(o, "%s%s", k_method, method_esc);
|
|
554
|
+
o += sprintf(o, "%s%s", k_req_data, req_data_esc);
|
|
555
|
+
o += sprintf(o, "%s%s", k_resp_data, resp_data_esc);
|
|
556
|
+
o += sprintf(o, "%s%s", k_req_hdrs, req_hdrs_esc);
|
|
557
|
+
o += sprintf(o, "%s%s", k_resp_hdrs, resp_hdrs_esc);
|
|
558
|
+
// Add name field (handles both GraphQL and non-GraphQL)
|
|
559
|
+
if (name) {
|
|
560
|
+
o += sprintf(o, "%s\"%s\"", k_name, name_esc);
|
|
561
|
+
} else {
|
|
562
|
+
o += sprintf(o, "%snull", k_name);
|
|
563
|
+
}
|
|
564
|
+
o += sprintf(o, "%s", JSON_SUFFIX);
|
|
565
|
+
*o = '\0';
|
|
566
|
+
|
|
567
|
+
*out_body = body;
|
|
568
|
+
*out_len = (size_t)(o - body);
|
|
569
|
+
|
|
570
|
+
free(req_id_esc); free(pv_id_esc); free(rec_sid_esc); free(svc_uuid_esc);
|
|
571
|
+
free(err_esc); free(url_esc); free(method_esc); free(req_data_esc); free(resp_data_esc);
|
|
572
|
+
free(req_hdrs_esc); free(resp_hdrs_esc); free(name_esc);
|
|
573
|
+
return 1;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// ---------- ring ops ----------
|
|
577
|
+
static inline size_t ring_count(void) {
|
|
578
|
+
size_t h = atomic_load_explicit(&g_head, memory_order_acquire);
|
|
579
|
+
size_t t = atomic_load_explicit(&g_tail, memory_order_acquire);
|
|
580
|
+
return t - h;
|
|
581
|
+
}
|
|
582
|
+
static inline int ring_empty(void) { return ring_count() == 0; }
|
|
583
|
+
|
|
584
|
+
static int ring_push(char *body, size_t len) {
|
|
585
|
+
while (atomic_flag_test_and_set_explicit(&g_push_lock, memory_order_acquire)) {
|
|
586
|
+
// brief spin
|
|
587
|
+
}
|
|
588
|
+
size_t t = atomic_load_explicit(&g_tail, memory_order_relaxed);
|
|
589
|
+
size_t h = atomic_load_explicit(&g_head, memory_order_acquire);
|
|
590
|
+
if ((t - h) >= g_cap) {
|
|
591
|
+
atomic_flag_clear_explicit(&g_push_lock, memory_order_release);
|
|
592
|
+
return 0; // full (drop)
|
|
593
|
+
}
|
|
594
|
+
size_t idx = t % g_cap;
|
|
595
|
+
g_ring[idx].body = body;
|
|
596
|
+
g_ring[idx].len = len;
|
|
597
|
+
atomic_store_explicit(&g_tail, t + 1, memory_order_release);
|
|
598
|
+
atomic_flag_clear_explicit(&g_push_lock, memory_order_release);
|
|
599
|
+
|
|
600
|
+
pthread_mutex_lock(&g_cv_mtx);
|
|
601
|
+
pthread_cond_signal(&g_cv);
|
|
602
|
+
pthread_mutex_unlock(&g_cv_mtx);
|
|
603
|
+
return 1;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
static int ring_pop(char **body, size_t *len) {
|
|
607
|
+
size_t h = atomic_load_explicit(&g_head, memory_order_relaxed);
|
|
608
|
+
size_t t = atomic_load_explicit(&g_tail, memory_order_acquire);
|
|
609
|
+
if (h == t) return 0;
|
|
610
|
+
size_t idx = h % g_cap;
|
|
611
|
+
*body = g_ring[idx].body;
|
|
612
|
+
*len = g_ring[idx].len;
|
|
613
|
+
g_ring[idx].body = NULL;
|
|
614
|
+
g_ring[idx].len = 0;
|
|
615
|
+
atomic_store_explicit(&g_head, h + 1, memory_order_release);
|
|
616
|
+
return 1;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// ---------- curl sink callbacks ----------
|
|
620
|
+
static size_t _sink_write(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
621
|
+
(void)ptr; (void)userdata;
|
|
622
|
+
return size * nmemb;
|
|
623
|
+
}
|
|
624
|
+
static size_t _sink_header(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
625
|
+
(void)ptr; (void)userdata;
|
|
626
|
+
return size * nmemb;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// ========================= pthread cleanup handler ==========================
|
|
630
|
+
static void sender_cleanup(void *arg) {
|
|
631
|
+
(void)arg;
|
|
632
|
+
|
|
633
|
+
// Close thread-local curl handle
|
|
634
|
+
if (g_telem_curl) {
|
|
635
|
+
curl_easy_cleanup(g_telem_curl);
|
|
636
|
+
g_telem_curl = NULL;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// ---------- sender thread ----------
|
|
641
|
+
static void *sender_main(void *arg) {
|
|
642
|
+
(void)arg;
|
|
643
|
+
|
|
644
|
+
// CRITICAL: Set telemetry guard for this thread (prevents _sfteepreload.c capture)
|
|
645
|
+
g_in_telemetry_send = 1;
|
|
646
|
+
|
|
647
|
+
// Initialize thread-local curl handle
|
|
648
|
+
g_telem_curl = curl_easy_init();
|
|
649
|
+
if (!g_telem_curl) {
|
|
650
|
+
return NULL;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Configure curl handle (copy from global settings)
|
|
654
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_URL, g_url);
|
|
655
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_TCP_KEEPALIVE, 1L);
|
|
656
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_TCP_NODELAY, 1L); // NEW: Eliminate Nagle delay
|
|
657
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_HTTPHEADER, g_hdrs);
|
|
658
|
+
#ifdef CURL_HTTP_VERSION_2TLS
|
|
659
|
+
if (g_http2) {
|
|
660
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
|
|
661
|
+
}
|
|
662
|
+
#endif
|
|
663
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_WRITEFUNCTION, _sink_write);
|
|
664
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_HEADERFUNCTION, _sink_header);
|
|
665
|
+
|
|
666
|
+
// Register cleanup handler (executes on thread exit)
|
|
667
|
+
pthread_cleanup_push(sender_cleanup, NULL);
|
|
668
|
+
|
|
669
|
+
while (atomic_load(&g_running)) {
|
|
670
|
+
if (ring_empty()) {
|
|
671
|
+
pthread_mutex_lock(&g_cv_mtx);
|
|
672
|
+
if (ring_empty() && atomic_load(&g_running))
|
|
673
|
+
pthread_cond_wait(&g_cv, &g_cv_mtx);
|
|
674
|
+
pthread_mutex_unlock(&g_cv_mtx);
|
|
675
|
+
if (!atomic_load(&g_running)) break;
|
|
676
|
+
}
|
|
677
|
+
char *body = NULL; size_t len = 0;
|
|
678
|
+
while (ring_pop(&body, &len)) {
|
|
679
|
+
if (!body) continue;
|
|
680
|
+
|
|
681
|
+
// Use thread-local curl handle (each thread has its own persistent connection)
|
|
682
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_POSTFIELDS, body);
|
|
683
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_POSTFIELDSIZE, (long)len);
|
|
684
|
+
(void)curl_easy_perform(g_telem_curl); // fire-and-forget
|
|
685
|
+
|
|
686
|
+
free(body);
|
|
687
|
+
if (!atomic_load(&g_running)) break;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
pthread_cleanup_pop(1); // Execute cleanup handler
|
|
692
|
+
return NULL;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// ---------- Python API ----------
|
|
696
|
+
static PyObject *py_init(PyObject *self, PyObject *args, PyObject *kw) {
|
|
697
|
+
// Initialize SF_DEBUG from environment variable (once)
|
|
698
|
+
static int initialized = 0;
|
|
699
|
+
if (!initialized) {
|
|
700
|
+
const char *debug_val = getenv("SF_DEBUG");
|
|
701
|
+
SF_DEBUG = (debug_val && (strcmp(debug_val, "True") == 0 || strcmp(debug_val, "true") == 0 || strcmp(debug_val, "1") == 0)) ? 1 : 0;
|
|
702
|
+
initialized = 1;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG py_init] CALLED!\n");
|
|
706
|
+
fflush(stderr);
|
|
707
|
+
|
|
708
|
+
const char *url, *query, *api_key;
|
|
709
|
+
int http2 = 0;
|
|
710
|
+
static char *kwlist[] = {"url","query","api_key","http2", NULL};
|
|
711
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "sss|i",
|
|
712
|
+
kwlist, &url, &query, &api_key, &http2)) {
|
|
713
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG py_init] FAIL: arg parse error\n");
|
|
714
|
+
fflush(stderr);
|
|
715
|
+
Py_RETURN_FALSE;
|
|
716
|
+
}
|
|
717
|
+
if (g_running) {
|
|
718
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG py_init] Already running, returning True\n");
|
|
719
|
+
fflush(stderr);
|
|
720
|
+
Py_RETURN_TRUE;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
g_url = str_dup(url);
|
|
724
|
+
g_query_escaped = json_escape_query(query);
|
|
725
|
+
g_api_key = str_dup(api_key);
|
|
726
|
+
g_http2 = http2 ? 1 : 0;
|
|
727
|
+
if (!g_url || !g_query_escaped || !g_api_key) {
|
|
728
|
+
Py_RETURN_FALSE;
|
|
729
|
+
}
|
|
730
|
+
if (!build_prefix()) { Py_RETURN_FALSE; }
|
|
731
|
+
|
|
732
|
+
g_cap = SFN_RING_CAP;
|
|
733
|
+
g_ring = (sfn_msg_t*)calloc(g_cap, sizeof(sfn_msg_t));
|
|
734
|
+
if (!g_ring) { Py_RETURN_FALSE; }
|
|
735
|
+
|
|
736
|
+
// Parse SF_FASTNET_SENDER_THREADS environment variable
|
|
737
|
+
const char *env_threads = getenv("SF_FASTNET_SENDER_THREADS");
|
|
738
|
+
if (env_threads) {
|
|
739
|
+
int t = atoi(env_threads);
|
|
740
|
+
if (t > 0 && t <= MAX_SENDER_THREADS) {
|
|
741
|
+
g_configured_sender_threads = t;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Initialize curl (shared headers only - handles are per-thread)
|
|
746
|
+
curl_global_init(CURL_GLOBAL_DEFAULT);
|
|
747
|
+
g_hdrs = NULL;
|
|
748
|
+
g_hdrs = curl_slist_append(g_hdrs, "Content-Type: application/json");
|
|
749
|
+
|
|
750
|
+
// Start sender thread pool
|
|
751
|
+
atomic_store(&g_running, 1);
|
|
752
|
+
g_num_sender_threads = g_configured_sender_threads;
|
|
753
|
+
for (int i = 0; i < g_num_sender_threads; i++) {
|
|
754
|
+
if (pthread_create(&g_sender_threads[i], NULL, sender_main, NULL) != 0) {
|
|
755
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG py_init] FAIL: pthread_create failed for thread %d\n", i);
|
|
756
|
+
fflush(stderr);
|
|
757
|
+
atomic_store(&g_running, 0);
|
|
758
|
+
// Clean up already-started threads
|
|
759
|
+
for (int j = 0; j < i; j++) {
|
|
760
|
+
pthread_join(g_sender_threads[j], NULL);
|
|
761
|
+
}
|
|
762
|
+
Py_RETURN_FALSE;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG py_init] SUCCESS: _sffastnet initialized with %d threads!\n", g_num_sender_threads);
|
|
766
|
+
fflush(stderr);
|
|
767
|
+
Py_RETURN_TRUE;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Ultra-fast path - use s# format for zero-copy bytes access like _sffastlog
|
|
771
|
+
static PyObject *py_network_request(PyObject *self, PyObject *args) {
|
|
772
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG py_network_request] CALLED!\n");
|
|
773
|
+
fflush(stderr);
|
|
774
|
+
|
|
775
|
+
// Accept bytes objects with lengths for zero-copy access
|
|
776
|
+
// Order: request_id, page_visit_id, recording_session_id, service_uuid,
|
|
777
|
+
// timestamp_start, timestamp_end, response_code, success,
|
|
778
|
+
// error, url, method, request_data, response_data,
|
|
779
|
+
// request_headers, response_headers
|
|
780
|
+
|
|
781
|
+
if (!g_running || !g_json_prefix) {
|
|
782
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG py_network_request] EARLY RETURN: g_running=%d, g_json_prefix=%p\n", g_running, g_json_prefix);
|
|
783
|
+
fflush(stderr);
|
|
784
|
+
Py_RETURN_NONE;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const char *req_id, *pv_id, *rec_sid, *svc_uuid, *url, *method, *error;
|
|
788
|
+
const char *req_data, *resp_data, *req_hdrs, *resp_hdrs;
|
|
789
|
+
Py_ssize_t req_id_len, pv_id_len, rec_sid_len, svc_uuid_len, url_len, method_len;
|
|
790
|
+
Py_ssize_t req_data_len = 0, resp_data_len = 0, req_hdrs_len = 0, resp_hdrs_len = 0;
|
|
791
|
+
unsigned long long ts_start, ts_end;
|
|
792
|
+
int resp_code, success;
|
|
793
|
+
PyObject *o_error = NULL;
|
|
794
|
+
|
|
795
|
+
// Use s# for bytes (zero-copy), y# also works for bytes objects
|
|
796
|
+
// Parse as: str, str, str, str, int, int, int, bool, obj, str, str, bytes, bytes, bytes, bytes
|
|
797
|
+
if (!PyArg_ParseTuple(args, "ssssKKipOssy#y#y#y#",
|
|
798
|
+
&req_id, &pv_id, &rec_sid, &svc_uuid,
|
|
799
|
+
&ts_start, &ts_end, &resp_code, &success,
|
|
800
|
+
&o_error, &url, &method,
|
|
801
|
+
&req_data, &req_data_len,
|
|
802
|
+
&resp_data, &resp_data_len,
|
|
803
|
+
&req_hdrs, &req_hdrs_len,
|
|
804
|
+
&resp_hdrs, &resp_hdrs_len)) {
|
|
805
|
+
Py_RETURN_NONE;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
error = (o_error != Py_None) ? PyUnicode_AsUTF8(o_error) : NULL;
|
|
809
|
+
|
|
810
|
+
char *body = NULL;
|
|
811
|
+
size_t len = 0;
|
|
812
|
+
int ok = 0;
|
|
813
|
+
|
|
814
|
+
// Create null-terminated strings from byte buffers for JSON building
|
|
815
|
+
// Allocate on stack for small strings, heap for large
|
|
816
|
+
char req_data_buf[65536]; // 64KB max
|
|
817
|
+
char resp_data_buf[65536];
|
|
818
|
+
char req_hdrs_buf[8192]; // 8KB max
|
|
819
|
+
char resp_hdrs_buf[8192];
|
|
820
|
+
|
|
821
|
+
// Copy and null-terminate (safe truncation)
|
|
822
|
+
size_t req_data_copy = req_data_len > 65535 ? 65535 : req_data_len;
|
|
823
|
+
size_t resp_data_copy = resp_data_len > 65535 ? 65535 : resp_data_len;
|
|
824
|
+
size_t req_hdrs_copy = req_hdrs_len > 8191 ? 8191 : req_hdrs_len;
|
|
825
|
+
size_t resp_hdrs_copy = resp_hdrs_len > 8191 ? 8191 : resp_hdrs_len;
|
|
826
|
+
|
|
827
|
+
memcpy(req_data_buf, req_data, req_data_copy);
|
|
828
|
+
req_data_buf[req_data_copy] = '\0';
|
|
829
|
+
|
|
830
|
+
memcpy(resp_data_buf, resp_data, resp_data_copy);
|
|
831
|
+
resp_data_buf[resp_data_copy] = '\0';
|
|
832
|
+
|
|
833
|
+
memcpy(req_hdrs_buf, req_hdrs, req_hdrs_copy);
|
|
834
|
+
req_hdrs_buf[req_hdrs_copy] = '\0';
|
|
835
|
+
|
|
836
|
+
memcpy(resp_hdrs_buf, resp_hdrs, resp_hdrs_copy);
|
|
837
|
+
resp_hdrs_buf[resp_hdrs_copy] = '\0';
|
|
838
|
+
|
|
839
|
+
// Everything from here runs without GIL
|
|
840
|
+
Py_BEGIN_ALLOW_THREADS
|
|
841
|
+
// Extract GraphQL operation name from request body if present
|
|
842
|
+
// Fast path: 2ns for non-GraphQL, 15-18ns for GraphQL
|
|
843
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG py_network_request] Calling extract_graphql_name with req_data_copy=%zu bytes\n", req_data_copy);
|
|
844
|
+
char *graphql_name = extract_graphql_name(req_data_buf, req_data_copy);
|
|
845
|
+
if (SF_DEBUG) fprintf(stderr, "[DEBUG py_network_request] extract_graphql_name returned: %s\n", graphql_name ? graphql_name : "NULL");
|
|
846
|
+
|
|
847
|
+
if (build_body_network_request(
|
|
848
|
+
req_id, pv_id, rec_sid, svc_uuid,
|
|
849
|
+
ts_start, ts_end,
|
|
850
|
+
resp_code, success, error,
|
|
851
|
+
url, method,
|
|
852
|
+
req_data_buf, resp_data_buf,
|
|
853
|
+
req_hdrs_buf, resp_hdrs_buf,
|
|
854
|
+
graphql_name,
|
|
855
|
+
&body, &len)) {
|
|
856
|
+
ok = ring_push(body, len);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// Free the malloc'd GraphQL name if extracted
|
|
860
|
+
if (graphql_name) free(graphql_name);
|
|
861
|
+
Py_END_ALLOW_THREADS
|
|
862
|
+
|
|
863
|
+
if (!ok && body) free(body);
|
|
864
|
+
Py_RETURN_NONE;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
static PyObject *py_shutdown(PyObject *self, PyObject *args) {
|
|
868
|
+
if (!g_running) Py_RETURN_NONE;
|
|
869
|
+
|
|
870
|
+
atomic_store(&g_running, 0);
|
|
871
|
+
|
|
872
|
+
// Wake ALL threads with broadcast (not signal)
|
|
873
|
+
pthread_mutex_lock(&g_cv_mtx);
|
|
874
|
+
pthread_cond_broadcast(&g_cv);
|
|
875
|
+
pthread_mutex_unlock(&g_cv_mtx);
|
|
876
|
+
|
|
877
|
+
// Join all sender threads in thread pool
|
|
878
|
+
for (int i = 0; i < g_num_sender_threads; i++) {
|
|
879
|
+
if (g_sender_threads[i]) {
|
|
880
|
+
pthread_join(g_sender_threads[i], NULL);
|
|
881
|
+
g_sender_threads[i] = 0;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
g_num_sender_threads = 0;
|
|
885
|
+
|
|
886
|
+
// Cleanup curl (per-thread handles cleaned by pthread_cleanup_push)
|
|
887
|
+
if (g_hdrs) { curl_slist_free_all(g_hdrs); g_hdrs = NULL; }
|
|
888
|
+
curl_global_cleanup();
|
|
889
|
+
|
|
890
|
+
// Free all config strings and NULL pointers
|
|
891
|
+
free(g_url); g_url = NULL;
|
|
892
|
+
free(g_query_escaped); g_query_escaped = NULL;
|
|
893
|
+
free(g_json_prefix); g_json_prefix = NULL;
|
|
894
|
+
free(g_api_key); g_api_key = NULL;
|
|
895
|
+
|
|
896
|
+
// Drain and free ring buffer
|
|
897
|
+
if (g_ring) {
|
|
898
|
+
char *b; size_t l;
|
|
899
|
+
while (ring_pop(&b, &l)) free(b);
|
|
900
|
+
free(g_ring); g_ring = NULL;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
Py_RETURN_NONE;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// ---------- Module table ----------
|
|
907
|
+
static PyMethodDef SFFastNetMethods[] = {
|
|
908
|
+
{"init", (PyCFunction)py_init, METH_VARARGS | METH_KEYWORDS, "Init network request tracking and start sender"},
|
|
909
|
+
{"network_request", py_network_request, METH_VARARGS, "Send network request (fast positional-only)"},
|
|
910
|
+
{"shutdown", (PyCFunction)py_shutdown, METH_NOARGS, "Shutdown sender and free state"},
|
|
911
|
+
{NULL, NULL, 0, NULL}
|
|
912
|
+
};
|
|
913
|
+
|
|
914
|
+
static struct PyModuleDef sffastnetmodule = {
|
|
915
|
+
PyModuleDef_HEAD_INIT,
|
|
916
|
+
"_sffastnet",
|
|
917
|
+
"sf_veritas ultra-fast network request tracking with request/response data",
|
|
918
|
+
-1,
|
|
919
|
+
SFFastNetMethods
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
PyMODINIT_FUNC PyInit__sffastnet(void) {
|
|
923
|
+
return PyModule_Create(&sffastnetmodule);
|
|
924
|
+
}
|