sf-veritas 0.11.10__cp314-cp314-manylinux_2_28_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sf_veritas/__init__.py +46 -0
- sf_veritas/_auto_preload.py +73 -0
- sf_veritas/_sfconfig.c +162 -0
- sf_veritas/_sfconfig.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfcrashhandler.c +267 -0
- sf_veritas/_sfcrashhandler.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastlog.c +953 -0
- sf_veritas/_sffastlog.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnet.c +994 -0
- sf_veritas/_sffastnet.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnetworkrequest.c +727 -0
- sf_veritas/_sffastnetworkrequest.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan.c +2791 -0
- sf_veritas/_sffuncspan.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan_config.c +730 -0
- sf_veritas/_sffuncspan_config.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfheadercheck.c +341 -0
- sf_veritas/_sfheadercheck.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfnetworkhop.c +1454 -0
- sf_veritas/_sfnetworkhop.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfservice.c +1223 -0
- sf_veritas/_sfservice.cpython-314-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfteepreload.c +6227 -0
- sf_veritas/app_config.py +57 -0
- sf_veritas/cli.py +336 -0
- sf_veritas/constants.py +10 -0
- sf_veritas/custom_excepthook.py +304 -0
- sf_veritas/custom_log_handler.py +146 -0
- sf_veritas/custom_output_wrapper.py +153 -0
- sf_veritas/custom_print.py +153 -0
- sf_veritas/django_app.py +5 -0
- sf_veritas/env_vars.py +186 -0
- sf_veritas/exception_handling_middleware.py +18 -0
- sf_veritas/exception_metaclass.py +69 -0
- sf_veritas/fast_frame_info.py +116 -0
- sf_veritas/fast_network_hop.py +293 -0
- sf_veritas/frame_tools.py +112 -0
- sf_veritas/funcspan_config_loader.py +693 -0
- sf_veritas/function_span_profiler.py +1313 -0
- sf_veritas/get_preload_path.py +34 -0
- sf_veritas/import_hook.py +62 -0
- sf_veritas/infra_details/__init__.py +3 -0
- sf_veritas/infra_details/get_infra_details.py +24 -0
- sf_veritas/infra_details/kubernetes/__init__.py +3 -0
- sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
- sf_veritas/infra_details/kubernetes/get_details.py +7 -0
- sf_veritas/infra_details/running_on/__init__.py +17 -0
- sf_veritas/infra_details/running_on/kubernetes.py +11 -0
- sf_veritas/interceptors.py +543 -0
- sf_veritas/libsfnettee.so +0 -0
- sf_veritas/local_env_detect.py +118 -0
- sf_veritas/package_metadata.py +6 -0
- sf_veritas/patches/__init__.py +0 -0
- sf_veritas/patches/_patch_tracker.py +74 -0
- sf_veritas/patches/concurrent_futures.py +19 -0
- sf_veritas/patches/constants.py +1 -0
- sf_veritas/patches/exceptions.py +82 -0
- sf_veritas/patches/multiprocessing.py +32 -0
- sf_veritas/patches/network_libraries/__init__.py +99 -0
- sf_veritas/patches/network_libraries/aiohttp.py +294 -0
- sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
- sf_veritas/patches/network_libraries/http_client.py +670 -0
- sf_veritas/patches/network_libraries/httpcore.py +580 -0
- sf_veritas/patches/network_libraries/httplib2.py +315 -0
- sf_veritas/patches/network_libraries/httpx.py +557 -0
- sf_veritas/patches/network_libraries/niquests.py +218 -0
- sf_veritas/patches/network_libraries/pycurl.py +399 -0
- sf_veritas/patches/network_libraries/requests.py +595 -0
- sf_veritas/patches/network_libraries/ssl_socket.py +822 -0
- sf_veritas/patches/network_libraries/tornado.py +360 -0
- sf_veritas/patches/network_libraries/treq.py +270 -0
- sf_veritas/patches/network_libraries/urllib_request.py +483 -0
- sf_veritas/patches/network_libraries/utils.py +598 -0
- sf_veritas/patches/os.py +17 -0
- sf_veritas/patches/threading.py +231 -0
- sf_veritas/patches/web_frameworks/__init__.py +54 -0
- sf_veritas/patches/web_frameworks/aiohttp.py +798 -0
- sf_veritas/patches/web_frameworks/async_websocket_consumer.py +337 -0
- sf_veritas/patches/web_frameworks/blacksheep.py +532 -0
- sf_veritas/patches/web_frameworks/bottle.py +513 -0
- sf_veritas/patches/web_frameworks/cherrypy.py +683 -0
- sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
- sf_veritas/patches/web_frameworks/django.py +963 -0
- sf_veritas/patches/web_frameworks/eve.py +401 -0
- sf_veritas/patches/web_frameworks/falcon.py +931 -0
- sf_veritas/patches/web_frameworks/fastapi.py +738 -0
- sf_veritas/patches/web_frameworks/flask.py +526 -0
- sf_veritas/patches/web_frameworks/klein.py +501 -0
- sf_veritas/patches/web_frameworks/litestar.py +616 -0
- sf_veritas/patches/web_frameworks/pyramid.py +440 -0
- sf_veritas/patches/web_frameworks/quart.py +841 -0
- sf_veritas/patches/web_frameworks/robyn.py +708 -0
- sf_veritas/patches/web_frameworks/sanic.py +874 -0
- sf_veritas/patches/web_frameworks/starlette.py +742 -0
- sf_veritas/patches/web_frameworks/strawberry.py +1446 -0
- sf_veritas/patches/web_frameworks/tornado.py +485 -0
- sf_veritas/patches/web_frameworks/utils.py +170 -0
- sf_veritas/print_override.py +13 -0
- sf_veritas/regular_data_transmitter.py +444 -0
- sf_veritas/request_interceptor.py +401 -0
- sf_veritas/request_utils.py +550 -0
- sf_veritas/segfault_handler.py +116 -0
- sf_veritas/server_status.py +1 -0
- sf_veritas/shutdown_flag.py +11 -0
- sf_veritas/subprocess_startup.py +3 -0
- sf_veritas/test_cli.py +145 -0
- sf_veritas/thread_local.py +1319 -0
- sf_veritas/timeutil.py +114 -0
- sf_veritas/transmit_exception_to_sailfish.py +28 -0
- sf_veritas/transmitter.py +132 -0
- sf_veritas/types.py +47 -0
- sf_veritas/unified_interceptor.py +1678 -0
- sf_veritas/utils.py +39 -0
- sf_veritas-0.11.10.dist-info/METADATA +97 -0
- sf_veritas-0.11.10.dist-info/RECORD +141 -0
- sf_veritas-0.11.10.dist-info/WHEEL +5 -0
- sf_veritas-0.11.10.dist-info/entry_points.txt +2 -0
- sf_veritas-0.11.10.dist-info/top_level.txt +1 -0
- sf_veritas.libs/libbrotlicommon-6ce2a53c.so.1.0.6 +0 -0
- sf_veritas.libs/libbrotlidec-811d1be3.so.1.0.6 +0 -0
- sf_veritas.libs/libcom_err-730ca923.so.2.1 +0 -0
- sf_veritas.libs/libcrypt-52aca757.so.1.1.0 +0 -0
- sf_veritas.libs/libcrypto-bdaed0ea.so.1.1.1k +0 -0
- sf_veritas.libs/libcurl-eaa3cf66.so.4.5.0 +0 -0
- sf_veritas.libs/libgssapi_krb5-323bbd21.so.2.2 +0 -0
- sf_veritas.libs/libidn2-2f4a5893.so.0.3.6 +0 -0
- sf_veritas.libs/libk5crypto-9a74ff38.so.3.1 +0 -0
- sf_veritas.libs/libkeyutils-2777d33d.so.1.6 +0 -0
- sf_veritas.libs/libkrb5-a55300e8.so.3.3 +0 -0
- sf_veritas.libs/libkrb5support-e6594cfc.so.0.1 +0 -0
- sf_veritas.libs/liblber-2-d20824ef.4.so.2.10.9 +0 -0
- sf_veritas.libs/libldap-2-cea2a960.4.so.2.10.9 +0 -0
- sf_veritas.libs/libnghttp2-39367a22.so.14.17.0 +0 -0
- sf_veritas.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
- sf_veritas.libs/libpsl-99becdd3.so.5.3.1 +0 -0
- sf_veritas.libs/libsasl2-7de4d792.so.3.0.0 +0 -0
- sf_veritas.libs/libselinux-d0805dcb.so.1 +0 -0
- sf_veritas.libs/libssh-c11d285b.so.4.8.7 +0 -0
- sf_veritas.libs/libssl-60250281.so.1.1.1k +0 -0
- sf_veritas.libs/libunistring-05abdd40.so.2.1.0 +0 -0
- sf_veritas.libs/libuuid-95b83d40.so.1.3.0 +0 -0
sf_veritas/_sfservice.c
ADDED
|
@@ -0,0 +1,1223 @@
|
|
|
1
|
+
// sf_veritas/_sfservice.c
|
|
2
|
+
#define PY_SSIZE_T_CLEAN
|
|
3
|
+
#include <Python.h>
|
|
4
|
+
#include <pthread.h>
|
|
5
|
+
#include <curl/curl.h>
|
|
6
|
+
#include <stdatomic.h>
|
|
7
|
+
#include <stdint.h>
|
|
8
|
+
#include <stdlib.h>
|
|
9
|
+
#include <string.h>
|
|
10
|
+
#include <time.h>
|
|
11
|
+
#include <sys/time.h>
|
|
12
|
+
#include "sf_tls.h"
|
|
13
|
+
extern void sf_guard_enter(void);
|
|
14
|
+
extern void sf_guard_leave(void);
|
|
15
|
+
extern int sf_guard_active(void);
|
|
16
|
+
|
|
17
|
+
// ===================== Thread-local guard flag ====================
|
|
18
|
+
// CRITICAL: Prevents _sfteepreload.c from capturing our telemetry traffic
|
|
19
|
+
__attribute__((visibility("default")))
|
|
20
|
+
|
|
21
|
+
// ---------- Ring buffer ----------
|
|
22
|
+
#ifndef SFS_RING_CAP
|
|
23
|
+
#define SFS_RING_CAP 256 // Smaller capacity for lower-load service operations
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
typedef struct {
|
|
27
|
+
char *body; // malloc'd HTTP JSON body
|
|
28
|
+
size_t len;
|
|
29
|
+
} sfs_msg_t;
|
|
30
|
+
|
|
31
|
+
static sfs_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
|
+
// wake/sleep
|
|
40
|
+
static pthread_mutex_t g_cv_mtx = PTHREAD_MUTEX_INITIALIZER;
|
|
41
|
+
static pthread_cond_t g_cv = PTHREAD_COND_INITIALIZER;
|
|
42
|
+
static _Atomic int g_running = 0;
|
|
43
|
+
|
|
44
|
+
// Thread pool for parallel sending (configurable via SF_SERVICE_SENDER_THREADS)
|
|
45
|
+
#define MAX_SENDER_THREADS 16
|
|
46
|
+
static pthread_t g_sender_threads[MAX_SENDER_THREADS];
|
|
47
|
+
static int g_num_sender_threads = 0;
|
|
48
|
+
static int g_configured_sender_threads = 1; // Default: 1 thread
|
|
49
|
+
|
|
50
|
+
// curl state (per-thread handles + shared headers)
|
|
51
|
+
__thread CURL *g_telem_curl = NULL;
|
|
52
|
+
static struct curl_slist *g_hdrs = NULL;
|
|
53
|
+
|
|
54
|
+
// config (owned strings)
|
|
55
|
+
static char *g_url = NULL;
|
|
56
|
+
|
|
57
|
+
static char *g_api_key = NULL;
|
|
58
|
+
static char *g_service_uuid = NULL;
|
|
59
|
+
static char *g_library = NULL;
|
|
60
|
+
static char *g_version = NULL;
|
|
61
|
+
static int g_http2 = 0;
|
|
62
|
+
|
|
63
|
+
// --- SERVICE IDENTIFIER channel state ---
|
|
64
|
+
static char *g_service_identifier_query_escaped = NULL;
|
|
65
|
+
static char *g_json_prefix_service_identifier = NULL;
|
|
66
|
+
|
|
67
|
+
// --- DOMAINS channel state ---
|
|
68
|
+
static char *g_domains_query_escaped = NULL;
|
|
69
|
+
static char *g_json_prefix_domains = NULL;
|
|
70
|
+
|
|
71
|
+
// --- UPDATE SERVICE DETAILS channel state ---
|
|
72
|
+
static char *g_update_service_query_escaped = NULL;
|
|
73
|
+
static char *g_json_prefix_update_service = NULL;
|
|
74
|
+
|
|
75
|
+
// --- COLLECT METADATA channel state ---
|
|
76
|
+
static char *g_collect_metadata_query_escaped = NULL;
|
|
77
|
+
static char *g_json_prefix_collect_metadata = NULL;
|
|
78
|
+
|
|
79
|
+
static const char *JSON_SUFFIX = "}}";
|
|
80
|
+
|
|
81
|
+
// ---------- helpers ----------
|
|
82
|
+
static inline uint64_t now_ms(void) {
|
|
83
|
+
#if defined(CLOCK_REALTIME_COARSE)
|
|
84
|
+
struct timespec ts;
|
|
85
|
+
clock_gettime(CLOCK_REALTIME_COARSE, &ts);
|
|
86
|
+
return ((uint64_t)ts.tv_sec) * 1000ULL + (uint64_t)(ts.tv_nsec / 1000000ULL);
|
|
87
|
+
#else
|
|
88
|
+
struct timeval tv;
|
|
89
|
+
gettimeofday(&tv, NULL);
|
|
90
|
+
return ((uint64_t)tv.tv_sec) * 1000ULL + (uint64_t)(tv.tv_usec / 1000ULL);
|
|
91
|
+
#endif
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static char *str_dup(const char *s) {
|
|
95
|
+
size_t n = strlen(s);
|
|
96
|
+
char *p = (char*)malloc(n + 1);
|
|
97
|
+
if (!p) return NULL;
|
|
98
|
+
memcpy(p, s, n);
|
|
99
|
+
p[n] = 0;
|
|
100
|
+
return p;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// escape for generic JSON string fields
|
|
104
|
+
static char *json_escape(const char *s) {
|
|
105
|
+
const unsigned char *in = (const unsigned char*)s;
|
|
106
|
+
size_t extra = 0;
|
|
107
|
+
for (const unsigned char *p = in; *p; ++p) {
|
|
108
|
+
switch (*p) {
|
|
109
|
+
case '\\': case '"': extra++; break;
|
|
110
|
+
default:
|
|
111
|
+
if (*p < 0x20) extra += 5; // \u00XX
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
size_t inlen = strlen(s);
|
|
115
|
+
char *out = (char*)malloc(inlen + extra + 1);
|
|
116
|
+
if (!out) return NULL;
|
|
117
|
+
|
|
118
|
+
char *o = out;
|
|
119
|
+
for (const unsigned char *p = in; *p; ++p) {
|
|
120
|
+
switch (*p) {
|
|
121
|
+
case '\\': *o++='\\'; *o++='\\'; break;
|
|
122
|
+
case '"': *o++='\\'; *o++='"'; break;
|
|
123
|
+
default:
|
|
124
|
+
if (*p < 0x20) {
|
|
125
|
+
static const char hex[] = "0123456789abcdef";
|
|
126
|
+
*o++='\\'; *o++='u'; *o++='0'; *o++='0';
|
|
127
|
+
*o++=hex[(*p)>>4]; *o++=hex[(*p)&0xF];
|
|
128
|
+
} else {
|
|
129
|
+
*o++ = (char)*p;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
*o = 0;
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// escape for the GraphQL "query" string (handle \n, \r, \t too)
|
|
138
|
+
static char *json_escape_query(const char *s) {
|
|
139
|
+
const unsigned char *in = (const unsigned char*)s;
|
|
140
|
+
size_t extra = 0;
|
|
141
|
+
for (const unsigned char *p = in; *p; ++p) {
|
|
142
|
+
switch (*p) {
|
|
143
|
+
case '\\': case '"': case '\n': case '\r': case '\t': extra++; break;
|
|
144
|
+
default: break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
size_t inlen = strlen(s);
|
|
148
|
+
char *out = (char*)malloc(inlen + extra + 1);
|
|
149
|
+
if (!out) return NULL;
|
|
150
|
+
char *o = out;
|
|
151
|
+
for (const unsigned char *p = in; *p; ++p) {
|
|
152
|
+
switch (*p) {
|
|
153
|
+
case '\\': *o++='\\'; *o++='\\'; break;
|
|
154
|
+
case '"': *o++='\\'; *o++='"'; break;
|
|
155
|
+
case '\n': *o++='\\'; *o++='n'; break;
|
|
156
|
+
case '\r': *o++='\\'; *o++='r'; break;
|
|
157
|
+
case '\t': *o++='\\'; *o++='t'; break;
|
|
158
|
+
default: *o++=(char)*p;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
*o=0;
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// generic prefix builder for a given escaped query
|
|
166
|
+
static int build_prefix_for_query(const char *query_escaped, char **out_prefix) {
|
|
167
|
+
const char *p1 = "{\"query\":\"";
|
|
168
|
+
const char *p2 = "\",\"variables\":{";
|
|
169
|
+
const char *k1 = "\"apiKey\":\"";
|
|
170
|
+
const char *k2 = "\",\"serviceUuid\":\"";
|
|
171
|
+
const char *k3 = "\",\"library\":\"";
|
|
172
|
+
const char *k4 = "\",\"version\":\"";
|
|
173
|
+
|
|
174
|
+
size_t n = strlen(p1) + strlen(query_escaped) + strlen(p2)
|
|
175
|
+
+ strlen(k1) + strlen(g_api_key)
|
|
176
|
+
+ strlen(k2) + strlen(g_service_uuid)
|
|
177
|
+
+ strlen(k3) + strlen(g_library)
|
|
178
|
+
+ strlen(k4) + strlen(g_version) + 5;
|
|
179
|
+
|
|
180
|
+
char *prefix = (char*)malloc(n);
|
|
181
|
+
if (!prefix) return 0;
|
|
182
|
+
|
|
183
|
+
char *o = prefix;
|
|
184
|
+
o += sprintf(o, "%s%s%s", p1, query_escaped, p2);
|
|
185
|
+
o += sprintf(o, "%s%s", k1, g_api_key);
|
|
186
|
+
o += sprintf(o, "%s%s", k2, g_service_uuid);
|
|
187
|
+
o += sprintf(o, "%s%s", k3, g_library);
|
|
188
|
+
o += sprintf(o, "%s%s\"", k4, g_version);
|
|
189
|
+
*o = '\0';
|
|
190
|
+
|
|
191
|
+
*out_prefix = prefix;
|
|
192
|
+
return 1;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Build SERVICE IDENTIFIER body
|
|
196
|
+
static int build_body_service_identifier(
|
|
197
|
+
const char *service_identifier, size_t service_identifier_len,
|
|
198
|
+
const char *service_version, size_t service_version_len,
|
|
199
|
+
const char *service_display_name, size_t service_display_name_len,
|
|
200
|
+
const char *service_additional_metadata, size_t service_additional_metadata_len,
|
|
201
|
+
const char *git_sha, size_t git_sha_len,
|
|
202
|
+
const char *infrastructure_type, size_t infrastructure_type_len,
|
|
203
|
+
const char *infrastructure_details, size_t infrastructure_details_len,
|
|
204
|
+
const char *setup_interceptors_file_path, size_t setup_interceptors_file_path_len,
|
|
205
|
+
int setup_interceptors_line_number,
|
|
206
|
+
char **out_body, size_t *out_len
|
|
207
|
+
){
|
|
208
|
+
// Escape all string fields
|
|
209
|
+
char *si_tmp = (char*)malloc(service_identifier_len + 1);
|
|
210
|
+
if (!si_tmp) return 0;
|
|
211
|
+
memcpy(si_tmp, service_identifier ? service_identifier : "", service_identifier_len);
|
|
212
|
+
si_tmp[service_identifier_len] = 0;
|
|
213
|
+
|
|
214
|
+
char *sv_tmp = (char*)malloc(service_version_len + 1);
|
|
215
|
+
if (!sv_tmp) { free(si_tmp); return 0; }
|
|
216
|
+
memcpy(sv_tmp, service_version ? service_version : "", service_version_len);
|
|
217
|
+
sv_tmp[service_version_len] = 0;
|
|
218
|
+
|
|
219
|
+
char *sdn_tmp = (char*)malloc(service_display_name_len + 1);
|
|
220
|
+
if (!sdn_tmp) { free(si_tmp); free(sv_tmp); return 0; }
|
|
221
|
+
memcpy(sdn_tmp, service_display_name ? service_display_name : "", service_display_name_len);
|
|
222
|
+
sdn_tmp[service_display_name_len] = 0;
|
|
223
|
+
|
|
224
|
+
char *sam_tmp = (char*)malloc(service_additional_metadata_len + 3); // +3 for "{}" and null
|
|
225
|
+
if (!sam_tmp) { free(si_tmp); free(sv_tmp); free(sdn_tmp); return 0; }
|
|
226
|
+
if (service_additional_metadata && service_additional_metadata_len > 0) {
|
|
227
|
+
memcpy(sam_tmp, service_additional_metadata, service_additional_metadata_len);
|
|
228
|
+
sam_tmp[service_additional_metadata_len] = 0;
|
|
229
|
+
} else {
|
|
230
|
+
strcpy(sam_tmp, "{}");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
char *gs_tmp = (char*)malloc(git_sha_len + 1);
|
|
234
|
+
if (!gs_tmp) { free(si_tmp); free(sv_tmp); free(sdn_tmp); free(sam_tmp); return 0; }
|
|
235
|
+
memcpy(gs_tmp, git_sha ? git_sha : "", git_sha_len);
|
|
236
|
+
gs_tmp[git_sha_len] = 0;
|
|
237
|
+
|
|
238
|
+
char *it_tmp = (char*)malloc(infrastructure_type_len + 1);
|
|
239
|
+
if (!it_tmp) { free(si_tmp); free(sv_tmp); free(sdn_tmp); free(sam_tmp); free(gs_tmp); return 0; }
|
|
240
|
+
memcpy(it_tmp, infrastructure_type ? infrastructure_type : "", infrastructure_type_len);
|
|
241
|
+
it_tmp[infrastructure_type_len] = 0;
|
|
242
|
+
|
|
243
|
+
char *id_tmp = (char*)malloc(infrastructure_details_len + 3); // +3 for "{}" and null
|
|
244
|
+
if (!id_tmp) { free(si_tmp); free(sv_tmp); free(sdn_tmp); free(sam_tmp); free(gs_tmp); free(it_tmp); return 0; }
|
|
245
|
+
if (infrastructure_details && infrastructure_details_len > 0) {
|
|
246
|
+
memcpy(id_tmp, infrastructure_details, infrastructure_details_len);
|
|
247
|
+
id_tmp[infrastructure_details_len] = 0;
|
|
248
|
+
} else {
|
|
249
|
+
strcpy(id_tmp, "{}");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
char *sifp_tmp = (char*)malloc(setup_interceptors_file_path_len + 1);
|
|
253
|
+
if (!sifp_tmp) { free(si_tmp); free(sv_tmp); free(sdn_tmp); free(sam_tmp); free(gs_tmp); free(it_tmp); free(id_tmp); return 0; }
|
|
254
|
+
memcpy(sifp_tmp, setup_interceptors_file_path ? setup_interceptors_file_path : "", setup_interceptors_file_path_len);
|
|
255
|
+
sifp_tmp[setup_interceptors_file_path_len] = 0;
|
|
256
|
+
|
|
257
|
+
char *si_esc = json_escape(si_tmp);
|
|
258
|
+
char *sv_esc = json_escape(sv_tmp);
|
|
259
|
+
char *sdn_esc = json_escape(sdn_tmp);
|
|
260
|
+
// DON'T escape serviceAdditionalMetadata - it's already JSON
|
|
261
|
+
// DON'T escape infrastructureDetails - it's already JSON
|
|
262
|
+
char *sam_esc = sam_tmp; // Use raw JSON string
|
|
263
|
+
char *gs_esc = json_escape(gs_tmp);
|
|
264
|
+
char *it_esc = json_escape(it_tmp);
|
|
265
|
+
char *id_esc = id_tmp; // Use raw JSON string
|
|
266
|
+
char *sifp_esc = json_escape(sifp_tmp);
|
|
267
|
+
|
|
268
|
+
// Note: sam_tmp and id_tmp are now used directly (not escaped), so don't free them yet
|
|
269
|
+
free(si_tmp); free(sv_tmp); free(sdn_tmp); free(gs_tmp); free(it_tmp); free(sifp_tmp);
|
|
270
|
+
|
|
271
|
+
if (!si_esc || !sv_esc || !sdn_esc || !sam_esc || !gs_esc || !it_esc || !id_esc || !sifp_esc) {
|
|
272
|
+
free(si_esc); free(sv_esc); free(sdn_esc); free(sam_tmp); free(gs_esc); free(it_esc); free(id_tmp); free(sifp_esc);
|
|
273
|
+
return 0;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
uint64_t tms = now_ms();
|
|
277
|
+
const char *k_si = ",\"serviceIdentifier\":\"";
|
|
278
|
+
const char *k_sv = "\",\"serviceVersion\":\"";
|
|
279
|
+
const char *k_sdn = "\",\"serviceDisplayName\":\"";
|
|
280
|
+
const char *k_sam = "\",\"serviceAdditionalMetadata\":"; // No quotes - raw JSON
|
|
281
|
+
const char *k_gs = ",\"gitSha\":\"";
|
|
282
|
+
const char *k_it = "\",\"infrastructureType\":\"";
|
|
283
|
+
const char *k_id = "\",\"infrastructureDetails\":"; // No quotes - raw JSON
|
|
284
|
+
const char *k_sifp = ",\"setupInterceptorsFilePath\":\"";
|
|
285
|
+
const char *k_siln = "\",\"setupInterceptorsLineNumber\":";
|
|
286
|
+
const char *k_ts = ",\"timestampMs\":\"";
|
|
287
|
+
|
|
288
|
+
char ts_buf[32];
|
|
289
|
+
int ts_len = snprintf(ts_buf, sizeof(ts_buf), "%llu", (unsigned long long)tms);
|
|
290
|
+
char ln_buf[32];
|
|
291
|
+
int ln_len = snprintf(ln_buf, sizeof(ln_buf), "%d", setup_interceptors_line_number);
|
|
292
|
+
|
|
293
|
+
if (!g_json_prefix_service_identifier) {
|
|
294
|
+
free(si_esc); free(sv_esc); free(sdn_esc); free(sam_esc); free(gs_esc); free(it_esc); free(id_esc); free(sifp_esc);
|
|
295
|
+
return 0;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
size_t len = strlen(g_json_prefix_service_identifier)
|
|
299
|
+
+ strlen(k_si) + strlen(si_esc)
|
|
300
|
+
+ strlen(k_sv) + strlen(sv_esc)
|
|
301
|
+
+ strlen(k_sdn) + strlen(sdn_esc)
|
|
302
|
+
+ strlen(k_sam) + strlen(sam_esc)
|
|
303
|
+
+ strlen(k_gs) + strlen(gs_esc)
|
|
304
|
+
+ strlen(k_it) + strlen(it_esc)
|
|
305
|
+
+ strlen(k_id) + strlen(id_esc)
|
|
306
|
+
+ strlen(k_sifp) + strlen(sifp_esc)
|
|
307
|
+
+ strlen(k_siln) + (size_t)ln_len
|
|
308
|
+
+ strlen(k_ts) + (size_t)ts_len + 1 // +1 for closing quote
|
|
309
|
+
+ strlen(JSON_SUFFIX);
|
|
310
|
+
|
|
311
|
+
char *body = (char*)malloc(len + 1);
|
|
312
|
+
if (!body) {
|
|
313
|
+
free(si_esc); free(sv_esc); free(sdn_esc);
|
|
314
|
+
free(sam_esc); // sam_esc points to sam_tmp
|
|
315
|
+
free(gs_esc); free(it_esc);
|
|
316
|
+
free(id_esc); // id_esc points to id_tmp
|
|
317
|
+
free(sifp_esc);
|
|
318
|
+
return 0;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
char *o = body;
|
|
322
|
+
o += sprintf(o, "%s", g_json_prefix_service_identifier);
|
|
323
|
+
o += sprintf(o, "%s%s", k_si, si_esc);
|
|
324
|
+
o += sprintf(o, "%s%s", k_sv, sv_esc);
|
|
325
|
+
o += sprintf(o, "%s%s", k_sdn, sdn_esc);
|
|
326
|
+
o += sprintf(o, "%s%s", k_sam, sam_esc);
|
|
327
|
+
o += sprintf(o, "%s%s", k_gs, gs_esc);
|
|
328
|
+
o += sprintf(o, "%s%s", k_it, it_esc);
|
|
329
|
+
o += sprintf(o, "%s%s", k_id, id_esc);
|
|
330
|
+
o += sprintf(o, "%s%s", k_sifp, sifp_esc);
|
|
331
|
+
o += sprintf(o, "%s%s", k_siln, ln_buf);
|
|
332
|
+
o += sprintf(o, "%s%s\"", k_ts, ts_buf);
|
|
333
|
+
o += sprintf(o, "%s", JSON_SUFFIX);
|
|
334
|
+
*o = '\0';
|
|
335
|
+
|
|
336
|
+
*out_body = body;
|
|
337
|
+
*out_len = (size_t)(o - body);
|
|
338
|
+
|
|
339
|
+
// Free escaped strings (sam_esc/id_esc point to original sam_tmp/id_tmp buffers)
|
|
340
|
+
free(si_esc); free(sv_esc); free(sdn_esc);
|
|
341
|
+
free(sam_esc); // Frees sam_tmp
|
|
342
|
+
free(gs_esc); free(it_esc);
|
|
343
|
+
free(id_esc); // Frees id_tmp
|
|
344
|
+
free(sifp_esc);
|
|
345
|
+
return 1;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Build DOMAINS body (takes array of domain strings)
|
|
349
|
+
static int build_body_domains(
|
|
350
|
+
const char **domains, size_t domain_count,
|
|
351
|
+
char **out_body, size_t *out_len
|
|
352
|
+
){
|
|
353
|
+
if (!g_json_prefix_domains) return 0;
|
|
354
|
+
|
|
355
|
+
// Escape each domain and build JSON array
|
|
356
|
+
char **escaped_domains = (char**)calloc(domain_count, sizeof(char*));
|
|
357
|
+
if (!escaped_domains) return 0;
|
|
358
|
+
|
|
359
|
+
size_t total_domains_len = 0;
|
|
360
|
+
for (size_t i = 0; i < domain_count; i++) {
|
|
361
|
+
escaped_domains[i] = json_escape(domains[i] ? domains[i] : "");
|
|
362
|
+
if (!escaped_domains[i]) {
|
|
363
|
+
for (size_t j = 0; j < i; j++) free(escaped_domains[j]);
|
|
364
|
+
free(escaped_domains);
|
|
365
|
+
return 0;
|
|
366
|
+
}
|
|
367
|
+
total_domains_len += strlen(escaped_domains[i]) + 3; // quotes + comma
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
uint64_t tms = now_ms();
|
|
371
|
+
const char *k_domains = ",\"domains\":[";
|
|
372
|
+
const char *k_ts = "],\"timestampMs\":\"";
|
|
373
|
+
char ts_buf[32];
|
|
374
|
+
int ts_len = snprintf(ts_buf, sizeof(ts_buf), "%llu", (unsigned long long)tms);
|
|
375
|
+
|
|
376
|
+
size_t len = strlen(g_json_prefix_domains)
|
|
377
|
+
+ strlen(k_domains) + total_domains_len
|
|
378
|
+
+ strlen(k_ts) + (size_t)ts_len + 1 // +1 for closing quote
|
|
379
|
+
+ strlen(JSON_SUFFIX);
|
|
380
|
+
|
|
381
|
+
char *body = (char*)malloc(len + 1);
|
|
382
|
+
if (!body) {
|
|
383
|
+
for (size_t i = 0; i < domain_count; i++) free(escaped_domains[i]);
|
|
384
|
+
free(escaped_domains);
|
|
385
|
+
return 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
char *o = body;
|
|
389
|
+
o += sprintf(o, "%s", g_json_prefix_domains);
|
|
390
|
+
o += sprintf(o, "%s", k_domains);
|
|
391
|
+
|
|
392
|
+
for (size_t i = 0; i < domain_count; i++) {
|
|
393
|
+
if (i > 0) *o++ = ',';
|
|
394
|
+
o += sprintf(o, "\"%s\"", escaped_domains[i]);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
o += sprintf(o, "%s%s\"", k_ts, ts_buf);
|
|
398
|
+
o += sprintf(o, "%s", JSON_SUFFIX);
|
|
399
|
+
*o = '\0';
|
|
400
|
+
|
|
401
|
+
*out_body = body;
|
|
402
|
+
*out_len = (size_t)(o - body);
|
|
403
|
+
|
|
404
|
+
for (size_t i = 0; i < domain_count; i++) free(escaped_domains[i]);
|
|
405
|
+
free(escaped_domains);
|
|
406
|
+
return 1;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Build UPDATE SERVICE DETAILS body (similar to domains)
|
|
410
|
+
static int build_body_update_service(
|
|
411
|
+
const char **domains, size_t domain_count,
|
|
412
|
+
char **out_body, size_t *out_len
|
|
413
|
+
){
|
|
414
|
+
if (!g_json_prefix_update_service) return 0;
|
|
415
|
+
|
|
416
|
+
// Escape each domain and build JSON array
|
|
417
|
+
char **escaped_domains = (char**)calloc(domain_count, sizeof(char*));
|
|
418
|
+
if (!escaped_domains) return 0;
|
|
419
|
+
|
|
420
|
+
size_t total_domains_len = 0;
|
|
421
|
+
for (size_t i = 0; i < domain_count; i++) {
|
|
422
|
+
escaped_domains[i] = json_escape(domains[i] ? domains[i] : "");
|
|
423
|
+
if (!escaped_domains[i]) {
|
|
424
|
+
for (size_t j = 0; j < i; j++) free(escaped_domains[j]);
|
|
425
|
+
free(escaped_domains);
|
|
426
|
+
return 0;
|
|
427
|
+
}
|
|
428
|
+
total_domains_len += strlen(escaped_domains[i]) + 3; // quotes + comma
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
uint64_t tms = now_ms();
|
|
432
|
+
const char *k_domains = ",\"domains\":[";
|
|
433
|
+
const char *k_ts = "],\"timestampMs\":\"";
|
|
434
|
+
char ts_buf[32];
|
|
435
|
+
int ts_len = snprintf(ts_buf, sizeof(ts_buf), "%llu", (unsigned long long)tms);
|
|
436
|
+
|
|
437
|
+
size_t len = strlen(g_json_prefix_update_service)
|
|
438
|
+
+ strlen(k_domains) + total_domains_len
|
|
439
|
+
+ strlen(k_ts) + (size_t)ts_len + 1 // +1 for closing quote
|
|
440
|
+
+ strlen(JSON_SUFFIX);
|
|
441
|
+
|
|
442
|
+
char *body = (char*)malloc(len + 1);
|
|
443
|
+
if (!body) {
|
|
444
|
+
for (size_t i = 0; i < domain_count; i++) free(escaped_domains[i]);
|
|
445
|
+
free(escaped_domains);
|
|
446
|
+
return 0;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
char *o = body;
|
|
450
|
+
o += sprintf(o, "%s", g_json_prefix_update_service);
|
|
451
|
+
o += sprintf(o, "%s", k_domains);
|
|
452
|
+
|
|
453
|
+
for (size_t i = 0; i < domain_count; i++) {
|
|
454
|
+
if (i > 0) *o++ = ',';
|
|
455
|
+
o += sprintf(o, "\"%s\"", escaped_domains[i]);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
o += sprintf(o, "%s%s\"", k_ts, ts_buf);
|
|
459
|
+
o += sprintf(o, "%s", JSON_SUFFIX);
|
|
460
|
+
*o = '\0';
|
|
461
|
+
|
|
462
|
+
*out_body = body;
|
|
463
|
+
*out_len = (size_t)(o - body);
|
|
464
|
+
|
|
465
|
+
for (size_t i = 0; i < domain_count; i++) free(escaped_domains[i]);
|
|
466
|
+
free(escaped_domains);
|
|
467
|
+
return 1;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Build COLLECT METADATA body
|
|
471
|
+
static int build_body_collect_metadata(
|
|
472
|
+
const char *session_id, size_t session_id_len,
|
|
473
|
+
const char *user_id, size_t user_id_len,
|
|
474
|
+
const char *traits_json, size_t traits_json_len,
|
|
475
|
+
const char **excluded_fields, size_t excluded_fields_count,
|
|
476
|
+
int override,
|
|
477
|
+
char **out_body, size_t *out_len
|
|
478
|
+
){
|
|
479
|
+
if (!g_json_prefix_collect_metadata) return 0;
|
|
480
|
+
|
|
481
|
+
// Escape session_id, user_id, and traits_json
|
|
482
|
+
char *sid_tmp = (char*)malloc(session_id_len + 1);
|
|
483
|
+
if (!sid_tmp) return 0;
|
|
484
|
+
memcpy(sid_tmp, session_id ? session_id : "", session_id_len);
|
|
485
|
+
sid_tmp[session_id_len] = 0;
|
|
486
|
+
|
|
487
|
+
char *uid_tmp = (char*)malloc(user_id_len + 1);
|
|
488
|
+
if (!uid_tmp) { free(sid_tmp); return 0; }
|
|
489
|
+
memcpy(uid_tmp, user_id ? user_id : "", user_id_len);
|
|
490
|
+
uid_tmp[user_id_len] = 0;
|
|
491
|
+
|
|
492
|
+
char *tj_tmp = (char*)malloc(traits_json_len + 1);
|
|
493
|
+
if (!tj_tmp) { free(sid_tmp); free(uid_tmp); return 0; }
|
|
494
|
+
memcpy(tj_tmp, traits_json ? traits_json : "", traits_json_len);
|
|
495
|
+
tj_tmp[traits_json_len] = 0;
|
|
496
|
+
|
|
497
|
+
char *sid_esc = json_escape(sid_tmp);
|
|
498
|
+
char *uid_esc = json_escape(uid_tmp);
|
|
499
|
+
char *tj_esc = json_escape(tj_tmp);
|
|
500
|
+
free(sid_tmp); free(uid_tmp); free(tj_tmp);
|
|
501
|
+
|
|
502
|
+
if (!sid_esc || !uid_esc || !tj_esc) {
|
|
503
|
+
free(sid_esc); free(uid_esc); free(tj_esc);
|
|
504
|
+
return 0;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Escape excluded fields array
|
|
508
|
+
char **escaped_fields = NULL;
|
|
509
|
+
size_t total_fields_len = 0;
|
|
510
|
+
if (excluded_fields_count > 0) {
|
|
511
|
+
escaped_fields = (char**)calloc(excluded_fields_count, sizeof(char*));
|
|
512
|
+
if (!escaped_fields) {
|
|
513
|
+
free(sid_esc); free(uid_esc); free(tj_esc);
|
|
514
|
+
return 0;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
for (size_t i = 0; i < excluded_fields_count; i++) {
|
|
518
|
+
escaped_fields[i] = json_escape(excluded_fields[i] ? excluded_fields[i] : "");
|
|
519
|
+
if (!escaped_fields[i]) {
|
|
520
|
+
for (size_t j = 0; j < i; j++) free(escaped_fields[j]);
|
|
521
|
+
free(escaped_fields);
|
|
522
|
+
free(sid_esc); free(uid_esc); free(tj_esc);
|
|
523
|
+
return 0;
|
|
524
|
+
}
|
|
525
|
+
total_fields_len += strlen(escaped_fields[i]) + 3; // quotes + comma
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
uint64_t tms = now_ms();
|
|
530
|
+
const char *k_sid = ",\"sessionId\":\"";
|
|
531
|
+
const char *k_uid = "\",\"userId\":\"";
|
|
532
|
+
const char *k_tj = "\",\"traitsJson\":\"";
|
|
533
|
+
const char *k_ef = "\",\"excludedFields\":[";
|
|
534
|
+
const char *k_ov = "],\"override\":";
|
|
535
|
+
const char *k_ts = ",\"timestampMs\":\"";
|
|
536
|
+
|
|
537
|
+
char ts_buf[32];
|
|
538
|
+
int ts_len = snprintf(ts_buf, sizeof(ts_buf), "%llu", (unsigned long long)tms);
|
|
539
|
+
const char *override_str = override ? "true" : "false";
|
|
540
|
+
|
|
541
|
+
size_t len = strlen(g_json_prefix_collect_metadata)
|
|
542
|
+
+ strlen(k_sid) + strlen(sid_esc)
|
|
543
|
+
+ strlen(k_uid) + strlen(uid_esc)
|
|
544
|
+
+ strlen(k_tj) + strlen(tj_esc)
|
|
545
|
+
+ strlen(k_ef) + total_fields_len
|
|
546
|
+
+ strlen(k_ov) + strlen(override_str)
|
|
547
|
+
+ strlen(k_ts) + (size_t)ts_len + 1 // +1 for closing quote
|
|
548
|
+
+ strlen(JSON_SUFFIX);
|
|
549
|
+
|
|
550
|
+
char *body = (char*)malloc(len + 1);
|
|
551
|
+
if (!body) {
|
|
552
|
+
if (escaped_fields) {
|
|
553
|
+
for (size_t i = 0; i < excluded_fields_count; i++) free(escaped_fields[i]);
|
|
554
|
+
free(escaped_fields);
|
|
555
|
+
}
|
|
556
|
+
free(sid_esc); free(uid_esc); free(tj_esc);
|
|
557
|
+
return 0;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
char *o = body;
|
|
561
|
+
o += sprintf(o, "%s", g_json_prefix_collect_metadata);
|
|
562
|
+
o += sprintf(o, "%s%s", k_sid, sid_esc);
|
|
563
|
+
o += sprintf(o, "%s%s", k_uid, uid_esc);
|
|
564
|
+
o += sprintf(o, "%s%s", k_tj, tj_esc);
|
|
565
|
+
o += sprintf(o, "%s", k_ef);
|
|
566
|
+
|
|
567
|
+
if (excluded_fields_count > 0) {
|
|
568
|
+
for (size_t i = 0; i < excluded_fields_count; i++) {
|
|
569
|
+
if (i > 0) *o++ = ',';
|
|
570
|
+
o += sprintf(o, "\"%s\"", escaped_fields[i]);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
o += sprintf(o, "%s%s", k_ov, override_str);
|
|
575
|
+
o += sprintf(o, "%s%s\"", k_ts, ts_buf);
|
|
576
|
+
o += sprintf(o, "%s", JSON_SUFFIX);
|
|
577
|
+
*o = '\0';
|
|
578
|
+
|
|
579
|
+
*out_body = body;
|
|
580
|
+
*out_len = (size_t)(o - body);
|
|
581
|
+
|
|
582
|
+
if (escaped_fields) {
|
|
583
|
+
for (size_t i = 0; i < excluded_fields_count; i++) free(escaped_fields[i]);
|
|
584
|
+
free(escaped_fields);
|
|
585
|
+
}
|
|
586
|
+
free(sid_esc); free(uid_esc); free(tj_esc);
|
|
587
|
+
return 1;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// ---------- ring ops ----------
|
|
591
|
+
static inline size_t ring_count(void) {
|
|
592
|
+
size_t h = atomic_load_explicit(&g_head, memory_order_acquire);
|
|
593
|
+
size_t t = atomic_load_explicit(&g_tail, memory_order_acquire);
|
|
594
|
+
return t - h;
|
|
595
|
+
}
|
|
596
|
+
static inline int ring_empty(void) { return ring_count() == 0; }
|
|
597
|
+
|
|
598
|
+
static int ring_push(char *body, size_t len) {
|
|
599
|
+
while (atomic_flag_test_and_set_explicit(&g_push_lock, memory_order_acquire)) {
|
|
600
|
+
// brief spin
|
|
601
|
+
}
|
|
602
|
+
size_t t = atomic_load_explicit(&g_tail, memory_order_relaxed);
|
|
603
|
+
size_t h = atomic_load_explicit(&g_head, memory_order_acquire);
|
|
604
|
+
if ((t - h) >= g_cap) {
|
|
605
|
+
atomic_flag_clear_explicit(&g_push_lock, memory_order_release);
|
|
606
|
+
return 0; // full (drop)
|
|
607
|
+
}
|
|
608
|
+
size_t idx = t % g_cap;
|
|
609
|
+
g_ring[idx].body = body;
|
|
610
|
+
g_ring[idx].len = len;
|
|
611
|
+
atomic_store_explicit(&g_tail, t + 1, memory_order_release);
|
|
612
|
+
atomic_flag_clear_explicit(&g_push_lock, memory_order_release);
|
|
613
|
+
|
|
614
|
+
pthread_mutex_lock(&g_cv_mtx);
|
|
615
|
+
pthread_cond_signal(&g_cv);
|
|
616
|
+
pthread_mutex_unlock(&g_cv_mtx);
|
|
617
|
+
return 1;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
static int ring_pop(char **body, size_t *len) {
|
|
621
|
+
size_t h = atomic_load_explicit(&g_head, memory_order_relaxed);
|
|
622
|
+
size_t t = atomic_load_explicit(&g_tail, memory_order_acquire);
|
|
623
|
+
if (h == t) return 0;
|
|
624
|
+
size_t idx = h % g_cap;
|
|
625
|
+
*body = g_ring[idx].body;
|
|
626
|
+
*len = g_ring[idx].len;
|
|
627
|
+
g_ring[idx].body = NULL;
|
|
628
|
+
g_ring[idx].len = 0;
|
|
629
|
+
atomic_store_explicit(&g_head, h + 1, memory_order_release);
|
|
630
|
+
return 1;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// ---------- curl sink callbacks ----------
|
|
634
|
+
static size_t _sink_write(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
635
|
+
(void)ptr; (void)userdata;
|
|
636
|
+
return size * nmemb;
|
|
637
|
+
}
|
|
638
|
+
static size_t _sink_header(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
639
|
+
(void)ptr; (void)userdata;
|
|
640
|
+
return size * nmemb;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// ---------- sender thread ----------
|
|
644
|
+
static void *sender_main(void *arg) {
|
|
645
|
+
(void)arg;
|
|
646
|
+
|
|
647
|
+
// CRITICAL: Set telemetry guard for this thread (prevents _sfteepreload.c capture)
|
|
648
|
+
sf_guard_enter();
|
|
649
|
+
|
|
650
|
+
// Initialize thread-local curl handle
|
|
651
|
+
g_telem_curl = curl_easy_init();
|
|
652
|
+
if (!g_telem_curl) {
|
|
653
|
+
sf_guard_leave();
|
|
654
|
+
return NULL;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Configure curl handle (copy from global settings)
|
|
658
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_URL, g_url);
|
|
659
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_TCP_KEEPALIVE, 1L);
|
|
660
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_TCP_NODELAY, 1L); // NEW: Eliminate Nagle delay
|
|
661
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_HTTPHEADER, g_hdrs);
|
|
662
|
+
#ifdef CURL_HTTP_VERSION_2TLS
|
|
663
|
+
if (g_http2) {
|
|
664
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
|
|
665
|
+
}
|
|
666
|
+
#endif
|
|
667
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
|
668
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
|
669
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_WRITEFUNCTION, _sink_write);
|
|
670
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_HEADERFUNCTION, _sink_header);
|
|
671
|
+
|
|
672
|
+
while (atomic_load(&g_running)) {
|
|
673
|
+
if (ring_empty()) {
|
|
674
|
+
pthread_mutex_lock(&g_cv_mtx);
|
|
675
|
+
if (ring_empty() && atomic_load(&g_running))
|
|
676
|
+
pthread_cond_wait(&g_cv, &g_cv_mtx);
|
|
677
|
+
pthread_mutex_unlock(&g_cv_mtx);
|
|
678
|
+
if (!atomic_load(&g_running)) break;
|
|
679
|
+
}
|
|
680
|
+
char *body = NULL; size_t len = 0;
|
|
681
|
+
while (ring_pop(&body, &len)) {
|
|
682
|
+
if (!body) continue;
|
|
683
|
+
|
|
684
|
+
// Use thread-local curl handle (each thread has its own persistent connection)
|
|
685
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_POSTFIELDS, body);
|
|
686
|
+
curl_easy_setopt(g_telem_curl, CURLOPT_POSTFIELDSIZE, (long)len);
|
|
687
|
+
(void)curl_easy_perform(g_telem_curl); // fire-and-forget
|
|
688
|
+
|
|
689
|
+
free(body);
|
|
690
|
+
if (!atomic_load(&g_running)) break;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
if (g_telem_curl) {
|
|
694
|
+
curl_easy_cleanup(g_telem_curl);
|
|
695
|
+
g_telem_curl = NULL;
|
|
696
|
+
}
|
|
697
|
+
sf_guard_leave();
|
|
698
|
+
return NULL;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// ---------- Python API ----------
|
|
702
|
+
static PyObject *py_init(PyObject *self, PyObject *args, PyObject *kw) {
|
|
703
|
+
const char *url, *query, *api_key, *service_uuid, *library, *version;
|
|
704
|
+
int http2 = 0;
|
|
705
|
+
static char *kwlist[] = {"url","query","api_key","service_uuid","library","version","http2", NULL};
|
|
706
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "ssssssi",
|
|
707
|
+
kwlist, &url, &query, &api_key, &service_uuid, &library, &version, &http2)) {
|
|
708
|
+
Py_RETURN_FALSE;
|
|
709
|
+
}
|
|
710
|
+
if (g_running) Py_RETURN_TRUE;
|
|
711
|
+
|
|
712
|
+
g_url = str_dup(url);
|
|
713
|
+
g_api_key = str_dup(api_key);
|
|
714
|
+
g_service_uuid = str_dup(service_uuid);
|
|
715
|
+
g_library = str_dup(library);
|
|
716
|
+
g_version = str_dup(version);
|
|
717
|
+
g_http2 = http2 ? 1 : 0;
|
|
718
|
+
if (!g_url || !g_api_key || !g_service_uuid || !g_library || !g_version) {
|
|
719
|
+
Py_RETURN_FALSE;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
g_cap = SFS_RING_CAP;
|
|
723
|
+
g_ring = (sfs_msg_t*)calloc(g_cap, sizeof(sfs_msg_t));
|
|
724
|
+
if (!g_ring) { Py_RETURN_FALSE; }
|
|
725
|
+
|
|
726
|
+
// Parse SF_SERVICE_SENDER_THREADS environment variable
|
|
727
|
+
const char *env_threads = getenv("SF_SERVICE_SENDER_THREADS");
|
|
728
|
+
if (env_threads) {
|
|
729
|
+
int t = atoi(env_threads);
|
|
730
|
+
if (t > 0 && t <= MAX_SENDER_THREADS) {
|
|
731
|
+
g_configured_sender_threads = t;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Initialize curl (shared headers only - handles are per-thread)
|
|
736
|
+
curl_global_init(CURL_GLOBAL_DEFAULT);
|
|
737
|
+
g_hdrs = NULL;
|
|
738
|
+
g_hdrs = curl_slist_append(g_hdrs, "Content-Type: application/json");
|
|
739
|
+
|
|
740
|
+
// Start sender thread pool
|
|
741
|
+
atomic_store(&g_running, 1);
|
|
742
|
+
g_num_sender_threads = g_configured_sender_threads;
|
|
743
|
+
for (int i = 0; i < g_num_sender_threads; i++) {
|
|
744
|
+
if (pthread_create(&g_sender_threads[i], NULL, sender_main, NULL) != 0) {
|
|
745
|
+
atomic_store(&g_running, 0);
|
|
746
|
+
// Clean up already-started threads
|
|
747
|
+
for (int j = 0; j < i; j++) {
|
|
748
|
+
pthread_join(g_sender_threads[j], NULL);
|
|
749
|
+
}
|
|
750
|
+
Py_RETURN_FALSE;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
Py_RETURN_TRUE;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
static PyObject *py_init_service_identifier(PyObject *self, PyObject *args, PyObject *kw) {
|
|
757
|
+
const char *url, *query, *api_key, *service_uuid, *library, *version;
|
|
758
|
+
int http2 = 1;
|
|
759
|
+
static char *kwlist[] = {"url","query","api_key","service_uuid","library","version","http2",NULL};
|
|
760
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "ssssssi", kwlist,
|
|
761
|
+
&url, &query, &api_key, &service_uuid, &library, &version, &http2)) {
|
|
762
|
+
Py_RETURN_FALSE;
|
|
763
|
+
}
|
|
764
|
+
if (g_service_identifier_query_escaped) { free(g_service_identifier_query_escaped); g_service_identifier_query_escaped = NULL; }
|
|
765
|
+
if (g_json_prefix_service_identifier) { free(g_json_prefix_service_identifier); g_json_prefix_service_identifier = NULL; }
|
|
766
|
+
|
|
767
|
+
g_service_identifier_query_escaped = json_escape_query(query);
|
|
768
|
+
if (!g_service_identifier_query_escaped) Py_RETURN_FALSE;
|
|
769
|
+
if (!build_prefix_for_query(g_service_identifier_query_escaped, &g_json_prefix_service_identifier)) {
|
|
770
|
+
Py_RETURN_FALSE;
|
|
771
|
+
}
|
|
772
|
+
Py_RETURN_TRUE;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
static PyObject *py_init_domains(PyObject *self, PyObject *args, PyObject *kw) {
|
|
776
|
+
const char *url, *query, *api_key, *service_uuid, *library, *version;
|
|
777
|
+
int http2 = 1;
|
|
778
|
+
static char *kwlist[] = {"url","query","api_key","service_uuid","library","version","http2",NULL};
|
|
779
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "ssssssi", kwlist,
|
|
780
|
+
&url, &query, &api_key, &service_uuid, &library, &version, &http2)) {
|
|
781
|
+
Py_RETURN_FALSE;
|
|
782
|
+
}
|
|
783
|
+
if (g_domains_query_escaped) { free(g_domains_query_escaped); g_domains_query_escaped = NULL; }
|
|
784
|
+
if (g_json_prefix_domains) { free(g_json_prefix_domains); g_json_prefix_domains = NULL; }
|
|
785
|
+
|
|
786
|
+
g_domains_query_escaped = json_escape_query(query);
|
|
787
|
+
if (!g_domains_query_escaped) Py_RETURN_FALSE;
|
|
788
|
+
if (!build_prefix_for_query(g_domains_query_escaped, &g_json_prefix_domains)) {
|
|
789
|
+
Py_RETURN_FALSE;
|
|
790
|
+
}
|
|
791
|
+
Py_RETURN_TRUE;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
static PyObject *py_init_update_service(PyObject *self, PyObject *args, PyObject *kw) {
|
|
795
|
+
const char *url, *query, *api_key, *service_uuid, *library, *version;
|
|
796
|
+
int http2 = 1;
|
|
797
|
+
static char *kwlist[] = {"url","query","api_key","service_uuid","library","version","http2",NULL};
|
|
798
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "ssssssi", kwlist,
|
|
799
|
+
&url, &query, &api_key, &service_uuid, &library, &version, &http2)) {
|
|
800
|
+
Py_RETURN_FALSE;
|
|
801
|
+
}
|
|
802
|
+
if (g_update_service_query_escaped) { free(g_update_service_query_escaped); g_update_service_query_escaped = NULL; }
|
|
803
|
+
if (g_json_prefix_update_service) { free(g_json_prefix_update_service); g_json_prefix_update_service = NULL; }
|
|
804
|
+
|
|
805
|
+
g_update_service_query_escaped = json_escape_query(query);
|
|
806
|
+
if (!g_update_service_query_escaped) Py_RETURN_FALSE;
|
|
807
|
+
if (!build_prefix_for_query(g_update_service_query_escaped, &g_json_prefix_update_service)) {
|
|
808
|
+
Py_RETURN_FALSE;
|
|
809
|
+
}
|
|
810
|
+
Py_RETURN_TRUE;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
static PyObject *py_init_collect_metadata(PyObject *self, PyObject *args, PyObject *kw) {
|
|
814
|
+
const char *url, *query, *api_key, *service_uuid, *library, *version;
|
|
815
|
+
int http2 = 1;
|
|
816
|
+
static char *kwlist[] = {"url","query","api_key","service_uuid","library","version","http2",NULL};
|
|
817
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "ssssssi", kwlist,
|
|
818
|
+
&url, &query, &api_key, &service_uuid, &library, &version, &http2)) {
|
|
819
|
+
Py_RETURN_FALSE;
|
|
820
|
+
}
|
|
821
|
+
if (g_collect_metadata_query_escaped) { free(g_collect_metadata_query_escaped); g_collect_metadata_query_escaped = NULL; }
|
|
822
|
+
if (g_json_prefix_collect_metadata) { free(g_json_prefix_collect_metadata); g_json_prefix_collect_metadata = NULL; }
|
|
823
|
+
|
|
824
|
+
g_collect_metadata_query_escaped = json_escape_query(query);
|
|
825
|
+
if (!g_collect_metadata_query_escaped) Py_RETURN_FALSE;
|
|
826
|
+
if (!build_prefix_for_query(g_collect_metadata_query_escaped, &g_json_prefix_collect_metadata)) {
|
|
827
|
+
Py_RETURN_FALSE;
|
|
828
|
+
}
|
|
829
|
+
Py_RETURN_TRUE;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
static PyObject *py_service_identifier(PyObject *self, PyObject *args, PyObject *kw) {
|
|
833
|
+
fprintf(stderr, "[DEBUG _sfservice.c] py_service_identifier: ENTRY\n");
|
|
834
|
+
fflush(stderr);
|
|
835
|
+
|
|
836
|
+
const char *service_identifier, *service_version, *service_display_name, *service_additional_metadata;
|
|
837
|
+
const char *git_sha, *infrastructure_type, *infrastructure_details;
|
|
838
|
+
const char *setup_interceptors_file_path;
|
|
839
|
+
Py_ssize_t si_len = 0, sv_len = 0, sdn_len = 0, sam_len = 0, gs_len = 0;
|
|
840
|
+
Py_ssize_t it_len = 0, id_len = 0, sifp_len = 0;
|
|
841
|
+
int setup_interceptors_line_number = 0;
|
|
842
|
+
|
|
843
|
+
static char *kwlist[] = {
|
|
844
|
+
"service_identifier", "service_version", "service_display_name", "service_additional_metadata",
|
|
845
|
+
"git_sha", "infrastructure_type", "infrastructure_details",
|
|
846
|
+
"setup_interceptors_file_path", "setup_interceptors_line_number", NULL
|
|
847
|
+
};
|
|
848
|
+
|
|
849
|
+
fprintf(stderr, "[DEBUG _sfservice.c] py_service_identifier: parsing arguments\n");
|
|
850
|
+
fflush(stderr);
|
|
851
|
+
|
|
852
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "s#s#s#s#s#s#s#s#i", kwlist,
|
|
853
|
+
&service_identifier, &si_len,
|
|
854
|
+
&service_version, &sv_len,
|
|
855
|
+
&service_display_name, &sdn_len,
|
|
856
|
+
&service_additional_metadata, &sam_len,
|
|
857
|
+
&git_sha, &gs_len,
|
|
858
|
+
&infrastructure_type, &it_len,
|
|
859
|
+
&infrastructure_details, &id_len,
|
|
860
|
+
&setup_interceptors_file_path, &sifp_len,
|
|
861
|
+
&setup_interceptors_line_number)) {
|
|
862
|
+
fprintf(stderr, "[DEBUG _sfservice.c] py_service_identifier: argument parsing FAILED\n");
|
|
863
|
+
fflush(stderr);
|
|
864
|
+
return NULL; // PyArg_ParseTupleAndKeywords already set exception
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
fprintf(stderr, "[DEBUG _sfservice.c] py_service_identifier: arguments parsed OK\n");
|
|
868
|
+
fflush(stderr);
|
|
869
|
+
|
|
870
|
+
if (!g_running || g_json_prefix_service_identifier == NULL) {
|
|
871
|
+
fprintf(stderr, "[DEBUG _sfservice.c] py_service_identifier: not running or prefix NULL, returning None\n");
|
|
872
|
+
fflush(stderr);
|
|
873
|
+
Py_RETURN_NONE;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// OPTIMIZATION: Release GIL during JSON building + ring push
|
|
877
|
+
// All string arguments are already C strings from PyArg_ParseTupleAndKeywords,
|
|
878
|
+
// so we can safely release GIL for the entire body building + transmission.
|
|
879
|
+
// This extends GIL-free duration from ~100ns to ~500-2000ns (5-20x improvement).
|
|
880
|
+
char *body = NULL;
|
|
881
|
+
size_t len = 0;
|
|
882
|
+
int ok = 0;
|
|
883
|
+
|
|
884
|
+
fprintf(stderr, "[DEBUG _sfservice.c] py_service_identifier: about to build body\n");
|
|
885
|
+
fflush(stderr);
|
|
886
|
+
|
|
887
|
+
Py_BEGIN_ALLOW_THREADS
|
|
888
|
+
// Build JSON body (WITHOUT GIL - pure C string operations)
|
|
889
|
+
if (build_body_service_identifier(
|
|
890
|
+
service_identifier, (size_t)si_len,
|
|
891
|
+
service_version, (size_t)sv_len,
|
|
892
|
+
service_display_name, (size_t)sdn_len,
|
|
893
|
+
service_additional_metadata, (size_t)sam_len,
|
|
894
|
+
git_sha, (size_t)gs_len,
|
|
895
|
+
infrastructure_type, (size_t)it_len,
|
|
896
|
+
infrastructure_details, (size_t)id_len,
|
|
897
|
+
setup_interceptors_file_path, (size_t)sifp_len,
|
|
898
|
+
setup_interceptors_line_number,
|
|
899
|
+
&body, &len)) {
|
|
900
|
+
// Push to ring buffer (WITHOUT GIL)
|
|
901
|
+
ok = ring_push(body, len);
|
|
902
|
+
}
|
|
903
|
+
Py_END_ALLOW_THREADS
|
|
904
|
+
|
|
905
|
+
fprintf(stderr, "[DEBUG _sfservice.c] py_service_identifier: body built, ok=%d\n", ok);
|
|
906
|
+
fflush(stderr);
|
|
907
|
+
|
|
908
|
+
if (!ok && body) free(body);
|
|
909
|
+
|
|
910
|
+
fprintf(stderr, "[DEBUG _sfservice.c] py_service_identifier: returning None\n");
|
|
911
|
+
fflush(stderr);
|
|
912
|
+
|
|
913
|
+
Py_RETURN_NONE;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
static PyObject *py_domains(PyObject *self, PyObject *args, PyObject *kw) {
|
|
917
|
+
fprintf(stderr, "[DEBUG _sfservice.c] py_domains: ENTRY\n");
|
|
918
|
+
fflush(stderr);
|
|
919
|
+
|
|
920
|
+
PyObject *domains_list = NULL;
|
|
921
|
+
static char *kwlist[] = {"domains", NULL};
|
|
922
|
+
|
|
923
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "O", kwlist, &domains_list)) {
|
|
924
|
+
fprintf(stderr, "[DEBUG _sfservice.c] py_domains: argument parsing FAILED\n");
|
|
925
|
+
fflush(stderr);
|
|
926
|
+
return NULL; // PyArg_ParseTupleAndKeywords already set exception
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
fprintf(stderr, "[DEBUG _sfservice.c] py_domains: arguments parsed OK\n");
|
|
930
|
+
fflush(stderr);
|
|
931
|
+
|
|
932
|
+
if (!g_running || g_json_prefix_domains == NULL) {
|
|
933
|
+
fprintf(stderr, "[DEBUG _sfservice.c] py_domains: not running or prefix NULL\n");
|
|
934
|
+
fflush(stderr);
|
|
935
|
+
Py_RETURN_NONE;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (!PyList_Check(domains_list)) {
|
|
939
|
+
fprintf(stderr, "[DEBUG _sfservice.c] py_domains: domains_list is not a list\n");
|
|
940
|
+
fflush(stderr);
|
|
941
|
+
PyErr_SetString(PyExc_TypeError, "domains must be a list");
|
|
942
|
+
return NULL; // Return NULL when exception is set
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
Py_ssize_t domain_count = PyList_Size(domains_list);
|
|
946
|
+
if (domain_count == 0) Py_RETURN_NONE;
|
|
947
|
+
|
|
948
|
+
const char **domains = (const char**)malloc(sizeof(char*) * domain_count);
|
|
949
|
+
if (!domains) Py_RETURN_NONE;
|
|
950
|
+
|
|
951
|
+
for (Py_ssize_t i = 0; i < domain_count; i++) {
|
|
952
|
+
PyObject *item = PyList_GetItem(domains_list, i);
|
|
953
|
+
if (!PyUnicode_Check(item)) {
|
|
954
|
+
free(domains);
|
|
955
|
+
PyErr_SetString(PyExc_TypeError, "all domains must be strings");
|
|
956
|
+
return NULL; // Return NULL when exception is set
|
|
957
|
+
}
|
|
958
|
+
domains[i] = PyUnicode_AsUTF8(item);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// OPTIMIZATION: Release GIL during JSON building + ring push
|
|
962
|
+
// All string arguments are already C strings from PyArg_ParseTupleAndKeywords,
|
|
963
|
+
// so we can safely release GIL for the entire body building + transmission.
|
|
964
|
+
char *body = NULL;
|
|
965
|
+
size_t len = 0;
|
|
966
|
+
int ok = 0;
|
|
967
|
+
|
|
968
|
+
Py_BEGIN_ALLOW_THREADS
|
|
969
|
+
// Build JSON body (WITHOUT GIL - pure C string operations)
|
|
970
|
+
if (build_body_domains(domains, (size_t)domain_count, &body, &len)) {
|
|
971
|
+
// Push to ring buffer (WITHOUT GIL)
|
|
972
|
+
ok = ring_push(body, len);
|
|
973
|
+
}
|
|
974
|
+
Py_END_ALLOW_THREADS
|
|
975
|
+
|
|
976
|
+
free(domains);
|
|
977
|
+
if (!ok && body) free(body);
|
|
978
|
+
Py_RETURN_NONE;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
static PyObject *py_update_service(PyObject *self, PyObject *args, PyObject *kw) {
|
|
982
|
+
PyObject *domains_list = NULL;
|
|
983
|
+
static char *kwlist[] = {"domains", NULL};
|
|
984
|
+
|
|
985
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "O", kwlist, &domains_list)) {
|
|
986
|
+
Py_RETURN_NONE;
|
|
987
|
+
}
|
|
988
|
+
if (!g_running || g_json_prefix_update_service == NULL) Py_RETURN_NONE;
|
|
989
|
+
|
|
990
|
+
if (!PyList_Check(domains_list)) {
|
|
991
|
+
PyErr_SetString(PyExc_TypeError, "domains must be a list");
|
|
992
|
+
Py_RETURN_NONE;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
Py_ssize_t domain_count = PyList_Size(domains_list);
|
|
996
|
+
if (domain_count == 0) Py_RETURN_NONE;
|
|
997
|
+
|
|
998
|
+
const char **domains = (const char**)malloc(sizeof(char*) * domain_count);
|
|
999
|
+
if (!domains) Py_RETURN_NONE;
|
|
1000
|
+
|
|
1001
|
+
for (Py_ssize_t i = 0; i < domain_count; i++) {
|
|
1002
|
+
PyObject *item = PyList_GetItem(domains_list, i);
|
|
1003
|
+
if (!PyUnicode_Check(item)) {
|
|
1004
|
+
free(domains);
|
|
1005
|
+
PyErr_SetString(PyExc_TypeError, "all domains must be strings");
|
|
1006
|
+
Py_RETURN_NONE;
|
|
1007
|
+
}
|
|
1008
|
+
domains[i] = PyUnicode_AsUTF8(item);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// OPTIMIZATION: Release GIL during JSON building + ring push
|
|
1012
|
+
// All string arguments are already C strings from PyArg_ParseTupleAndKeywords,
|
|
1013
|
+
// so we can safely release GIL for the entire body building + transmission.
|
|
1014
|
+
char *body = NULL;
|
|
1015
|
+
size_t len = 0;
|
|
1016
|
+
int ok = 0;
|
|
1017
|
+
|
|
1018
|
+
Py_BEGIN_ALLOW_THREADS
|
|
1019
|
+
// Build JSON body (WITHOUT GIL - pure C string operations)
|
|
1020
|
+
if (build_body_update_service(domains, (size_t)domain_count, &body, &len)) {
|
|
1021
|
+
// Push to ring buffer (WITHOUT GIL)
|
|
1022
|
+
ok = ring_push(body, len);
|
|
1023
|
+
}
|
|
1024
|
+
Py_END_ALLOW_THREADS
|
|
1025
|
+
|
|
1026
|
+
free(domains);
|
|
1027
|
+
if (!ok && body) free(body);
|
|
1028
|
+
Py_RETURN_NONE;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
static PyObject *py_collect_metadata(PyObject *self, PyObject *args, PyObject *kw) {
|
|
1032
|
+
const char *session_id, *user_id, *traits_json;
|
|
1033
|
+
Py_ssize_t session_id_len = 0, user_id_len = 0, traits_json_len = 0;
|
|
1034
|
+
PyObject *excluded_fields_list = NULL;
|
|
1035
|
+
int override = 0;
|
|
1036
|
+
|
|
1037
|
+
static char *kwlist[] = {"session_id", "user_id", "traits_json", "excluded_fields", "override", NULL};
|
|
1038
|
+
|
|
1039
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "s#s#s#O|p", kwlist,
|
|
1040
|
+
&session_id, &session_id_len,
|
|
1041
|
+
&user_id, &user_id_len,
|
|
1042
|
+
&traits_json, &traits_json_len,
|
|
1043
|
+
&excluded_fields_list,
|
|
1044
|
+
&override)) {
|
|
1045
|
+
Py_RETURN_NONE;
|
|
1046
|
+
}
|
|
1047
|
+
if (!g_running || g_json_prefix_collect_metadata == NULL) Py_RETURN_NONE;
|
|
1048
|
+
|
|
1049
|
+
if (!PyList_Check(excluded_fields_list)) {
|
|
1050
|
+
PyErr_SetString(PyExc_TypeError, "excluded_fields must be a list");
|
|
1051
|
+
Py_RETURN_NONE;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
Py_ssize_t excluded_fields_count = PyList_Size(excluded_fields_list);
|
|
1055
|
+
const char **excluded_fields = NULL;
|
|
1056
|
+
|
|
1057
|
+
if (excluded_fields_count > 0) {
|
|
1058
|
+
excluded_fields = (const char**)malloc(sizeof(char*) * excluded_fields_count);
|
|
1059
|
+
if (!excluded_fields) Py_RETURN_NONE;
|
|
1060
|
+
|
|
1061
|
+
for (Py_ssize_t i = 0; i < excluded_fields_count; i++) {
|
|
1062
|
+
PyObject *item = PyList_GetItem(excluded_fields_list, i);
|
|
1063
|
+
if (!PyUnicode_Check(item)) {
|
|
1064
|
+
free(excluded_fields);
|
|
1065
|
+
PyErr_SetString(PyExc_TypeError, "all excluded_fields must be strings");
|
|
1066
|
+
Py_RETURN_NONE;
|
|
1067
|
+
}
|
|
1068
|
+
excluded_fields[i] = PyUnicode_AsUTF8(item);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// OPTIMIZATION: Release GIL during JSON building + ring push
|
|
1073
|
+
// All string arguments are already C strings from PyArg_ParseTupleAndKeywords,
|
|
1074
|
+
// so we can safely release GIL for the entire body building + transmission.
|
|
1075
|
+
// This extends GIL-free duration from ~100ns to ~500-2000ns (5-20x improvement).
|
|
1076
|
+
char *body = NULL;
|
|
1077
|
+
size_t len = 0;
|
|
1078
|
+
int ok = 0;
|
|
1079
|
+
|
|
1080
|
+
Py_BEGIN_ALLOW_THREADS
|
|
1081
|
+
// Build JSON body (WITHOUT GIL - pure C string operations)
|
|
1082
|
+
if (build_body_collect_metadata(
|
|
1083
|
+
session_id, (size_t)session_id_len,
|
|
1084
|
+
user_id, (size_t)user_id_len,
|
|
1085
|
+
traits_json, (size_t)traits_json_len,
|
|
1086
|
+
excluded_fields, (size_t)excluded_fields_count,
|
|
1087
|
+
override,
|
|
1088
|
+
&body, &len)) {
|
|
1089
|
+
// Push to ring buffer (WITHOUT GIL)
|
|
1090
|
+
ok = ring_push(body, len);
|
|
1091
|
+
}
|
|
1092
|
+
Py_END_ALLOW_THREADS
|
|
1093
|
+
|
|
1094
|
+
if (excluded_fields) free(excluded_fields);
|
|
1095
|
+
if (!ok && body) free(body);
|
|
1096
|
+
Py_RETURN_NONE;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// Convenience wrapper: identify (calls collect_metadata)
|
|
1100
|
+
static PyObject *py_identify(PyObject *self, PyObject *args, PyObject *kw) {
|
|
1101
|
+
const char *session_id, *user_id, *traits_json;
|
|
1102
|
+
Py_ssize_t session_id_len = 0, user_id_len = 0, traits_json_len = 0;
|
|
1103
|
+
PyObject *excluded_fields_list = NULL;
|
|
1104
|
+
int override = 0;
|
|
1105
|
+
|
|
1106
|
+
static char *kwlist[] = {"session_id", "user_id", "traits_json", "excluded_fields", "override", NULL};
|
|
1107
|
+
|
|
1108
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "s#s#s#O|p", kwlist,
|
|
1109
|
+
&session_id, &session_id_len,
|
|
1110
|
+
&user_id, &user_id_len,
|
|
1111
|
+
&traits_json, &traits_json_len,
|
|
1112
|
+
&excluded_fields_list,
|
|
1113
|
+
&override)) {
|
|
1114
|
+
Py_RETURN_NONE;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// Just delegate to collect_metadata
|
|
1118
|
+
return py_collect_metadata(self, args, kw);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// Convenience wrapper: add_or_update_metadata (calls collect_metadata)
|
|
1122
|
+
static PyObject *py_add_or_update_metadata(PyObject *self, PyObject *args, PyObject *kw) {
|
|
1123
|
+
const char *session_id, *user_id, *traits_json;
|
|
1124
|
+
Py_ssize_t session_id_len = 0, user_id_len = 0, traits_json_len = 0;
|
|
1125
|
+
PyObject *excluded_fields_list = NULL;
|
|
1126
|
+
int override = 0;
|
|
1127
|
+
|
|
1128
|
+
static char *kwlist[] = {"session_id", "user_id", "traits_json", "excluded_fields", "override", NULL};
|
|
1129
|
+
|
|
1130
|
+
if (!PyArg_ParseTupleAndKeywords(args, kw, "s#s#s#O|p", kwlist,
|
|
1131
|
+
&session_id, &session_id_len,
|
|
1132
|
+
&user_id, &user_id_len,
|
|
1133
|
+
&traits_json, &traits_json_len,
|
|
1134
|
+
&excluded_fields_list,
|
|
1135
|
+
&override)) {
|
|
1136
|
+
Py_RETURN_NONE;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// Just delegate to collect_metadata
|
|
1140
|
+
return py_collect_metadata(self, args, kw);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
static PyObject *py_shutdown(PyObject *self, PyObject *args) {
|
|
1144
|
+
if (!g_running) Py_RETURN_NONE;
|
|
1145
|
+
|
|
1146
|
+
atomic_store(&g_running, 0);
|
|
1147
|
+
|
|
1148
|
+
// Wake ALL threads with broadcast (not signal)
|
|
1149
|
+
pthread_mutex_lock(&g_cv_mtx);
|
|
1150
|
+
pthread_cond_broadcast(&g_cv);
|
|
1151
|
+
pthread_mutex_unlock(&g_cv_mtx);
|
|
1152
|
+
|
|
1153
|
+
// Join all sender threads in thread pool
|
|
1154
|
+
for (int i = 0; i < g_num_sender_threads; i++) {
|
|
1155
|
+
if (g_sender_threads[i]) {
|
|
1156
|
+
pthread_join(g_sender_threads[i], NULL);
|
|
1157
|
+
g_sender_threads[i] = 0;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
g_num_sender_threads = 0;
|
|
1161
|
+
|
|
1162
|
+
// Cleanup curl (per-thread handles cleaned by pthread_cleanup_push)
|
|
1163
|
+
if (g_hdrs) { curl_slist_free_all(g_hdrs); g_hdrs = NULL; }
|
|
1164
|
+
curl_global_cleanup();
|
|
1165
|
+
|
|
1166
|
+
// Free all config strings and NULL pointers
|
|
1167
|
+
free(g_url); g_url = NULL;
|
|
1168
|
+
|
|
1169
|
+
free(g_service_identifier_query_escaped); g_service_identifier_query_escaped = NULL;
|
|
1170
|
+
free(g_json_prefix_service_identifier); g_json_prefix_service_identifier = NULL;
|
|
1171
|
+
|
|
1172
|
+
free(g_domains_query_escaped); g_domains_query_escaped = NULL;
|
|
1173
|
+
free(g_json_prefix_domains); g_json_prefix_domains = NULL;
|
|
1174
|
+
|
|
1175
|
+
free(g_update_service_query_escaped); g_update_service_query_escaped = NULL;
|
|
1176
|
+
free(g_json_prefix_update_service); g_json_prefix_update_service = NULL;
|
|
1177
|
+
|
|
1178
|
+
free(g_collect_metadata_query_escaped); g_collect_metadata_query_escaped = NULL;
|
|
1179
|
+
free(g_json_prefix_collect_metadata); g_json_prefix_collect_metadata = NULL;
|
|
1180
|
+
|
|
1181
|
+
free(g_api_key); g_api_key = NULL;
|
|
1182
|
+
free(g_service_uuid); g_service_uuid = NULL;
|
|
1183
|
+
free(g_library); g_library = NULL;
|
|
1184
|
+
free(g_version); g_version = NULL;
|
|
1185
|
+
|
|
1186
|
+
// Drain and free ring buffer
|
|
1187
|
+
if (g_ring) {
|
|
1188
|
+
char *b; size_t l;
|
|
1189
|
+
while (ring_pop(&b, &l)) free(b);
|
|
1190
|
+
free(g_ring); g_ring = NULL;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
Py_RETURN_NONE;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// ---------- Module table ----------
|
|
1197
|
+
static PyMethodDef SFServiceMethods[] = {
|
|
1198
|
+
{"init", (PyCFunction)py_init, METH_VARARGS | METH_KEYWORDS, "Init and start sender"},
|
|
1199
|
+
{"init_service_identifier", (PyCFunction)py_init_service_identifier,METH_VARARGS | METH_KEYWORDS, "Init (service identifier) query/prefix"},
|
|
1200
|
+
{"init_domains", (PyCFunction)py_init_domains, METH_VARARGS | METH_KEYWORDS, "Init (domains) query/prefix"},
|
|
1201
|
+
{"init_update_service", (PyCFunction)py_init_update_service, METH_VARARGS | METH_KEYWORDS, "Init (update service) query/prefix"},
|
|
1202
|
+
{"init_collect_metadata", (PyCFunction)py_init_collect_metadata, METH_VARARGS | METH_KEYWORDS, "Init (collect metadata) query/prefix"},
|
|
1203
|
+
{"service_identifier", (PyCFunction)py_service_identifier, METH_VARARGS | METH_KEYWORDS, "Send service identifier"},
|
|
1204
|
+
{"domains", (PyCFunction)py_domains, METH_VARARGS | METH_KEYWORDS, "Send domains"},
|
|
1205
|
+
{"update_service", (PyCFunction)py_update_service, METH_VARARGS | METH_KEYWORDS, "Send update service"},
|
|
1206
|
+
{"collect_metadata", (PyCFunction)py_collect_metadata, METH_VARARGS | METH_KEYWORDS, "Send collect metadata"},
|
|
1207
|
+
{"identify", (PyCFunction)py_identify, METH_VARARGS | METH_KEYWORDS, "Identify user (alias for collect_metadata)"},
|
|
1208
|
+
{"add_or_update_metadata", (PyCFunction)py_add_or_update_metadata, METH_VARARGS | METH_KEYWORDS, "Add/update metadata (alias for collect_metadata)"},
|
|
1209
|
+
{"shutdown", (PyCFunction)py_shutdown, METH_NOARGS, "Shutdown sender and free state"},
|
|
1210
|
+
{NULL, NULL, 0, NULL}
|
|
1211
|
+
};
|
|
1212
|
+
|
|
1213
|
+
static struct PyModuleDef sfservicemodule = {
|
|
1214
|
+
PyModuleDef_HEAD_INIT,
|
|
1215
|
+
"_sfservice",
|
|
1216
|
+
"sf_veritas ultra-fast service operations",
|
|
1217
|
+
-1,
|
|
1218
|
+
SFServiceMethods
|
|
1219
|
+
};
|
|
1220
|
+
|
|
1221
|
+
PyMODINIT_FUNC PyInit__sfservice(void) {
|
|
1222
|
+
return PyModule_Create(&sfservicemodule);
|
|
1223
|
+
}
|