v2.1.5 - Offload duplicate and COUNT paths, add config/NIP-11 caching, and reduce DB reader threads

This commit is contained in:
Laan Tungir
2026-04-01 16:15:46 -04:00
parent 9b248b740f
commit ce9ae5a488
12 changed files with 3052 additions and 169 deletions
+379
View File
@@ -0,0 +1,379 @@
# Plan: Remove All SQLite from lws-main Thread
## Problem Statement
After implementing the 4 offload fixes (v2.1.4), `lws-main` still consumes **53.87% avg CPU** (down from 68.71%). Perf profiling shows **100% of the top symbols are SQLite** — B-tree traversal, page cache, VFS reads. The worker threads (`db-read`, `db-write`, `event-worker`) are nearly idle at 0.00.1%.
The goal is to eliminate all synchronous SQLite access from `lws-main` so it only does: JSON parsing, WebSocket I/O, subscription matching (in-memory), and message queuing.
## Thread Architecture Change
Reduce from 7 threads to 4:
```
Current: lws-main, db-read-1, db-read-2, db-read-3, db-read-4, db-write, event-worker
Proposed: lws-main, db-read, db-write, event-worker
```
The 4 reader threads are overkill — they average 0.070.10% CPU each. A single `db-read` thread is sufficient for the current workload. This can be made configurable later if needed.
## Remaining SQLite Calls on lws-main
Every synchronous SQLite call that currently runs on `lws-main`:
### Hot Path — Per-Event (every incoming EVENT message)
| Call | Location | Frequency | Cost |
|------|----------|-----------|------|
| `event_id_exists_in_db()` | websockets.c:1324, 2134 | Every EVENT | ~10-50us per B-tree lookup |
| `get_config_value("relay_pubkey")` | websockets.c:2050 | Every kind-14 EVENT | SQLite SELECT |
| `get_config_value("admin_pubkey")` | websockets.c:2073 | Every kind-23456 EVENT | SQLite SELECT |
### Hot Path — Per-Connection (every new WebSocket connection)
| Call | Location | Frequency | Cost |
|------|----------|-----------|------|
| `get_config_bool("trust_proxy_headers")` | websockets.c:1071 | Every connection | SQLite SELECT |
| `get_config_bool("nip42_auth_required_events")` | websockets.c:1134 | Every connection | SQLite SELECT |
| `get_config_bool("nip42_auth_required_subscriptions")` | websockets.c:1135 | Every connection | SQLite SELECT |
### Hot Path — Per-REQ (every subscription request)
| Call | Location | Frequency | Cost |
|------|----------|-----------|------|
| `get_config_bool("expiration_enabled")` | main.c:1424 | Every REQ | SQLite SELECT |
| `get_config_bool("expiration_filter")` | main.c:1425 | Every REQ | SQLite SELECT |
| `check_database_auth_rules()` | websockets.c:1706 | Every REQ when WoT enabled | SQLite SELECT |
### Medium Path — Per-COUNT (NIP-45)
| Call | Location | Frequency | Cost |
|------|----------|-----------|------|
| `thread_pool_execute_count_sync()` | websockets.c:3689 | Every COUNT message | Blocks main thread waiting for db-read |
### Low Path — Periodic (every 60s or on timer)
| Call | Location | Frequency | Cost |
|------|----------|-----------|------|
| `refresh_hot_config_if_needed()` | websockets.c:228-247 | Every 5s | 8 SQLite SELECTs |
| `generate_and_post_status_event()` | websockets.c:3213 | Every N hours | store_event + broadcast |
| `ip_ban_cleanup/log_stats` | websockets.c:3004-3005 | Every 60s | SQLite reads/writes |
### Per-EVENT — Validator Path (called from event-worker AND sync path)
| Call | Location | Frequency | Cost |
|------|----------|-----------|------|
| `get_config_value("admin_pubkey")` | request_validator.c:308 | Every kind-23456 EVENT | SQLite SELECT |
| `get_config_value("nip42_auth_enabled")` | request_validator.c:326 | Every EVENT | SQLite SELECT |
| `get_config_bool("pow_enabled")` | request_validator.c:370 | Every EVENT | SQLite SELECT |
| `get_config_int("pow_min_difficulty")` | request_validator.c:371 | Every EVENT | SQLite SELECT |
| `get_config_int("pow_validation_flags")` | request_validator.c:372 | Every EVENT | SQLite SELECT |
| `get_config_int("nip40_expiration_grace_period")` | request_validator.c:432 | Every EVENT with expiration | SQLite SELECT |
| `get_config_value("auth_enabled")` | request_validator.c:212 | Every EVENT (reload_auth_config) | SQLite SELECT |
| `get_config_value("auth_rules_enabled")` | request_validator.c:220 | Every EVENT (reload_auth_config) | SQLite SELECT |
| `db_count_active_whitelist_rules()` | request_validator.c:536 | Every 5s (cached) | Opens separate SQLite connection |
**Note:** These run on the **event-worker** thread for async-eligible events, but on **lws-main** for special kinds (14, 1059, 23456) that go through the sync validation path. They also run on lws-main for the `nostr_validate_unified_request()` call in the sync fallback.
### Per-EVENT — NIP-13 PoW Validation
| Call | Location | Frequency | Cost |
|------|----------|-----------|------|
| `get_config_bool("pow_enabled")` | nip013.c:27 | Every EVENT | SQLite SELECT |
| `get_config_int("pow_min_difficulty")` | nip013.c:28 | Every EVENT | SQLite SELECT |
| `get_config_value("pow_mode")` | nip013.c:29 | Every EVENT | SQLite SELECT |
### Per-Broadcast — NIP-40 Expiration Check
| Call | Location | Frequency | Cost |
|------|----------|-----------|------|
| `get_config_bool("expiration_enabled")` | subscriptions.c:764 | Every broadcast | SQLite SELECT |
| `get_config_bool("expiration_filter")` | subscriptions.c:765 | Every broadcast | SQLite SELECT |
### Per-NIP-11 Request (HTTP, not WebSocket)
| Call | Location | Frequency | Cost |
|------|----------|-----------|------|
| 15+ `get_config_value()` calls | nip011.c:96-110 | Every NIP-11 HTTP request | 15 SQLite SELECTs |
| 10+ `get_config_int()`/`get_config_bool()` calls | nip011.c:113-123 | Every NIP-11 HTTP request | 10 SQLite SELECTs |
### Per-NIP-42 Auth Event
| Call | Location | Frequency | Cost |
|------|----------|-----------|------|
| `get_config_value("relay_url")` | nip042.c:99 | Every AUTH event | SQLite SELECT |
| `get_config_int("relay_port")` | nip042.c:103 | Every AUTH event (fallback) | SQLite SELECT |
### Per-NIP-40 Expiration Check (EVENT submission)
| Call | Location | Frequency | Cost |
|------|----------|-----------|------|
| 5 `get_config_bool()`/`get_config_int()` calls | nip040.c:37-41 | Every EVENT with expiration tag | 5 SQLite SELECTs |
### Subscription Lifecycle (per sub create/close/disconnect)
| Call | Location | Frequency | Cost |
|------|----------|-----------|------|
| `db_log_subscription_created()` | subscriptions.c:1043 | Every REQ | SQLite INSERT |
| `db_log_subscription_closed()` | subscriptions.c:1053 | Every CLOSE | SQLite INSERT + UPDATE |
| `db_log_subscription_disconnected()` | subscriptions.c:1060 | Every disconnect | SQLite UPDATE + INSERT |
| `db_update_subscription_events_sent()` | subscriptions.c:1088 | Every broadcast match | SQLite UPDATE |
| `db_cleanup_orphaned_subscriptions()` | subscriptions.c:1100 | Startup only | SQLite DELETE |
### IP Ban System (per-connection and periodic)
| Call | Location | Frequency | Cost |
|------|----------|-----------|------|
| `get_config_value("idle_ban_whitelist")` | ip_ban.c:254 | Every ban check | SQLite SELECT |
| `get_config_bool("auth_fail_ban_enabled")` | ip_ban.c:301, 343 | Every ban check + failure | SQLite SELECT |
| `get_config_bool("idle_ban_enabled")` | ip_ban.c:307, 394 | Every ban check + idle failure | SQLite SELECT |
| 6+ `get_config_int()` calls | ip_ban.c:345-399, 464-465 | Every failure recording | SQLite SELECTs |
| `db_prepare()`/`db_step_stmt()` for ip_bans | ip_ban.c:141-238 | Load at startup + periodic save | SQLite reads/writes |
### Special Kind Sync Path (kinds 14, 1059, 23456)
| Call | Location | Frequency | Cost |
|------|----------|-----------|------|
| `store_event()` | websockets.c:1548-1629 | Every special-kind EVENT | Full sync store |
| `process_admin_event_in_config()` | websockets.c:1468 | Every kind-23456 | Config DB writes |
| `process_nip17_admin_message()` | websockets.c:1523 | Every kind-1059 when enabled | Decrypt + DB |
| `process_dm_stats_command()` | websockets.c:3240 | Admin DM stats | Decrypt + query + encrypt |
| `generate_stats_json()` | api.c:1167 (via dm_admin.c:805) | Admin stats command | Multiple SQLite queries |
### DM Admin Path (called from lws-main for kind 1059/14)
| Call | Location | Frequency | Cost |
|------|----------|-----------|------|
| `get_config_value("relay_pubkey")` | dm_admin.c:358, 451 | Every admin DM | SQLite SELECT |
| `get_config_value("admin_pubkey")` | dm_admin.c:504 | Every admin DM | SQLite SELECT |
| `get_config_int("nip59_timestamp_max_delay_sec")` | dm_admin.c:373 | Every admin DM | SQLite SELECT |
| `get_config_int("wot_enabled")` | dm_admin.c:613, 640, 660 | WoT commands | SQLite SELECT |
### API/Monitoring (called from lws-main periodic timer)
| Call | Location | Frequency | Cost |
|------|----------|-----------|------|
| `generate_stats_json()` | api.c:1167 | Status post + admin stats | ~10 SQLite queries |
| `query_time_based_statistics()` | api.c:102 | Monitoring events | SQLite queries |
| `query_subscription_details()` | api.c:197 | Monitoring events | SQLite queries |
| `get_config_value("relay_pubkey")` | api.c:419, 821, 1291 | Every monitoring event | SQLite SELECT |
| `get_config_int("kind_1_status_posts_hours")` | api.c:547 | Status post check | SQLite SELECT |
| `get_config_int("kind_24567_reporting_throttle_sec")` | api.c:60 | Monitoring throttle | SQLite SELECT |
## Implementation Plan
### Fix 1: Create Global Config Cache — Eliminate ALL get_config_* SQLite Calls
**Impact: Very High** — eliminates 50+ distinct `get_config_value/int/bool` call sites across 8 source files
The audit found `get_config_value/int/bool` calls in: `websockets.c`, `main.c`, `subscriptions.c`, `request_validator.c`, `nip011.c`, `nip013.c`, `nip040.c`, `nip042.c`, `ip_ban.c`, `dm_admin.c`, `api.c`, `config.c`. Every single one does a `SELECT value FROM config WHERE key=?` SQLite query.
**Approach:** Replace `get_config_value_from_table()` with an in-memory hash map that is loaded once at startup and refreshed periodically (every 5s) or on admin config change events.
**Changes:**
1. **New: `config_cache` in config.c** — A simple hash map or fixed array of key-value pairs loaded from the config table:
```c
typedef struct {
char key[64];
char value[512];
} config_cache_entry_t;
static config_cache_entry_t g_config_cache[256];
static int g_config_cache_count = 0;
static time_t g_config_cache_last_refresh = 0;
static pthread_mutex_t g_config_cache_mutex;
#define CONFIG_CACHE_TTL_SEC 5
```
2. **Modify `get_config_value_from_table()`** — Instead of querying SQLite, look up in the in-memory cache. If cache is stale, refresh from SQLite (but only once per TTL period, not per call).
3. **Add `config_cache_refresh()`** — Loads all rows from `SELECT key, value FROM config` into the hash map. Called:
- Once at startup after config table is populated
- Every 5 seconds from the existing `refresh_hot_config_if_needed()` timer
- Immediately after `process_admin_event_in_config()` modifies config
4. **Thread safety:** The cache is read from lws-main and event-worker threads. Use a read-write lock or double-buffer pattern. Since config changes are rare, a simple mutex with short hold time is fine.
5. **Remove the existing `hot_config_cache_t`** in websockets.c — it becomes redundant since the global config cache serves the same purpose with broader coverage.
This single change eliminates SQLite calls from:
- Per-connection: `trust_proxy_headers`, `nip42_auth_required_*` (websockets.c)
- Per-REQ: `expiration_enabled`, `expiration_filter` (main.c)
- Per-EVENT: `admin_pubkey`, `relay_pubkey`, `nip42_auth_enabled`, `pow_*` (request_validator.c, nip013.c)
- Per-broadcast: `expiration_enabled`, `expiration_filter` (subscriptions.c)
- Per-NIP-11: 25+ config values (nip011.c)
- Per-NIP-42: `relay_url`, `relay_port` (nip042.c)
- Per-NIP-40: 5 expiration config values (nip040.c)
- Per-IP-ban: `idle_ban_*`, `auth_fail_*` config values (ip_ban.c)
- Per-admin-DM: `relay_pubkey`, `admin_pubkey`, `wot_enabled` (dm_admin.c)
- Per-monitoring: `relay_pubkey`, throttle values (api.c)
### Fix 2: Move Duplicate Check to Event Worker Thread
**Impact: High** — eliminates the most frequent per-EVENT SQLite call from lws-main
Currently `event_id_exists_in_db()` runs on lws-main before async submission. Move it into the `async_event_worker_main()` function, which already has its own SQLite connection.
**Changes:**
- Remove `event_id_exists_in_db()` calls from both EVENT paths in websockets.c (lines 1324 and 2134)
- Add the duplicate check at the start of `async_event_worker_main()` before `nostr_validate_unified_request()`
- If duplicate found, set `completion->success = 1` and `completion->error_message = "duplicate: already have this event"` and skip crypto
- The completion handler already sends OK responses, so this works seamlessly
**Trade-off:** Without the early dedup check on lws-main, duplicate events will be submitted to the event worker queue and consume a queue slot + thread wakeup before being detected. This is acceptable because:
- The worker thread dedup check is still fast (B-tree index lookup)
- It avoids the much larger cost of running SQLite on lws-main for every event
- Queue depth is 4096, so even under heavy duplicate traffic this won't overflow
### Fix 3: Make COUNT Queries Async
**Impact: Medium** — eliminates blocking wait on lws-main for NIP-45 COUNT
Currently `handle_count_message()` calls `thread_pool_execute_count_sync()` which blocks lws-main waiting for the db-read worker. Convert to the same async pattern used for REQ queries.
**Changes:**
- Create `count_async_state_t` similar to `req_async_state_t`
- Create `count_async_completion_t` with count result
- Add `submit_count_query_async()` that uses `thread_pool_submit_read()` with a callback
- The callback pushes to a count completion queue
- Add `process_count_async_completions()` to drain the queue on lws-main and send COUNT responses
- Call it from the main event loop alongside `process_req_async_completions()`
### Fix 4: Cache Auth Rules Check Result
**Impact: Low-Medium** — eliminates per-REQ SQLite when WoT is enabled
`check_database_auth_rules()` at websockets.c:1706 runs a SQLite query for every REQ when `wot_enabled == 2`. Cache the result per-session.
**Changes:**
- Add `wot_checked` and `wot_allowed` fields to `per_session_data`
- On first REQ, call `check_database_auth_rules()` and cache the result
- On subsequent REQs from the same session, use the cached value
- Reset cache when auth state changes (e.g., after NIP-42 AUTH)
### Fix 5: Move Special-Kind EVENT Store to Event Worker
**Impact: Low** — special kinds are rare but currently do full sync store on lws-main
Kinds 14, 1059, and 23456 are currently excluded from async processing because they have special handling. However, the `store_event()` call within those paths still blocks lws-main.
**Changes:**
- For kind 1059 and kind 14: after special processing completes, submit the store to the event worker instead of calling `store_event()` synchronously
- For kind 23456: admin events are not stored (processed by admin API), so no change needed
- This requires the special processing (decryption, admin check) to still happen on lws-main (it needs `pss` context), but the final DB write goes async
### Fix 6: Reduce Reader Threads from 4 to 1
**Impact: Resource savings** — 3 fewer threads, 3 fewer SQLite connections
**Changes:**
- Change default `reader_threads` in `thread_pool_init()` from 4 to 1
- Or make it configurable via config with default 1
- The single `db-read` thread handles all REQ and COUNT queries sequentially
- If future profiling shows the read thread becoming a bottleneck, increase back to 2+
### Fix 7: Move Subscription Logging Off Main Thread
**Impact: Medium** — eliminates per-REQ INSERT, per-CLOSE UPDATE, per-broadcast UPDATE
The subscription lifecycle functions in subscriptions.c do synchronous SQLite writes on lws-main:
- `db_log_subscription_created()` — INSERT on every REQ
- `db_log_subscription_closed()` — INSERT + UPDATE on every CLOSE
- `db_log_subscription_disconnected()` — UPDATE + INSERT on every disconnect
- `db_update_subscription_events_sent()` — UPDATE on every broadcast match
**Changes:**
- Submit these as fire-and-forget write jobs to the db-write thread via `thread_pool_submit_write()`
- Create a new job type `THREAD_POOL_JOB_SUBSCRIPTION_LOG` or simply use a generic callback job
- The main thread doesn't need to wait for these to complete — they're purely observational logging
### Fix 8: Move Periodic Tasks Off Main Thread
**Impact: Low** — these run every 60s but can still cause latency spikes
- `generate_and_post_status_event()`: Submit as an async job to the event worker
- `ip_ban_cleanup()`/`ip_ban_log_stats()`/`ip_ban_save()`: These do SQLite reads/writes. Move to a periodic job on the db-write thread
- Config cache refresh (Fix 1) replaces the old `refresh_hot_config_if_needed()` SQLite queries
### Fix 9: Cache NIP-11 Response
**Impact: Low-Medium** — eliminates 25+ SQLite SELECTs per NIP-11 HTTP request
`generate_relay_info_json()` in nip011.c calls `get_config_value()` 15 times and `get_config_int()`/`get_config_bool()` 10 times. After Fix 1 (global config cache), these become in-memory lookups. But we can go further:
**Changes:**
- Cache the serialized NIP-11 JSON response string with a TTL (e.g., 60 seconds)
- On NIP-11 request, return the cached string directly without rebuilding
- Invalidate on admin config change events
## Implementation Order
| # | Fix | Risk | Complexity |
|---|-----|------|------------|
| 1 | Global config cache (replaces all get_config_* SQLite) | Low | Medium |
| 2 | Move dedup check to event worker | Low | Low |
| 3 | Async COUNT queries | Low | Medium |
| 4 | Cache WoT auth rules per-session | Very Low | Low |
| 5 | Async store for special kinds | Medium | Medium |
| 6 | Reduce reader threads to 1 | Very Low | Trivial |
| 7 | Move subscription logging off main thread | Low | Low-Medium |
| 8 | Move periodic tasks off main thread | Low | Medium |
| 9 | Cache NIP-11 response | Very Low | Low |
## Expected Impact
After all 9 fixes, `lws-main` should have **zero direct SQLite calls**. Its work becomes:
- JSON parse incoming messages (~fast)
- In-memory subscription matching (~fast)
- In-memory config lookups (~fast, hash map)
- Message formatting and queuing (~fast)
- `lws_service()` I/O (~fast)
- Draining completion queues (~fast)
### Impact by fix
| Fix | SQLite calls eliminated | Frequency |
|-----|------------------------|-----------|
| Fix 1: Global config cache | ~50+ `get_config_*` call sites across 8 files | Per-event, per-connection, per-REQ, per-broadcast, per-NIP-11 |
| Fix 2: Dedup to event worker | 2 `event_id_exists_in_db()` calls | Per-EVENT |
| Fix 3: Async COUNT | 1 `thread_pool_execute_count_sync()` | Per-COUNT message |
| Fix 4: WoT cache per-session | 1 `check_database_auth_rules()` | Per-REQ when WoT enabled |
| Fix 5: Async special-kind store | ~6 `store_event()` calls | Per special-kind EVENT |
| Fix 6: Reduce readers | N/A (resource savings) | N/A |
| Fix 7: Async subscription logging | 4 `db_log_*`/`db_update_*` calls | Per-REQ, per-CLOSE, per-broadcast |
| Fix 8: Async periodic tasks | `generate_stats_json()`, `ip_ban_*` | Every 60s |
| Fix 9: Cache NIP-11 | 25+ config lookups per request | Per NIP-11 HTTP request |
Estimated main-thread CPU reduction: from **54% to ~5-10%** (mostly WebSocket I/O, subscription matching, and JSON parsing).
## Thread Model After Changes
```
lws-main — WebSocket I/O, JSON parse, subscription match, message queue
NO SQLite. Pure compute + I/O.
db-read — All SELECT queries: REQ results, COUNT results, config refresh,
auth rule checks, event dedup checks via completion queue
db-write — All INSERT/UPDATE: event storage, tag inserts, IP ban persistence,
status event generation
event-worker — EVENT validation: signature verification, then submits store to
db-write via completion queue, results flow back to lws-main
```
```mermaid
flowchart LR
CLIENT[Nostr Clients] --> LWS[lws-main<br/>WebSocket I/O<br/>JSON parse<br/>Sub matching<br/>NO SQLite]
LWS -->|EVENT submit| EW[event-worker<br/>Sig verify<br/>Dedup check]
EW -->|store job| DW[db-write<br/>INSERT events<br/>INSERT tags<br/>IP ban save]
EW -->|completion| LWS
DW -->|completion| LWS
LWS -->|REQ/COUNT submit| DR[db-read<br/>SELECT queries<br/>Config refresh<br/>Auth checks]
DR -->|completion| LWS
LWS -->|OK/EVENT/EOSE| CLIENT
```
+1 -1
View File
@@ -1 +1 @@
678119
694269
+125 -12
View File
@@ -251,14 +251,21 @@ const char* get_config_value(const char* key) {
if (!key) {
return NULL;
}
return get_config_value_from_table(key);
const char* cached = get_config_value_from_table(key);
if (!cached) {
return NULL;
}
// Public API returns an owned string; callers may free it.
return cached;
}
int get_config_int(const char* key, int default_value) {
if (!key) {
return default_value;
}
const char* value = get_config_value_from_table(key);
const char* value = get_config_value(key);
if (!value) {
return default_value;
}
@@ -271,7 +278,7 @@ int get_config_bool(const char* key, int default_value) {
if (!key) {
return default_value;
}
const char* value = get_config_value_from_table(key);
const char* value = get_config_value(key);
if (!value) {
return default_value;
}
@@ -1606,13 +1613,7 @@ int handle_configuration_event(cJSON* event, char* error_message, size_t error_s
// Note: Config table is now created via embedded schema in sql_schema.h
// Get value from config table
const char* get_config_value_from_table(const char* key) {
if (!db_is_available() || !key) {
return NULL;
}
return db_get_config_value_dup(key);
}
const char* get_config_value_from_table(const char* key);
// Set value in config table
int set_config_value_in_table(const char* key, const char* value, const char* data_type,
@@ -1621,7 +1622,12 @@ int set_config_value_in_table(const char* key, const char* value, const char* da
return -1;
}
return db_set_config_value_full(key, value, data_type, description, category, requires_restart);
int rc = db_set_config_value_full(key, value, data_type, description, category, requires_restart);
if (rc == 0) {
invalidate_config_cache();
}
return rc;
}
// Update config in table (simpler version of set_config_value_in_table)
@@ -1637,7 +1643,12 @@ int update_config_in_table(const char* key, const char* value) {
return -1;
}
return db_update_config_value_only(key, value);
int rc = db_update_config_value_only(key, value);
if (rc == 0) {
invalidate_config_cache();
}
return rc;
}
// DEPRECATED: This function is no longer used in the unified startup flow.
@@ -4454,6 +4465,108 @@ int populate_all_config_values_atomic(const char* admin_pubkey, const char* rela
// CONFIGURATION CACHE MANAGEMENT
// ================================
#define CONFIG_CACHE_MAX_ITEMS 256
typedef struct {
char key[128];
char value[CONFIG_VALUE_MAX_LENGTH + 1];
int in_use;
} config_cache_entry_t;
static config_cache_entry_t g_config_cache[CONFIG_CACHE_MAX_ITEMS];
static pthread_mutex_t g_config_cache_mutex = PTHREAD_MUTEX_INITIALIZER;
static int find_cache_index_by_key(const char* key) {
if (!key) {
return -1;
}
for (int i = 0; i < CONFIG_CACHE_MAX_ITEMS; i++) {
if (g_config_cache[i].in_use && strcmp(g_config_cache[i].key, key) == 0) {
return i;
}
}
return -1;
}
static int find_empty_cache_index(void) {
for (int i = 0; i < CONFIG_CACHE_MAX_ITEMS; i++) {
if (!g_config_cache[i].in_use) {
return i;
}
}
return -1;
}
static void clear_config_cache_locked(void) {
memset(g_config_cache, 0, sizeof(g_config_cache));
}
void invalidate_config_cache(void) {
pthread_mutex_lock(&g_config_cache_mutex);
clear_config_cache_locked();
pthread_mutex_unlock(&g_config_cache_mutex);
}
void force_config_cache_refresh(void) {
invalidate_config_cache();
}
int reload_config_from_table(void) {
force_config_cache_refresh();
return 0;
}
const char* get_config_value_from_table(const char* key) {
if (!db_is_available() || !key) {
return NULL;
}
pthread_mutex_lock(&g_config_cache_mutex);
int idx = find_cache_index_by_key(key);
if (idx >= 0) {
const char* result = strdup(g_config_cache[idx].value);
pthread_mutex_unlock(&g_config_cache_mutex);
return result;
}
pthread_mutex_unlock(&g_config_cache_mutex);
char* db_value = db_get_config_value_dup(key);
if (!db_value) {
return NULL;
}
pthread_mutex_lock(&g_config_cache_mutex);
idx = find_cache_index_by_key(key);
if (idx < 0) {
idx = find_empty_cache_index();
}
if (idx >= 0) {
g_config_cache[idx].in_use = 1;
strncpy(g_config_cache[idx].key, key, sizeof(g_config_cache[idx].key) - 1);
g_config_cache[idx].key[sizeof(g_config_cache[idx].key) - 1] = '\0';
strncpy(g_config_cache[idx].value, db_value, sizeof(g_config_cache[idx].value) - 1);
g_config_cache[idx].value[sizeof(g_config_cache[idx].value) - 1] = '\0';
}
pthread_mutex_unlock(&g_config_cache_mutex);
return db_value;
}
const char* get_admin_pubkey_cached(void) {
return get_config_value_from_table("admin_pubkey");
}
const char* get_relay_pubkey_cached(void) {
return get_config_value_from_table("relay_pubkey");
}
// ================================
// HYBRID CONFIG ACCESS FUNCTIONS
+1 -1
View File
@@ -2301,7 +2301,7 @@ int main(int argc, char* argv[]) {
if (thread_pool_enabled) {
thread_pool_config_t tp_cfg;
memset(&tp_cfg, 0, sizeof(tp_cfg));
tp_cfg.reader_threads = get_config_int("thread_pool_readers", 4);
tp_cfg.reader_threads = get_config_int("thread_pool_readers", 1);
tp_cfg.max_queue_depth = get_config_int("thread_pool_max_queue_depth", 4096);
tp_cfg.db_path = g_database_path;
tp_cfg.wake_loop_cb = wake_event_loop_from_thread_pool;
+2 -2
View File
@@ -13,8 +13,8 @@
// Using CRELAY_ prefix to avoid conflicts with nostr_core_lib VERSION macros
#define CRELAY_VERSION_MAJOR 2
#define CRELAY_VERSION_MINOR 1
#define CRELAY_VERSION_PATCH 4
#define CRELAY_VERSION "v2.1.4"
#define CRELAY_VERSION_PATCH 5
#define CRELAY_VERSION "v2.1.5"
// Relay metadata (authoritative source for NIP-11 information)
#define RELAY_NAME "C-Relay"
+68 -33
View File
@@ -23,6 +23,67 @@ int get_config_bool(const char* key, int default_value);
#define HTTP_STATUS_INTERNAL_SERVER_ERROR 500
#define NIP11_CACHE_TTL_SEC 5
static pthread_mutex_t g_nip11_cache_mutex = PTHREAD_MUTEX_INITIALIZER;
static char* g_nip11_cached_json = NULL;
static size_t g_nip11_cached_json_len = 0;
static time_t g_nip11_cache_expires_at = 0;
cJSON* generate_relay_info_json();
static void invalidate_nip11_cache_locked(void) {
if (g_nip11_cached_json) {
free(g_nip11_cached_json);
g_nip11_cached_json = NULL;
}
g_nip11_cached_json_len = 0;
g_nip11_cache_expires_at = 0;
}
static char* get_nip11_json_cached_dup(size_t* out_len) {
time_t now = time(NULL);
pthread_mutex_lock(&g_nip11_cache_mutex);
if (g_nip11_cached_json && now < g_nip11_cache_expires_at) {
char* copy = strdup(g_nip11_cached_json);
if (copy && out_len) {
*out_len = g_nip11_cached_json_len;
}
pthread_mutex_unlock(&g_nip11_cache_mutex);
return copy;
}
pthread_mutex_unlock(&g_nip11_cache_mutex);
cJSON* info_json = generate_relay_info_json();
if (!info_json) {
return NULL;
}
char* fresh_json = cJSON_Print(info_json);
cJSON_Delete(info_json);
if (!fresh_json) {
return NULL;
}
size_t fresh_len = strlen(fresh_json);
pthread_mutex_lock(&g_nip11_cache_mutex);
invalidate_nip11_cache_locked();
g_nip11_cached_json = strdup(fresh_json);
if (g_nip11_cached_json) {
g_nip11_cached_json_len = fresh_len;
g_nip11_cache_expires_at = now + NIP11_CACHE_TTL_SEC;
}
pthread_mutex_unlock(&g_nip11_cache_mutex);
if (out_len) {
*out_len = fresh_len;
}
return fresh_json;
}
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// NIP-11 RELAY INFORMATION DOCUMENT
@@ -80,8 +141,9 @@ void init_relay_info() {
// Clean up relay information JSON objects
void cleanup_relay_info() {
// NIP-11 relay information is now generated dynamically from config table
// No cleanup needed - data is fetched directly from database when requested
pthread_mutex_lock(&g_nip11_cache_mutex);
invalidate_nip11_cache_locked();
pthread_mutex_unlock(&g_nip11_cache_mutex);
}
// Generate NIP-11 compliant JSON document
@@ -348,9 +410,10 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
return -1; // Close connection
}
// Generate relay information JSON
cJSON* info_json = generate_relay_info_json();
if (!info_json) {
// Generate (or fetch cached) relay information JSON
size_t json_len = 0;
char* json_string = get_nip11_json_cached_dup(&json_len);
if (!json_string) {
DEBUG_ERROR("Failed to generate relay info JSON");
unsigned char buf[LWS_PRE + 256];
unsigned char *p = &buf[LWS_PRE];
@@ -373,34 +436,6 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
return -1;
}
char* json_string = cJSON_Print(info_json);
cJSON_Delete(info_json);
if (!json_string) {
DEBUG_ERROR("Failed to serialize relay info JSON");
unsigned char buf[LWS_PRE + 256];
unsigned char *p = &buf[LWS_PRE];
unsigned char *start = p;
unsigned char *end = &buf[sizeof(buf) - 1];
if (lws_add_http_header_status(wsi, HTTP_STATUS_INTERNAL_SERVER_ERROR, &p, end)) {
return -1;
}
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char*)"text/plain", 10, &p, end)) {
return -1;
}
if (lws_add_http_header_content_length(wsi, 0, &p, end)) {
return -1;
}
if (lws_finalize_http_header(wsi, &p, end)) {
return -1;
}
lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
return -1;
}
size_t json_len = strlen(json_string);
// Allocate session data to manage buffer lifetime across callbacks
struct nip11_session_data* session_data = malloc(sizeof(struct nip11_session_data));
if (!session_data) {
+290 -120
View File
@@ -330,6 +330,7 @@ typedef struct async_event_completion {
int success;
int should_broadcast;
int run_post_actions;
char ok_message[128];
char error_message[512];
struct async_event_completion* next;
} async_event_completion_t;
@@ -346,6 +347,27 @@ static async_event_completion_t* g_async_event_completion_tail = NULL;
static pthread_t g_async_event_worker_thread;
static int g_async_event_worker_running = 0;
typedef struct count_async_state {
char sub_id[SUBSCRIPTION_ID_MAX_LENGTH];
struct lws* wsi;
struct per_session_data* pss_token;
int pending_jobs;
int total_count;
int had_error;
char error_message[256];
} count_async_state_t;
typedef struct count_async_completion {
count_async_state_t* state;
thread_pool_status_t status;
int count;
struct count_async_completion* next;
} count_async_completion_t;
static pthread_mutex_t g_count_async_completion_mutex = PTHREAD_MUTEX_INITIALIZER;
static count_async_completion_t* g_count_async_completion_head = NULL;
static count_async_completion_t* g_count_async_completion_tail = NULL;
static void map_validation_error_message(int validation_result, char* out, size_t out_size) {
if (!out || out_size == 0) return;
@@ -506,38 +528,46 @@ static void* async_event_worker_main(void* arg) {
strncpy(completion->event_id, job->event_id, sizeof(completion->event_id) - 1);
completion->event_id[sizeof(completion->event_id) - 1] = '\0';
int validation_result = nostr_validate_unified_request(completion->event_json, strlen(completion->event_json));
if (validation_result != NOSTR_SUCCESS) {
completion->success = 0;
if (job->event_id[0] != '\0' && event_id_exists_in_db(job->event_id)) {
completion->success = 1;
completion->should_broadcast = 0;
map_validation_error_message(validation_result, completion->error_message, sizeof(completion->error_message));
completion->run_post_actions = 0;
snprintf(completion->ok_message, sizeof(completion->ok_message),
"duplicate: already have this event");
} else {
cJSON* event_obj = cJSON_Parse(completion->event_json);
if (!event_obj || !cJSON_IsObject(event_obj)) {
int validation_result = nostr_validate_unified_request(completion->event_json, strlen(completion->event_json));
if (validation_result != NOSTR_SUCCESS) {
completion->success = 0;
completion->should_broadcast = 0;
strncpy(completion->error_message, "error: failed to parse event", sizeof(completion->error_message) - 1);
completion->error_message[sizeof(completion->error_message) - 1] = '\0';
map_validation_error_message(validation_result, completion->error_message, sizeof(completion->error_message));
} else {
if (job->event_kind >= 20000 && job->event_kind < 30000) {
completion->success = 1;
completion->should_broadcast = 1;
completion->run_post_actions = 0;
cJSON* event_obj = cJSON_Parse(completion->event_json);
if (!event_obj || !cJSON_IsObject(event_obj)) {
completion->success = 0;
completion->should_broadcast = 0;
strncpy(completion->error_message, "error: failed to parse event", sizeof(completion->error_message) - 1);
completion->error_message[sizeof(completion->error_message) - 1] = '\0';
} else {
int core_rc = store_event_core(event_obj);
if (core_rc < 0) {
completion->success = 0;
completion->should_broadcast = 0;
completion->run_post_actions = 0;
strncpy(completion->error_message, "error: failed to store event", sizeof(completion->error_message) - 1);
completion->error_message[sizeof(completion->error_message) - 1] = '\0';
} else {
if (job->event_kind >= 20000 && job->event_kind < 30000) {
completion->success = 1;
completion->should_broadcast = 1;
completion->run_post_actions = (core_rc == 0);
completion->run_post_actions = 0;
} else {
int core_rc = store_event_core(event_obj);
if (core_rc < 0) {
completion->success = 0;
completion->should_broadcast = 0;
completion->run_post_actions = 0;
strncpy(completion->error_message, "error: failed to store event", sizeof(completion->error_message) - 1);
completion->error_message[sizeof(completion->error_message) - 1] = '\0';
} else {
completion->success = 1;
completion->should_broadcast = 1;
completion->run_post_actions = (core_rc == 0);
}
}
cJSON_Delete(event_obj);
}
cJSON_Delete(event_obj);
}
}
@@ -684,7 +714,9 @@ static void process_async_event_completions(void) {
current_pss,
completion->event_id,
completion->success,
completion->success ? "" : completion->error_message);
completion->success
? (completion->ok_message[0] ? completion->ok_message : "")
: completion->error_message);
}
free(completion->event_json);
@@ -692,6 +724,195 @@ static void process_async_event_completions(void) {
}
}
static void free_count_payload_ws(void* payload_void) {
thread_pool_count_payload_t* payload = (thread_pool_count_payload_t*)payload_void;
if (!payload) return;
free(payload->sql);
if (payload->bind_params) {
for (int i = 0; i < payload->bind_param_count; i++) {
free(payload->bind_params[i]);
}
free(payload->bind_params);
}
free(payload);
}
static void count_async_completion_push(count_async_completion_t* completion) {
if (!completion) return;
pthread_mutex_lock(&g_count_async_completion_mutex);
completion->next = NULL;
if (!g_count_async_completion_tail) {
g_count_async_completion_head = completion;
g_count_async_completion_tail = completion;
} else {
g_count_async_completion_tail->next = completion;
g_count_async_completion_tail = completion;
}
pthread_mutex_unlock(&g_count_async_completion_mutex);
}
static count_async_completion_t* count_async_completion_pop(void) {
pthread_mutex_lock(&g_count_async_completion_mutex);
count_async_completion_t* completion = g_count_async_completion_head;
if (completion) {
g_count_async_completion_head = completion->next;
if (!g_count_async_completion_head) {
g_count_async_completion_tail = NULL;
}
}
pthread_mutex_unlock(&g_count_async_completion_mutex);
return completion;
}
static void count_async_result_cb(const thread_pool_result_t* result, void* user_ctx) {
count_async_state_t* state = (count_async_state_t*)user_ctx;
if (!result || !state) {
if (result && result->result_data) {
free(result->result_data);
}
return;
}
count_async_completion_t* completion = calloc(1, sizeof(*completion));
if (!completion) {
if (result->result_data) {
free(result->result_data);
}
return;
}
completion->state = state;
completion->status = result->status;
if (result->status == THREAD_POOL_STATUS_OK && result->result_data) {
thread_pool_count_result_t* count_result = (thread_pool_count_result_t*)result->result_data;
completion->count = count_result->count;
}
if (result->result_data) {
free(result->result_data);
}
count_async_completion_push(completion);
if (ws_context) {
lws_cancel_service(ws_context);
}
}
static int submit_count_query_async(count_async_state_t* state,
const char* sql,
const char** bind_params,
int bind_param_count) {
if (!state || !sql) {
return -1;
}
thread_pool_count_payload_t* payload = calloc(1, sizeof(*payload));
if (!payload) {
return -1;
}
payload->sql = strdup(sql);
payload->bind_param_count = bind_param_count;
if (!payload->sql) {
free_count_payload_ws(payload);
return -1;
}
if (bind_param_count > 0) {
payload->bind_params = calloc((size_t)bind_param_count, sizeof(char*));
if (!payload->bind_params) {
free_count_payload_ws(payload);
return -1;
}
for (int i = 0; i < bind_param_count; i++) {
const char* value = (bind_params && bind_params[i]) ? bind_params[i] : "";
payload->bind_params[i] = strdup(value);
if (!payload->bind_params[i]) {
free_count_payload_ws(payload);
return -1;
}
}
}
thread_pool_job_t job;
memset(&job, 0, sizeof(job));
job.type = THREAD_POOL_JOB_COUNT_QUERY;
job.payload = payload;
job.payload_free = free_count_payload_ws;
job.result_cb = count_async_result_cb;
job.result_cb_ctx = state;
thread_pool_status_t submit_rc = thread_pool_submit_read(&job, NULL);
if (submit_rc != THREAD_POOL_STATUS_OK) {
free_count_payload_ws(payload);
return -1;
}
state->pending_jobs++;
return 0;
}
static void send_count_response(struct lws* wsi, struct per_session_data* pss,
const char* sub_id, int total_count) {
cJSON* count_response = cJSON_CreateArray();
if (!count_response) {
return;
}
cJSON_AddItemToArray(count_response, cJSON_CreateString("COUNT"));
cJSON_AddItemToArray(count_response, cJSON_CreateString(sub_id));
cJSON* count_obj = cJSON_CreateObject();
cJSON_AddNumberToObject(count_obj, "count", total_count);
cJSON_AddItemToArray(count_response, count_obj);
char* count_str = cJSON_Print(count_response);
if (count_str) {
size_t count_len = strlen(count_str);
if (queue_message(wsi, pss, count_str, count_len, LWS_WRITE_TEXT) != 0) {
DEBUG_ERROR("Failed to queue COUNT message");
}
free(count_str);
}
cJSON_Delete(count_response);
}
static void process_count_async_completions(void) {
count_async_completion_t* completion = NULL;
while ((completion = count_async_completion_pop()) != NULL) {
count_async_state_t* state = completion->state;
if (state) {
if (completion->status == THREAD_POOL_STATUS_OK) {
state->total_count += completion->count;
} else {
state->had_error = 1;
if (state->error_message[0] == '\0') {
strncpy(state->error_message, "error: failed to execute count query", sizeof(state->error_message) - 1);
state->error_message[sizeof(state->error_message) - 1] = '\0';
}
}
state->pending_jobs--;
if (state->pending_jobs <= 0) {
struct per_session_data* current_pss = (struct per_session_data*)lws_wsi_user(state->wsi);
int target_alive = (current_pss && current_pss == state->pss_token);
if (target_alive) {
if (state->had_error) {
send_notice_message(state->wsi, current_pss, state->error_message[0] ? state->error_message : "error: count query failed");
} else {
send_count_response(state->wsi, current_pss, state->sub_id, state->total_count);
}
}
free(state);
}
}
free(completion);
}
}
// Message queue functions for proper libwebsockets pattern
/**
@@ -1315,27 +1536,6 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
return 0;
}
// Early duplicate check: skip expensive crypto for events already in DB.
// The event id is a 64-char hex string — look it up via primary key index (~10μs).
// This must happen AFTER we have the id string but BEFORE signature verification.
cJSON* id_obj_dup = cJSON_GetObjectItemCaseSensitive(event, "id");
if (id_obj_dup && cJSON_IsString(id_obj_dup)) {
const char* event_id_str = cJSON_GetStringValue(id_obj_dup);
if (event_id_str && event_id_exists_in_db(event_id_str)) {
// Already have this event — send OK true and skip crypto
DEBUG_TRACE("Duplicate event %s — skipping signature verification", event_id_str);
char ok_msg[128];
snprintf(ok_msg, sizeof(ok_msg),
"[\"OK\",\"%s\",true,\"duplicate: already have this event\"]",
event_id_str);
size_t ok_len = strlen(ok_msg);
queue_message(wsi, pss, ok_msg, ok_len, LWS_WRITE_TEXT);
free(event_json_str);
cJSON_Delete(json);
return 0;
}
}
// Try async EVENT offload first (Phase 1).
int async_submit_rc = try_submit_async_event(event, event_json_str, wsi, pss);
if (async_submit_rc == 0) {
@@ -1888,10 +2088,15 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
return 0;
}
handle_count_message(subscription_id, filters, wsi, pss);
int count_result = handle_count_message(subscription_id, filters, wsi, pss);
// Clean up the filters array we created
cJSON_Delete(filters);
if (count_result == HANDLE_REQ_ASYNC_PENDING) {
cJSON_Delete(json);
return 0;
}
}
} else if (strcmp(msg_type, "CLOSE") == 0) {
// Handle CLOSE message
@@ -2127,25 +2332,6 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
return 0;
}
// Early duplicate check: skip expensive crypto for events already in DB.
cJSON* id_obj_dup2 = cJSON_GetObjectItemCaseSensitive(event, "id");
if (id_obj_dup2 && cJSON_IsString(id_obj_dup2)) {
const char* event_id_str2 = cJSON_GetStringValue(id_obj_dup2);
if (event_id_str2 && event_id_exists_in_db(event_id_str2)) {
DEBUG_TRACE("Duplicate event %s — skipping signature verification", event_id_str2);
char ok_msg2[128];
snprintf(ok_msg2, sizeof(ok_msg2),
"[\"OK\",\"%s\",true,\"duplicate: already have this event\"]",
event_id_str2);
size_t ok_len2 = strlen(ok_msg2);
queue_message(wsi, pss, ok_msg2, ok_len2, LWS_WRITE_TEXT);
free(event_json_str);
cJSON_Delete(json);
free(message);
return 0;
}
}
// Try async EVENT offload first (Phase 1).
int async_submit_rc = try_submit_async_event(event, event_json_str, wsi, pss);
if (async_submit_rc == 0) {
@@ -2683,10 +2869,16 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
return 0;
}
handle_count_message(subscription_id, filters, wsi, pss);
int count_result = handle_count_message(subscription_id, filters, wsi, pss);
// Clean up the filters array we created
cJSON_Delete(filters);
if (count_result == HANDLE_REQ_ASYNC_PENDING) {
cJSON_Delete(json);
free(message);
return 0;
}
}
} else if (strcmp(msg_type, "CLOSE") == 0) {
// Handle CLOSE message
@@ -3201,6 +3393,9 @@ int start_websocket_relay(int port_override, int strict_port) {
// Drain completed async EVENT jobs and emit OK/broadcast on service thread.
process_async_event_completions();
// Drain completed async COUNT jobs and emit COUNT/NOTICE on service thread.
process_count_async_completions();
// Check if it's time to post status update
time_t current_time = time(NULL);
int status_post_hours = hot_cfg_kind_1_status_posts_hours();
@@ -3433,7 +3628,16 @@ int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, st
int bind_param_count = 0;
int bind_param_capacity = 0;
int total_count = 0;
count_async_state_t* async_state = calloc(1, sizeof(*async_state));
if (!async_state) {
DEBUG_ERROR("Failed to allocate COUNT async state");
return 0;
}
strncpy(async_state->sub_id, sub_id ? sub_id : "", sizeof(async_state->sub_id) - 1);
async_state->sub_id[sizeof(async_state->sub_id) - 1] = '\0';
async_state->wsi = wsi;
async_state->pss_token = pss;
// Process each filter in the array
for (int i = 0; i < cJSON_GetArraySize(filters); i++) {
@@ -3678,68 +3882,34 @@ int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, st
remaining = sizeof(sql) - strlen(sql);
}
// Execute count query
// Start query timing
struct timespec query_start, query_end;
clock_gettime(CLOCK_MONOTONIC, &query_start);
// Execute count query via thread pool helper
int filter_count = 0;
if (thread_pool_execute_count_sync(sql, (const char**)bind_params, bind_param_count, &filter_count) != 0) {
DEBUG_ERROR("Failed to execute COUNT query via db_ops/thread_pool");
continue;
// Submit COUNT query asynchronously to DB reader pool.
if (submit_count_query_async(async_state, sql, (const char**)bind_params, bind_param_count) != 0) {
DEBUG_ERROR("Failed to submit COUNT query async");
strncpy(async_state->error_message, "error: failed to submit count query", sizeof(async_state->error_message) - 1);
async_state->error_message[sizeof(async_state->error_message) - 1] = '\0';
async_state->had_error = 1;
break;
}
// Stop query timing and log
clock_gettime(CLOCK_MONOTONIC, &query_end);
long elapsed_us = (query_end.tv_sec - query_start.tv_sec) * 1000000L +
(query_end.tv_nsec - query_start.tv_nsec) / 1000L;
log_query_execution("COUNT", sub_id, pss ? pss->client_ip : NULL,
sql, elapsed_us, 1); // COUNT always returns 1 row
total_count += filter_count;
}
// Total count calculated
// Send COUNT response - NIP-45 format: ["COUNT", <subscription_id>, {"count": <count>}]
cJSON* count_response = cJSON_CreateArray();
cJSON_AddItemToArray(count_response, cJSON_CreateString("COUNT"));
cJSON_AddItemToArray(count_response, cJSON_CreateString(sub_id));
// Create count object as per NIP-45 specification
cJSON* count_obj = cJSON_CreateObject();
cJSON_AddNumberToObject(count_obj, "count", total_count);
cJSON_AddItemToArray(count_response, count_obj);
char *count_str = cJSON_Print(count_response);
if (count_str) {
size_t count_len = strlen(count_str);
// DEBUG: Log WebSocket frame details before sending
DEBUG_TRACE("WS_FRAME_SEND: type=COUNT len=%zu data=%.100s%s",
count_len,
count_str,
count_len > 100 ? "..." : "");
// Queue message for proper libwebsockets pattern
if (queue_message(wsi, pss, count_str, count_len, LWS_WRITE_TEXT) != 0) {
DEBUG_ERROR("Failed to queue COUNT message");
}
free(count_str);
}
cJSON_Delete(count_response);
// Cleanup bind params
// Cleanup bind params from the final filter iteration.
for (int i = 0; i < bind_param_count; i++) {
free(bind_params[i]);
}
free(bind_params);
return total_count;
if (async_state->pending_jobs <= 0) {
if (async_state->had_error) {
send_notice_message(wsi, pss,
async_state->error_message[0] ? async_state->error_message : "error: count query failed");
} else {
send_count_response(wsi, pss, sub_id, 0);
}
free(async_state);
return 0;
}
return HANDLE_REQ_ASYNC_PENDING;
}
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,5 @@
Profiling PID: 2115498
CLK_TCK=100
Starting perf record in background...
perf_pid=2116380
Done. Artifacts in /tmp/c_relay_thread_profile_20260401_193216
@@ -0,0 +1,211 @@
timestamp,tid,thread_name,cpu_pct,delta_ticks,total_ticks
1775071937,2115498,lws-main,0.00,0,2521
1775071937,2115501,db-read-1,0.00,0,9
1775071937,2115502,db-read-2,0.00,0,10
1775071937,2115503,db-read-3,0.00,0,9
1775071937,2115504,db-read-4,0.00,0,9
1775071937,2115505,db-write,0.00,0,0
1775071937,2115506,event-worker,0.00,0,0
1775071947,2115498,lws-main,0.10,1,2522
1775071947,2115501,db-read-1,0.10,1,10
1775071947,2115502,db-read-2,0.00,0,10
1775071947,2115503,db-read-3,0.00,0,9
1775071947,2115504,db-read-4,0.30,3,12
1775071947,2115505,db-write,0.00,0,0
1775071947,2115506,event-worker,0.00,0,0
1775071957,2115498,lws-main,26.80,268,2790
1775071957,2115501,db-read-1,0.00,0,10
1775071957,2115502,db-read-2,0.10,1,11
1775071957,2115503,db-read-3,0.00,0,9
1775071957,2115504,db-read-4,0.00,0,12
1775071957,2115505,db-write,0.00,0,0
1775071957,2115506,event-worker,0.00,0,0
1775071967,2115498,lws-main,72.10,721,3511
1775071967,2115501,db-read-1,0.00,0,10
1775071967,2115502,db-read-2,0.00,0,11
1775071967,2115503,db-read-3,0.00,0,9
1775071967,2115504,db-read-4,0.00,0,12
1775071967,2115505,db-write,0.00,0,0
1775071967,2115506,event-worker,0.00,0,0
1775071977,2115498,lws-main,64.40,644,4155
1775071977,2115501,db-read-1,0.20,2,12
1775071977,2115502,db-read-2,0.10,1,12
1775071977,2115503,db-read-3,0.10,1,10
1775071977,2115504,db-read-4,0.20,2,14
1775071977,2115505,db-write,0.00,0,0
1775071977,2115506,event-worker,0.00,0,0
1775071987,2115498,lws-main,42.60,426,4581
1775071987,2115501,db-read-1,0.10,1,13
1775071987,2115502,db-read-2,0.10,1,13
1775071987,2115503,db-read-3,0.10,1,11
1775071987,2115504,db-read-4,0.00,0,14
1775071987,2115505,db-write,0.00,0,0
1775071987,2115506,event-worker,0.00,0,0
1775071997,2115498,lws-main,7.20,72,4653
1775071997,2115501,db-read-1,0.00,0,13
1775071997,2115502,db-read-2,0.00,0,13
1775071997,2115503,db-read-3,0.00,0,11
1775071997,2115504,db-read-4,0.00,0,14
1775071997,2115505,db-write,0.00,0,0
1775071997,2115506,event-worker,0.00,0,0
1775072007,2115498,lws-main,42.90,429,5082
1775072007,2115501,db-read-1,0.00,0,13
1775072007,2115502,db-read-2,0.00,0,13
1775072007,2115503,db-read-3,0.00,0,11
1775072007,2115504,db-read-4,0.00,0,14
1775072007,2115505,db-write,0.00,0,0
1775072007,2115506,event-worker,0.00,0,0
1775072017,2115498,lws-main,38.10,381,5463
1775072017,2115501,db-read-1,0.10,1,14
1775072017,2115502,db-read-2,0.20,2,15
1775072017,2115503,db-read-3,0.40,4,15
1775072017,2115504,db-read-4,0.10,1,15
1775072017,2115505,db-write,0.00,0,0
1775072017,2115506,event-worker,0.00,0,0
1775072027,2115498,lws-main,93.20,932,6395
1775072027,2115501,db-read-1,0.40,4,18
1775072027,2115502,db-read-2,0.40,4,19
1775072027,2115503,db-read-3,0.00,0,15
1775072027,2115504,db-read-4,0.10,1,16
1775072027,2115505,db-write,0.00,0,0
1775072027,2115506,event-worker,0.00,0,0
1775072037,2115498,lws-main,95.10,951,7346
1775072037,2115501,db-read-1,0.10,1,19
1775072037,2115502,db-read-2,0.20,2,21
1775072037,2115503,db-read-3,0.30,3,18
1775072037,2115504,db-read-4,0.00,0,16
1775072037,2115505,db-write,0.10,1,1
1775072037,2115506,event-worker,0.00,0,0
1775072047,2115498,lws-main,65.00,650,7996
1775072047,2115501,db-read-1,0.00,0,19
1775072047,2115502,db-read-2,0.00,0,21
1775072047,2115503,db-read-3,0.10,1,19
1775072047,2115504,db-read-4,0.10,1,17
1775072047,2115505,db-write,0.00,0,1
1775072047,2115506,event-worker,0.00,0,0
1775072057,2115498,lws-main,62.80,628,8624
1775072057,2115501,db-read-1,0.00,0,19
1775072057,2115502,db-read-2,0.00,0,21
1775072057,2115503,db-read-3,0.00,0,19
1775072057,2115504,db-read-4,0.10,1,18
1775072057,2115505,db-write,0.00,0,1
1775072057,2115506,event-worker,0.00,0,0
1775072068,2115498,lws-main,20.80,208,8832
1775072068,2115501,db-read-1,0.10,1,20
1775072068,2115502,db-read-2,0.00,0,21
1775072068,2115503,db-read-3,0.00,0,19
1775072068,2115504,db-read-4,0.00,0,18
1775072068,2115505,db-write,0.00,0,1
1775072068,2115506,event-worker,0.00,0,0
1775072078,2115498,lws-main,52.30,523,9355
1775072078,2115501,db-read-1,0.10,1,21
1775072078,2115502,db-read-2,0.50,5,26
1775072078,2115503,db-read-3,0.10,1,20
1775072078,2115504,db-read-4,0.10,1,19
1775072078,2115505,db-write,0.00,0,1
1775072078,2115506,event-worker,0.00,0,0
1775072088,2115498,lws-main,33.40,334,9689
1775072088,2115501,db-read-1,0.10,1,22
1775072088,2115502,db-read-2,0.30,3,29
1775072088,2115503,db-read-3,0.10,1,21
1775072088,2115504,db-read-4,0.10,1,20
1775072088,2115505,db-write,0.00,0,1
1775072088,2115506,event-worker,0.00,0,0
1775072098,2115498,lws-main,75.30,753,10442
1775072098,2115501,db-read-1,0.00,0,22
1775072098,2115502,db-read-2,0.00,0,29
1775072098,2115503,db-read-3,0.00,0,21
1775072098,2115504,db-read-4,0.00,0,20
1775072098,2115505,db-write,0.00,0,1
1775072098,2115506,event-worker,0.00,0,0
1775072108,2115498,lws-main,100.20,1002,11444
1775072108,2115501,db-read-1,0.00,0,22
1775072108,2115502,db-read-2,0.00,0,29
1775072108,2115503,db-read-3,0.10,1,22
1775072108,2115504,db-read-4,0.00,0,20
1775072108,2115505,db-write,0.00,0,1
1775072108,2115506,event-worker,0.00,0,0
1775072118,2115498,lws-main,78.40,784,12228
1775072118,2115501,db-read-1,0.00,0,22
1775072118,2115502,db-read-2,0.10,1,30
1775072118,2115503,db-read-3,0.00,0,22
1775072118,2115504,db-read-4,0.00,0,20
1775072118,2115505,db-write,0.00,0,1
1775072118,2115506,event-worker,0.00,0,0
1775072128,2115498,lws-main,27.80,278,12506
1775072128,2115501,db-read-1,0.00,0,22
1775072128,2115502,db-read-2,0.00,0,30
1775072128,2115503,db-read-3,0.00,0,22
1775072128,2115504,db-read-4,0.10,1,21
1775072128,2115505,db-write,0.00,0,1
1775072128,2115506,event-worker,0.00,0,0
1775072138,2115498,lws-main,65.80,658,13164
1775072138,2115501,db-read-1,0.40,4,26
1775072138,2115502,db-read-2,0.30,3,33
1775072138,2115503,db-read-3,0.20,2,24
1775072138,2115504,db-read-4,0.30,3,24
1775072138,2115505,db-write,0.00,0,1
1775072138,2115506,event-worker,0.00,0,0
1775072148,2115498,lws-main,30.70,307,13471
1775072148,2115501,db-read-1,0.00,0,26
1775072148,2115502,db-read-2,0.00,0,33
1775072148,2115503,db-read-3,0.00,0,24
1775072148,2115504,db-read-4,0.10,1,25
1775072148,2115505,db-write,0.00,0,1
1775072148,2115506,event-worker,0.00,0,0
1775072158,2115498,lws-main,68.80,688,14159
1775072158,2115501,db-read-1,0.40,4,30
1775072158,2115502,db-read-2,0.30,3,36
1775072158,2115503,db-read-3,0.10,1,25
1775072158,2115504,db-read-4,0.20,2,27
1775072158,2115505,db-write,0.00,0,1
1775072158,2115506,event-worker,0.00,0,0
1775072168,2115498,lws-main,38.20,382,14541
1775072168,2115501,db-read-1,0.00,0,30
1775072168,2115502,db-read-2,0.00,0,36
1775072168,2115503,db-read-3,0.00,0,25
1775072168,2115504,db-read-4,0.00,0,27
1775072168,2115505,db-write,0.00,0,1
1775072168,2115506,event-worker,0.00,0,0
1775072178,2115498,lws-main,52.20,522,15063
1775072178,2115501,db-read-1,0.10,1,31
1775072178,2115502,db-read-2,0.10,1,37
1775072178,2115503,db-read-3,0.20,2,27
1775072178,2115504,db-read-4,0.30,3,30
1775072178,2115505,db-write,0.00,0,1
1775072178,2115506,event-worker,0.00,0,0
1775072188,2115498,lws-main,78.30,783,15846
1775072188,2115501,db-read-1,0.10,1,32
1775072188,2115502,db-read-2,0.10,1,38
1775072188,2115503,db-read-3,0.00,0,27
1775072188,2115504,db-read-4,0.10,1,31
1775072188,2115505,db-write,0.00,0,1
1775072188,2115506,event-worker,0.00,0,0
1775072198,2115498,lws-main,96.90,969,16815
1775072198,2115501,db-read-1,0.10,1,33
1775072198,2115502,db-read-2,0.00,0,38
1775072198,2115503,db-read-3,0.10,1,28
1775072198,2115504,db-read-4,0.10,1,32
1775072198,2115505,db-write,0.00,0,1
1775072198,2115506,event-worker,0.00,0,0
1775072208,2115498,lws-main,87.80,878,17693
1775072208,2115501,db-read-1,0.10,1,34
1775072208,2115502,db-read-2,0.10,1,39
1775072208,2115503,db-read-3,0.10,1,29
1775072208,2115504,db-read-4,0.00,0,32
1775072208,2115505,db-write,0.00,0,1
1775072208,2115506,event-worker,0.00,0,0
1775072218,2115498,lws-main,77.20,772,18465
1775072218,2115501,db-read-1,0.00,0,34
1775072218,2115502,db-read-2,0.10,1,40
1775072218,2115503,db-read-3,0.10,1,30
1775072218,2115504,db-read-4,0.00,0,32
1775072218,2115505,db-write,0.00,0,1
1775072218,2115506,event-worker,0.00,0,0
1775072228,2115498,lws-main,21.70,217,18682
1775072228,2115501,db-read-1,0.00,0,34
1775072228,2115502,db-read-2,0.00,0,40
1775072228,2115503,db-read-3,0.00,0,30
1775072228,2115504,db-read-4,0.00,0,32
1775072228,2115505,db-write,0.00,0,1
1775072228,2115506,event-worker,0.00,0,0
1 timestamp tid thread_name cpu_pct delta_ticks total_ticks
2 1775071937 2115498 lws-main 0.00 0 2521
3 1775071937 2115501 db-read-1 0.00 0 9
4 1775071937 2115502 db-read-2 0.00 0 10
5 1775071937 2115503 db-read-3 0.00 0 9
6 1775071937 2115504 db-read-4 0.00 0 9
7 1775071937 2115505 db-write 0.00 0 0
8 1775071937 2115506 event-worker 0.00 0 0
9 1775071947 2115498 lws-main 0.10 1 2522
10 1775071947 2115501 db-read-1 0.10 1 10
11 1775071947 2115502 db-read-2 0.00 0 10
12 1775071947 2115503 db-read-3 0.00 0 9
13 1775071947 2115504 db-read-4 0.30 3 12
14 1775071947 2115505 db-write 0.00 0 0
15 1775071947 2115506 event-worker 0.00 0 0
16 1775071957 2115498 lws-main 26.80 268 2790
17 1775071957 2115501 db-read-1 0.00 0 10
18 1775071957 2115502 db-read-2 0.10 1 11
19 1775071957 2115503 db-read-3 0.00 0 9
20 1775071957 2115504 db-read-4 0.00 0 12
21 1775071957 2115505 db-write 0.00 0 0
22 1775071957 2115506 event-worker 0.00 0 0
23 1775071967 2115498 lws-main 72.10 721 3511
24 1775071967 2115501 db-read-1 0.00 0 10
25 1775071967 2115502 db-read-2 0.00 0 11
26 1775071967 2115503 db-read-3 0.00 0 9
27 1775071967 2115504 db-read-4 0.00 0 12
28 1775071967 2115505 db-write 0.00 0 0
29 1775071967 2115506 event-worker 0.00 0 0
30 1775071977 2115498 lws-main 64.40 644 4155
31 1775071977 2115501 db-read-1 0.20 2 12
32 1775071977 2115502 db-read-2 0.10 1 12
33 1775071977 2115503 db-read-3 0.10 1 10
34 1775071977 2115504 db-read-4 0.20 2 14
35 1775071977 2115505 db-write 0.00 0 0
36 1775071977 2115506 event-worker 0.00 0 0
37 1775071987 2115498 lws-main 42.60 426 4581
38 1775071987 2115501 db-read-1 0.10 1 13
39 1775071987 2115502 db-read-2 0.10 1 13
40 1775071987 2115503 db-read-3 0.10 1 11
41 1775071987 2115504 db-read-4 0.00 0 14
42 1775071987 2115505 db-write 0.00 0 0
43 1775071987 2115506 event-worker 0.00 0 0
44 1775071997 2115498 lws-main 7.20 72 4653
45 1775071997 2115501 db-read-1 0.00 0 13
46 1775071997 2115502 db-read-2 0.00 0 13
47 1775071997 2115503 db-read-3 0.00 0 11
48 1775071997 2115504 db-read-4 0.00 0 14
49 1775071997 2115505 db-write 0.00 0 0
50 1775071997 2115506 event-worker 0.00 0 0
51 1775072007 2115498 lws-main 42.90 429 5082
52 1775072007 2115501 db-read-1 0.00 0 13
53 1775072007 2115502 db-read-2 0.00 0 13
54 1775072007 2115503 db-read-3 0.00 0 11
55 1775072007 2115504 db-read-4 0.00 0 14
56 1775072007 2115505 db-write 0.00 0 0
57 1775072007 2115506 event-worker 0.00 0 0
58 1775072017 2115498 lws-main 38.10 381 5463
59 1775072017 2115501 db-read-1 0.10 1 14
60 1775072017 2115502 db-read-2 0.20 2 15
61 1775072017 2115503 db-read-3 0.40 4 15
62 1775072017 2115504 db-read-4 0.10 1 15
63 1775072017 2115505 db-write 0.00 0 0
64 1775072017 2115506 event-worker 0.00 0 0
65 1775072027 2115498 lws-main 93.20 932 6395
66 1775072027 2115501 db-read-1 0.40 4 18
67 1775072027 2115502 db-read-2 0.40 4 19
68 1775072027 2115503 db-read-3 0.00 0 15
69 1775072027 2115504 db-read-4 0.10 1 16
70 1775072027 2115505 db-write 0.00 0 0
71 1775072027 2115506 event-worker 0.00 0 0
72 1775072037 2115498 lws-main 95.10 951 7346
73 1775072037 2115501 db-read-1 0.10 1 19
74 1775072037 2115502 db-read-2 0.20 2 21
75 1775072037 2115503 db-read-3 0.30 3 18
76 1775072037 2115504 db-read-4 0.00 0 16
77 1775072037 2115505 db-write 0.10 1 1
78 1775072037 2115506 event-worker 0.00 0 0
79 1775072047 2115498 lws-main 65.00 650 7996
80 1775072047 2115501 db-read-1 0.00 0 19
81 1775072047 2115502 db-read-2 0.00 0 21
82 1775072047 2115503 db-read-3 0.10 1 19
83 1775072047 2115504 db-read-4 0.10 1 17
84 1775072047 2115505 db-write 0.00 0 1
85 1775072047 2115506 event-worker 0.00 0 0
86 1775072057 2115498 lws-main 62.80 628 8624
87 1775072057 2115501 db-read-1 0.00 0 19
88 1775072057 2115502 db-read-2 0.00 0 21
89 1775072057 2115503 db-read-3 0.00 0 19
90 1775072057 2115504 db-read-4 0.10 1 18
91 1775072057 2115505 db-write 0.00 0 1
92 1775072057 2115506 event-worker 0.00 0 0
93 1775072068 2115498 lws-main 20.80 208 8832
94 1775072068 2115501 db-read-1 0.10 1 20
95 1775072068 2115502 db-read-2 0.00 0 21
96 1775072068 2115503 db-read-3 0.00 0 19
97 1775072068 2115504 db-read-4 0.00 0 18
98 1775072068 2115505 db-write 0.00 0 1
99 1775072068 2115506 event-worker 0.00 0 0
100 1775072078 2115498 lws-main 52.30 523 9355
101 1775072078 2115501 db-read-1 0.10 1 21
102 1775072078 2115502 db-read-2 0.50 5 26
103 1775072078 2115503 db-read-3 0.10 1 20
104 1775072078 2115504 db-read-4 0.10 1 19
105 1775072078 2115505 db-write 0.00 0 1
106 1775072078 2115506 event-worker 0.00 0 0
107 1775072088 2115498 lws-main 33.40 334 9689
108 1775072088 2115501 db-read-1 0.10 1 22
109 1775072088 2115502 db-read-2 0.30 3 29
110 1775072088 2115503 db-read-3 0.10 1 21
111 1775072088 2115504 db-read-4 0.10 1 20
112 1775072088 2115505 db-write 0.00 0 1
113 1775072088 2115506 event-worker 0.00 0 0
114 1775072098 2115498 lws-main 75.30 753 10442
115 1775072098 2115501 db-read-1 0.00 0 22
116 1775072098 2115502 db-read-2 0.00 0 29
117 1775072098 2115503 db-read-3 0.00 0 21
118 1775072098 2115504 db-read-4 0.00 0 20
119 1775072098 2115505 db-write 0.00 0 1
120 1775072098 2115506 event-worker 0.00 0 0
121 1775072108 2115498 lws-main 100.20 1002 11444
122 1775072108 2115501 db-read-1 0.00 0 22
123 1775072108 2115502 db-read-2 0.00 0 29
124 1775072108 2115503 db-read-3 0.10 1 22
125 1775072108 2115504 db-read-4 0.00 0 20
126 1775072108 2115505 db-write 0.00 0 1
127 1775072108 2115506 event-worker 0.00 0 0
128 1775072118 2115498 lws-main 78.40 784 12228
129 1775072118 2115501 db-read-1 0.00 0 22
130 1775072118 2115502 db-read-2 0.10 1 30
131 1775072118 2115503 db-read-3 0.00 0 22
132 1775072118 2115504 db-read-4 0.00 0 20
133 1775072118 2115505 db-write 0.00 0 1
134 1775072118 2115506 event-worker 0.00 0 0
135 1775072128 2115498 lws-main 27.80 278 12506
136 1775072128 2115501 db-read-1 0.00 0 22
137 1775072128 2115502 db-read-2 0.00 0 30
138 1775072128 2115503 db-read-3 0.00 0 22
139 1775072128 2115504 db-read-4 0.10 1 21
140 1775072128 2115505 db-write 0.00 0 1
141 1775072128 2115506 event-worker 0.00 0 0
142 1775072138 2115498 lws-main 65.80 658 13164
143 1775072138 2115501 db-read-1 0.40 4 26
144 1775072138 2115502 db-read-2 0.30 3 33
145 1775072138 2115503 db-read-3 0.20 2 24
146 1775072138 2115504 db-read-4 0.30 3 24
147 1775072138 2115505 db-write 0.00 0 1
148 1775072138 2115506 event-worker 0.00 0 0
149 1775072148 2115498 lws-main 30.70 307 13471
150 1775072148 2115501 db-read-1 0.00 0 26
151 1775072148 2115502 db-read-2 0.00 0 33
152 1775072148 2115503 db-read-3 0.00 0 24
153 1775072148 2115504 db-read-4 0.10 1 25
154 1775072148 2115505 db-write 0.00 0 1
155 1775072148 2115506 event-worker 0.00 0 0
156 1775072158 2115498 lws-main 68.80 688 14159
157 1775072158 2115501 db-read-1 0.40 4 30
158 1775072158 2115502 db-read-2 0.30 3 36
159 1775072158 2115503 db-read-3 0.10 1 25
160 1775072158 2115504 db-read-4 0.20 2 27
161 1775072158 2115505 db-write 0.00 0 1
162 1775072158 2115506 event-worker 0.00 0 0
163 1775072168 2115498 lws-main 38.20 382 14541
164 1775072168 2115501 db-read-1 0.00 0 30
165 1775072168 2115502 db-read-2 0.00 0 36
166 1775072168 2115503 db-read-3 0.00 0 25
167 1775072168 2115504 db-read-4 0.00 0 27
168 1775072168 2115505 db-write 0.00 0 1
169 1775072168 2115506 event-worker 0.00 0 0
170 1775072178 2115498 lws-main 52.20 522 15063
171 1775072178 2115501 db-read-1 0.10 1 31
172 1775072178 2115502 db-read-2 0.10 1 37
173 1775072178 2115503 db-read-3 0.20 2 27
174 1775072178 2115504 db-read-4 0.30 3 30
175 1775072178 2115505 db-write 0.00 0 1
176 1775072178 2115506 event-worker 0.00 0 0
177 1775072188 2115498 lws-main 78.30 783 15846
178 1775072188 2115501 db-read-1 0.10 1 32
179 1775072188 2115502 db-read-2 0.10 1 38
180 1775072188 2115503 db-read-3 0.00 0 27
181 1775072188 2115504 db-read-4 0.10 1 31
182 1775072188 2115505 db-write 0.00 0 1
183 1775072188 2115506 event-worker 0.00 0 0
184 1775072198 2115498 lws-main 96.90 969 16815
185 1775072198 2115501 db-read-1 0.10 1 33
186 1775072198 2115502 db-read-2 0.00 0 38
187 1775072198 2115503 db-read-3 0.10 1 28
188 1775072198 2115504 db-read-4 0.10 1 32
189 1775072198 2115505 db-write 0.00 0 1
190 1775072198 2115506 event-worker 0.00 0 0
191 1775072208 2115498 lws-main 87.80 878 17693
192 1775072208 2115501 db-read-1 0.10 1 34
193 1775072208 2115502 db-read-2 0.10 1 39
194 1775072208 2115503 db-read-3 0.10 1 29
195 1775072208 2115504 db-read-4 0.00 0 32
196 1775072208 2115505 db-write 0.00 0 1
197 1775072208 2115506 event-worker 0.00 0 0
198 1775072218 2115498 lws-main 77.20 772 18465
199 1775072218 2115501 db-read-1 0.00 0 34
200 1775072218 2115502 db-read-2 0.10 1 40
201 1775072218 2115503 db-read-3 0.10 1 30
202 1775072218 2115504 db-read-4 0.00 0 32
203 1775072218 2115505 db-write 0.00 0 1
204 1775072218 2115506 event-worker 0.00 0 0
205 1775072228 2115498 lws-main 21.70 217 18682
206 1775072228 2115501 db-read-1 0.00 0 34
207 1775072228 2115502 db-read-2 0.00 0 40
208 1775072228 2115503 db-read-3 0.00 0 30
209 1775072228 2115504 db-read-4 0.00 0 32
210 1775072228 2115505 db-write 0.00 0 1
211 1775072228 2115506 event-worker 0.00 0 0
@@ -0,0 +1,14 @@
==========================================
Thread CPU Summary (avg + max over run)
==========================================
Run timestamp (UTC): 20260401_193216
Duration: 300s, Interval: 10s
2115498 lws-main 53.87 100.20
2115502 db-read-2 0.10 0.50
2115501 db-read-1 0.08 0.40
2115504 db-read-4 0.08 0.30
2115503 db-read-3 0.07 0.40
2115505 db-write 0.00 0.10
2115506 event-worker 0.00 0.00
TID THREAD AVG_CPU% MAX_CPU%