Compare commits

...
11 Commits
Author SHA1 Message Date
Laan Tungir ce9ae5a488 v2.1.5 - Offload duplicate and COUNT paths, add config/NIP-11 caching, and reduce DB reader threads 2026-04-01 16:15:46 -04:00
Laan Tungir 9b248b740f v2.1.4 - Harden async EVENT fallback handling and verify relay/nginx 503 behavior 2026-04-01 15:19:28 -04:00
Laan Tungir 0f77aeb72b v2.1.3 - Fix async EVENT thread safety by splitting store core/post-actions and naming lws main thread 2026-04-01 11:29:14 -04:00
Laan Tungir bb64651a0c v2.1.2 - Fix async REQ callback leak and validate Phase 2+3 subscription offload path 2026-04-01 11:01:38 -04:00
Laan Tungir 17be9c2b03 v2.1.1 - Wire REQ/COUNT/EVENT paths through thread pool sync helpers and stabilize filter limit tests 2026-04-01 09:38:25 -04:00
Laan Tungir f396c622b6 v2.1.0 - Bump to v2.1.0 as SQLite baseline for PostgreSQL fork handoff 2026-04-01 09:14:23 -04:00
Laan Tungir cbd260c1ae v2.0.4 - Complete Phase 1 DB abstraction: opaque db_stmt API, ip_ban migration, and SQLite leakage cleanup 2026-04-01 09:10:42 -04:00
Laan Tungir bc9ac290ab v2.0.3 - Complete Phase 1 SQLite abstraction cleanup across config/api/websockets and add db path accessor 2026-04-01 07:47:21 -04:00
Laan Tungir 192eeb248d v2.0.2 - Stabilize websocket queue draining, add db config count helper, and align NIP-45/50 test expectations 2026-04-01 07:04:58 -04:00
Laan Tungir da6c505420 v2.0.1 - Fix test-mode build/restart pipeline and NIP-11 root path handling; validate core NIP tests 2026-04-01 05:43:57 -04:00
Laan Tungir c077c209e5 v2.0.0 - Major architecture refactor: db_ops abstraction, low-risk SQLite refactor, and thread-pool scaffolding 2026-04-01 05:25:03 -04:00
64 changed files with 19382 additions and 2150 deletions
Executable
BIN
View File
Binary file not shown.
+11 -14
View File
@@ -28,7 +28,8 @@ RUN apk add --no-cache \
sqlite-static \
linux-headers \
wget \
bash
bash \
openssh-client
# Set working directory
WORKDIR /build
@@ -68,15 +69,8 @@ RUN cd /tmp && \
make install && \
rm -rf /tmp/libwebsockets
# Copy only submodule configuration and git directory
COPY .gitmodules /build/.gitmodules
COPY .git /build/.git
# Clean up any stale submodule references (nips directory is not a submodule)
RUN git rm --cached nips 2>/dev/null || true
# Initialize submodules (cached unless .gitmodules changes)
RUN git submodule update --init --recursive
# Submodules are provided in the local build context and copied explicitly below.
# Avoid network/SSH dependency inside Docker image build.
# Copy nostr_core_lib source files (cached unless nostr_core_lib changes)
COPY nostr_core_lib /build/nostr_core_lib/
@@ -84,11 +78,13 @@ COPY nostr_core_lib /build/nostr_core_lib/
# Copy c_utils_lib source files (cached unless c_utils_lib changes)
COPY c_utils_lib /build/c_utils_lib/
# Build c_utils_lib with MUSL-compatible flags (cached unless c_utils_lib changes)
# Build c_utils_lib static library for relay logging utilities
# (build only debug.c to avoid broken/missing version header surface in submodule revision)
RUN cd c_utils_lib && \
sed -i 's/CFLAGS = -Wall -Wextra -std=c99 -O2 -g/CFLAGS = -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -O2 -g/' Makefile && \
make clean && \
make
mkdir -p build && \
gcc -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -O2 -g \
-Isrc -Iinclude -c src/debug.c -o build/debug.o && \
ar rcs libc_utils.a build/debug.o
# Build nostr_core_lib with required NIPs (cached unless nostr_core_lib changes)
# Disable fortification in build.sh to prevent __*_chk symbol issues
@@ -122,6 +118,7 @@ RUN if [ "$DEBUG_BUILD" = "true" ]; then \
src/main.c src/config.c src/dm_admin.c src/request_validator.c \
src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c \
src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c src/ip_ban.c \
src/db_ops.c src/thread_pool.c \
-o /build/c_relay_static \
c_utils_lib/libc_utils.a \
nostr_core_lib/libnostr_core_x64.a \
+1 -1
View File
@@ -9,7 +9,7 @@ LIBS = -lsqlite3 -lwebsockets -lz -ldl -lpthread -lm -L/usr/local/lib -lsecp256k
BUILD_DIR = build
# Source files
MAIN_SRC = src/main.c src/config.c src/dm_admin.c src/request_validator.c src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c src/ip_ban.c
MAIN_SRC = src/main.c src/config.c src/dm_admin.c src/request_validator.c src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c src/ip_ban.c src/db_ops.c src/thread_pool.c
NOSTR_CORE_LIB = nostr_core_lib/libnostr_core_x64.a
C_UTILS_LIB = c_utils_lib/libc_utils.a
+1
Submodule nostr-rs-relay added at 64cfcaf44a
+632
View File
@@ -0,0 +1,632 @@
# c-relay-pg Plan — PostgreSQL Backend + Multi-Instance + Dashboard
## Overview
**c-relay-pg** is a new project (separate repository) forked from c-relay after the `db_ops` abstraction layer is complete. It replaces the SQLite backend with PostgreSQL, enabling horizontal scaling, external dashboards, and production-grade deployments.
### Prerequisites
- The `db_ops.h` / `db_ops.c` abstraction layer must be complete in c-relay first — see [c-relay thread pool plan](c_relay_thread_pool_plan.md) Phase 1.
- The fork happens after Phase 1 of that plan is done and tested.
### Why a Separate Project?
| | c-relay | c-relay-pg |
|---|---------|-----------|
| **Database** | SQLite (embedded) | PostgreSQL (client-server) |
| **Deployment** | Single binary + DB file | Binary + PostgreSQL server |
| **Scaling** | Single instance | Multiple instances + load balancer |
| **Dashboard** | Embedded web UI | Separate web server + Grafana |
| **Target user** | Personal relay, small community | Medium-large relay, production |
| **Complexity** | Minimal | More infrastructure |
For the full analysis of why PostgreSQL was chosen over MySQL/MariaDB and other databases, see the [database architecture analysis](database_architecture_analysis.md).
## Architecture
```mermaid
flowchart TD
subgraph c-relay-pg - Production Deployment
LB[nginx - TLS + load balancing] -->|WebSocket| R1[c-relay-pg Instance 1]
LB -->|WebSocket| R2[c-relay-pg Instance 2]
LB -->|HTTP| DASH[Dashboard]
R1 -->|db_ops API| PGBACK[PostgreSQL Backend - db_ops_postgres.c]
R2 -->|db_ops API| PGBACK
PGBACK -->|libpq| PG[PostgreSQL Server]
DASH -->|SQL| PG
R1 <-->|LISTEN/NOTIFY| R2
end
```
---
## Phase 1: PostgreSQL Backend
### Goal
Fork c-relay into a new repository called **c-relay-pg**. Replace the SQLite `db_ops` implementation with PostgreSQL using `libpq`. The `db_ops.h` interface stays identical — only the backend changes.
### New Files
- `src/db_ops_sqlite.c` — Renamed from `db_ops.c` (the Phase 1 SQLite implementation from c-relay)
- `src/db_ops_postgres.c` — New PostgreSQL implementation
- `src/db_ops.c` — Thin dispatcher that calls the active backend
### PostgreSQL Schema
```sql
-- PostgreSQL schema for c-relay-pg
-- No event_tags table needed — JSONB + GIN handles everything
CREATE TABLE events (
id TEXT PRIMARY KEY,
pubkey TEXT NOT NULL,
created_at BIGINT NOT NULL,
kind INTEGER NOT NULL,
event_type TEXT NOT NULL CHECK (event_type IN ('regular', 'replaceable', 'ephemeral', 'addressable')),
content TEXT NOT NULL,
sig TEXT NOT NULL,
tags JSONB NOT NULL DEFAULT '[]',
event_json TEXT NOT NULL,
first_seen BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT
);
-- Core indexes
CREATE INDEX idx_events_pubkey ON events(pubkey);
CREATE INDEX idx_events_kind ON events(kind);
CREATE INDEX idx_events_created_at ON events(created_at DESC);
CREATE INDEX idx_events_kind_created_at ON events(kind, created_at DESC);
CREATE INDEX idx_events_pubkey_created_at ON events(pubkey, created_at DESC);
CREATE INDEX idx_events_pubkey_kind ON events(pubkey, kind);
-- THE KEY INDEX: GIN on JSONB tags — replaces the entire event_tags table
CREATE INDEX idx_events_tags ON events USING GIN (tags);
-- Partial index: only non-ephemeral events (what REQ queries actually filter)
CREATE INDEX idx_events_non_ephemeral ON events(created_at DESC)
WHERE kind < 20000 OR kind >= 30000;
-- Config table
CREATE TABLE config (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
data_type TEXT NOT NULL CHECK (data_type IN ('string', 'integer', 'boolean', 'json')),
description TEXT,
category TEXT DEFAULT 'general',
requires_restart INTEGER DEFAULT 0,
created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT,
updated_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT
);
-- Auth rules table
CREATE TABLE auth_rules (
id SERIAL PRIMARY KEY,
rule_type TEXT NOT NULL CHECK (rule_type IN ('whitelist', 'blacklist', 'rate_limit', 'auth_required', 'wot_whitelist')),
pattern_type TEXT NOT NULL CHECK (pattern_type IN ('pubkey', 'kind', 'ip', 'global')),
pattern_value TEXT,
active INTEGER NOT NULL DEFAULT 1,
created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT,
updated_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT
);
CREATE INDEX idx_auth_rules_lookup ON auth_rules(rule_type, pattern_type, pattern_value) WHERE active = 1;
-- Relay private key storage
CREATE TABLE relay_seckey (
private_key_hex TEXT NOT NULL CHECK (length(private_key_hex) = 64),
created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT
);
-- Subscription logging
CREATE TABLE subscriptions (
id SERIAL PRIMARY KEY,
subscription_id TEXT NOT NULL,
wsi_pointer TEXT NOT NULL,
client_ip TEXT NOT NULL,
event_type TEXT NOT NULL CHECK (event_type IN ('created', 'closed', 'expired', 'disconnected')),
filter_json TEXT,
events_sent INTEGER DEFAULT 0,
created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::BIGINT,
ended_at BIGINT,
duration INTEGER,
UNIQUE(subscription_id, wsi_pointer)
);
-- IP ban persistence
CREATE TABLE ip_bans (
ip TEXT PRIMARY KEY,
failure_count INTEGER NOT NULL DEFAULT 0,
ban_count INTEGER NOT NULL DEFAULT 0,
banned_until BIGINT NOT NULL DEFAULT 0,
first_failure BIGINT NOT NULL DEFAULT 0,
has_authed_successfully INTEGER NOT NULL DEFAULT 0,
last_success_at BIGINT NOT NULL DEFAULT 0,
total_connections INTEGER NOT NULL DEFAULT 0,
total_failures INTEGER NOT NULL DEFAULT 0,
total_successes INTEGER NOT NULL DEFAULT 0,
first_seen BIGINT NOT NULL DEFAULT 0,
idle_failure_count INTEGER NOT NULL DEFAULT 0,
idle_ban_count INTEGER NOT NULL DEFAULT 0,
idle_banned_until BIGINT NOT NULL DEFAULT 0,
idle_first_failure BIGINT NOT NULL DEFAULT 0
);
-- Materialized views for dashboard (refreshed periodically)
CREATE MATERIALIZED VIEW event_kinds_mv AS
SELECT kind, COUNT(*) as count,
ROUND(COUNT(*) * 100.0 / NULLIF((SELECT COUNT(*) FROM events), 0), 2) as percentage
FROM events GROUP BY kind;
CREATE MATERIALIZED VIEW time_stats_mv AS
SELECT 'total' as period, COUNT(*) as total_events, COUNT(DISTINCT pubkey) as unique_pubkeys
FROM events
UNION ALL
SELECT '24h', COUNT(*), COUNT(DISTINCT pubkey)
FROM events WHERE created_at >= EXTRACT(EPOCH FROM NOW())::BIGINT - 86400
UNION ALL
SELECT '7d', COUNT(*), COUNT(DISTINCT pubkey)
FROM events WHERE created_at >= EXTRACT(EPOCH FROM NOW())::BIGINT - 604800;
CREATE MATERIALIZED VIEW top_pubkeys_mv AS
SELECT pubkey, COUNT(*) as event_count,
ROUND(COUNT(*) * 100.0 / NULLIF((SELECT COUNT(*) FROM events), 0), 2) as percentage
FROM events GROUP BY pubkey ORDER BY event_count DESC LIMIT 20;
-- Unique indexes required for CONCURRENTLY refresh
CREATE UNIQUE INDEX idx_event_kinds_mv ON event_kinds_mv(kind);
CREATE UNIQUE INDEX idx_time_stats_mv ON time_stats_mv(period);
CREATE UNIQUE INDEX idx_top_pubkeys_mv ON top_pubkeys_mv(pubkey);
```
### Key Implementation Details
#### Tag Queries with JSONB
The biggest win — replacing the `event_tags` subquery with JSONB containment:
```c
// SQLite (current): subquery into event_tags table
// AND id IN (SELECT event_id FROM event_tags WHERE tag_name = ? AND tag_value IN (?))
// PostgreSQL: JSONB containment operator with GIN index
// AND tags @> '[["p", "pubkey_hex"]]'
// For multiple tag values:
// AND (tags @> '[["p", "val1"]]' OR tags @> '[["p", "val2"]]')
```
This eliminates the entire `event_tags` table (4 million rows, ~2 GB indexes in the current SQLite schema). The GIN index on JSONB handles everything in ~100200 MB.
#### LISTEN/NOTIFY Integration
For cross-instance event broadcasting:
```c
// In db_ops_postgres.c:
int db_notify_new_event(const char* event_id) {
char cmd[128];
snprintf(cmd, sizeof(cmd), "NOTIFY new_event, '%s'", event_id);
PGresult* res = PQexec(g_pg_conn, cmd);
PQclear(res);
return 0;
}
// Called from the lws event loop every iteration:
int db_check_notifications(char* event_id_out, size_t max_len) {
PQconsumeInput(g_pg_notify_conn);
PGnotify* notify = PQnotifies(g_pg_notify_conn);
if (notify) {
strncpy(event_id_out, notify->extra, max_len);
PQfreemem(notify);
return 1; // Got a notification
}
return 0; // No notifications
}
```
#### Connection Management
```c
// db_ops_postgres.c connection pool (simple version)
#define DB_POOL_SIZE 4
static PGconn* g_pg_read_pool[DB_POOL_SIZE]; // For REQ queries
static PGconn* g_pg_write_conn; // For EVENT inserts
static PGconn* g_pg_notify_conn; // For LISTEN/NOTIFY
static pthread_mutex_t g_pool_lock;
PGconn* db_get_read_connection(void) {
pthread_mutex_lock(&g_pool_lock);
// Round-robin or find idle connection
// ...
pthread_mutex_unlock(&g_pool_lock);
}
```
### Build System Changes
```makefile
# Makefile additions
DB_BACKEND ?= sqlite # Default to sqlite, override with: make DB_BACKEND=postgres
ifeq ($(DB_BACKEND),postgres)
MAIN_SRC += src/db_ops.c src/db_ops_postgres.c
LIBS += -lpq
CFLAGS += -DDB_BACKEND_POSTGRES
else
MAIN_SRC += src/db_ops.c src/db_ops_sqlite.c
CFLAGS += -DDB_BACKEND_SQLITE
endif
```
### Data Migration Tool
A standalone tool to migrate existing SQLite data to PostgreSQL:
```bash
# migrate_to_postgres.sh
# 1. Export events from SQLite
sqlite3 old_relay.db ".mode csv" "SELECT id,pubkey,created_at,kind,event_type,content,sig,tags,event_json,first_seen FROM events" > events.csv
# 2. Import into PostgreSQL
psql -d crelay -c "\COPY events FROM 'events.csv' WITH CSV"
# 3. Migrate config
sqlite3 old_relay.db ".mode csv" "SELECT key,value,data_type,description,category,requires_restart FROM config" > config.csv
psql -d crelay -c "\COPY config(key,value,data_type,description,category,requires_restart) FROM 'config.csv' WITH CSV"
# 4. Migrate auth rules
sqlite3 old_relay.db ".mode csv" "SELECT rule_type,pattern_type,pattern_value,active FROM auth_rules" > auth_rules.csv
psql -d crelay -c "\COPY auth_rules(rule_type,pattern_type,pattern_value,active) FROM 'auth_rules.csv' WITH CSV"
# 5. Refresh materialized views
psql -d crelay -c "REFRESH MATERIALIZED VIEW event_kinds_mv; REFRESH MATERIALIZED VIEW time_stats_mv; REFRESH MATERIALIZED VIEW top_pubkeys_mv;"
```
---
## Phase 2: Multi-Instance + Dashboard
### Goal
Run multiple c-relay-pg instances behind a load balancer with a separate dashboard web server, all sharing the same PostgreSQL database.
### Components
1. **nginx config** — WebSocket load balancing with `ip_hash` stickiness
2. **systemd template**`c-relay-pg@.service` for multiple instances
3. **LISTEN/NOTIFY integration** — Cross-instance event broadcasting in the `lws_service()` loop
4. **PgBouncer** — Connection pooling between c-relay-pg instances and PostgreSQL
5. **Dashboard** — Separate web server querying PostgreSQL directly
6. **Materialized view refresh** — Background job to refresh analytics views
### Multi-Instance Architecture
```mermaid
flowchart TD
INTERNET[Internet - Nostr Clients] -->|wss://relay.example.com| NGINX[nginx reverse proxy - TLS termination + load balancing]
NGINX -->|ws://localhost:8888| R1[c-relay-pg Instance 1 - port 8888]
NGINX -->|ws://localhost:8889| R2[c-relay-pg Instance 2 - port 8889]
NGINX -->|ws://localhost:8890| R3[c-relay-pg Instance 3 - port 8890]
NGINX -->|http://localhost:3000| DASHWEB[Dashboard Web Server]
R1 -->|libpq connection pool| PGPOOL[PgBouncer - connection pooler]
R2 -->|libpq connection pool| PGPOOL
R3 -->|libpq connection pool| PGPOOL
PGPOOL -->|pooled connections| PG[PostgreSQL Primary]
DASHWEB -->|read queries| PG
PG -->|streaming replication| REPLICA[Read Replica - optional]
DASHWEB -.->|heavy analytics| REPLICA
```
### nginx Load Balancing Config
```nginx
# nginx.conf - WebSocket load balancing for c-relay-pg
upstream relay_backends {
# ip_hash ensures a client always hits the same instance
# (important for WebSocket session stickiness)
ip_hash;
server 127.0.0.1:8888;
server 127.0.0.1:8889;
server 127.0.0.1:8890;
}
server {
listen 443 ssl;
server_name relay.example.com;
# TLS config...
location / {
proxy_pass http://relay_backends;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400; # Keep WebSocket alive for 24h
}
# Dashboard on separate path
location /dashboard {
proxy_pass http://127.0.0.1:3000;
}
}
```
**Session stickiness** (`ip_hash`) ensures that once a client connects to Instance 2, all their subsequent WebSocket frames go to Instance 2. This is important because subscriptions are held in-memory per instance.
### Starting Multiple Instances
Each instance is the same binary, just on a different port, all pointing to the same PostgreSQL:
```bash
# Instance 1
./build/c_relay_x86 --port 8888 --db-host localhost --db-name crelay &
# Instance 2
./build/c_relay_x86 --port 8889 --db-host localhost --db-name crelay &
# Instance 3
./build/c_relay_x86 --port 8890 --db-host localhost --db-name crelay &
```
Or with systemd template units:
```ini
# /etc/systemd/system/c-relay-pg@.service
[Unit]
Description=C-Relay-PG Nostr Instance %i
After=postgresql.service
[Service]
ExecStart=/opt/c-relay-pg/c_relay_x86 --port %i --db-host localhost --db-name crelay
Restart=always
User=c-relay
[Install]
WantedBy=multi-user.target
```
```bash
systemctl enable c-relay-pg@8888 c-relay-pg@8889 c-relay-pg@8890
systemctl start c-relay-pg@8888 c-relay-pg@8889 c-relay-pg@8890
```
### Cross-Instance Event Broadcasting
When Instance 1 receives a new EVENT and stores it in PostgreSQL, Instance 2 and Instance 3 need to know about it so they can broadcast to their connected subscribers.
```mermaid
sequenceDiagram
participant Client_A as Client A - connected to Instance 1
participant I1 as Instance 1
participant PG as PostgreSQL
participant I2 as Instance 2
participant I3 as Instance 3
participant Client_B as Client B - connected to Instance 2
participant Client_C as Client C - connected to Instance 3
Client_A->>I1: EVENT - new kind:1 note
I1->>PG: INSERT INTO events...
PG-->>I1: OK
I1->>I1: broadcast to local subscribers
I1->>PG: NOTIFY new_event with event_id
Note over PG: PostgreSQL delivers notification to all listeners
PG-->>I2: NOTIFY: new_event event_id
PG-->>I3: NOTIFY: new_event event_id
I2->>PG: SELECT event_json FROM events WHERE id = event_id
PG-->>I2: event JSON
I2->>I2: match against local subscriptions
I2->>Client_B: EVENT message - if subscription matches
I3->>PG: SELECT event_json FROM events WHERE id = event_id
PG-->>I3: event JSON
I3->>I3: match against local subscriptions
I3->>Client_C: EVENT message - if subscription matches
```
#### PostgreSQL LISTEN/NOTIFY Implementation
Built into PostgreSQL — no additional infrastructure needed:
```c
// === In the relay's event loop (modified lws_service loop) ===
// Setup: create a dedicated connection for LISTEN
PGconn* notify_conn = PQconnectdb("host=localhost dbname=crelay");
PQexec(notify_conn, "LISTEN new_event");
int notify_fd = PQsocket(notify_conn); // Get the socket fd for poll()
// After storing an event:
void on_event_stored(PGconn* write_conn, const char* event_id) {
char notify_cmd[128];
snprintf(notify_cmd, sizeof(notify_cmd),
"NOTIFY new_event, '%s'", event_id);
PQexec(write_conn, notify_cmd);
}
// In the main event loop (runs every lws_service iteration):
void check_cross_instance_events(PGconn* notify_conn) {
// Non-blocking check for notifications
PQconsumeInput(notify_conn);
PGnotify* notify;
while ((notify = PQnotifies(notify_conn)) != NULL) {
// Another instance stored a new event
const char* event_id = notify->extra;
// Fetch the event and check against local subscriptions
cJSON* event = db_get_event_by_id(event_id);
if (event) {
broadcast_event_to_subscriptions(event);
cJSON_Delete(event);
}
PQfreemem(notify);
}
}
```
**Performance**: LISTEN/NOTIFY adds ~15ms latency for cross-instance delivery. For a Nostr relay, this is imperceptible — clients already expect network latency.
**Payload limit**: NOTIFY payloads are limited to 8000 bytes. For event IDs (64 hex chars), this is fine.
#### Alternative: Redis Pub/Sub
If you later need even lower latency or more sophisticated routing:
```c
// Using hiredis (Redis C client)
redisContext* redis = redisConnect("127.0.0.1", 6379);
// After storing event:
redisCommand(redis, "PUBLISH new_event %s", event_json);
// Subscriber (in each instance):
redisCommand(redis, "SUBSCRIBE new_event");
// Then poll for messages in the event loop
```
Redis adds sub-millisecond pub/sub but requires running a Redis server. For most relays, PostgreSQL LISTEN/NOTIFY is sufficient.
### Connection Pooling with PgBouncer
Each relay instance needs multiple database connections. Without pooling, 3 instances × 10 connections = 30 PostgreSQL backend processes. With PgBouncer:
```ini
# /etc/pgbouncer/pgbouncer.ini
[databases]
crelay = host=127.0.0.1 port=5432 dbname=crelay
[pgbouncer]
listen_port = 6432
listen_addr = 127.0.0.1
auth_type = md5
pool_mode = transaction # Return connection to pool after each transaction
max_client_conn = 200 # Total connections from all relay instances
default_pool_size = 20 # Actual PostgreSQL connections
```
Relay instances connect to PgBouncer (port 6432) instead of PostgreSQL directly (port 5432). PgBouncer multiplexes 200 client connections onto 20 actual PostgreSQL connections.
### Zero-Downtime Deployment
```mermaid
sequenceDiagram
participant LB as nginx
participant I1 as Instance 1 - v1.0
participant I2 as Instance 2 - v1.0
participant I3 as Instance 3 - v1.0
participant I1_NEW as Instance 1 - v1.1
Note over LB,I3: Normal operation: 3 instances serving traffic
LB->>I1: Mark upstream as down
Note over I1: Drain: wait for existing connections to close or timeout
I1->>I1: Graceful shutdown
Note over LB: Traffic now goes to I2 and I3 only
I1_NEW->>I1_NEW: Start with new binary
I1_NEW->>LB: Health check passes
LB->>I1_NEW: Mark upstream as up
Note over LB,I1_NEW: Instance 1 now running v1.1, repeat for I2 and I3
```
Rolling deploy script:
```bash
#!/bin/bash
# rolling_deploy.sh - Zero-downtime deployment
for port in 8888 8889 8890; do
echo "Deploying instance on port $port..."
# 1. Tell nginx to stop sending new connections
sed -i "s/server 127.0.0.1:$port;/server 127.0.0.1:$port down;/" /etc/nginx/nginx.conf
nginx -s reload
# 2. Wait for existing connections to drain (30 seconds)
sleep 30
# 3. Stop old instance
systemctl stop c-relay-pg@$port
# 4. Deploy new binary
cp ./build/c_relay_x86 /opt/c-relay-pg/c_relay_x86
# 5. Start new instance
systemctl start c-relay-pg@$port
# 6. Wait for health check
sleep 5
# 7. Re-enable in nginx
sed -i "s/server 127.0.0.1:$port down;/server 127.0.0.1:$port;/" /etc/nginx/nginx.conf
nginx -s reload
echo "Instance on port $port deployed successfully"
done
```
### Dashboard Options
With PostgreSQL, the dashboard can be built with any technology:
- **Grafana** — Point directly at PostgreSQL, zero custom code
- **Custom web app** — React/Vue frontend + any backend (Node, Python, Go) querying PostgreSQL
- **Embedded in c-relay-pg** — Keep current approach but queries go through `db_ops` to PostgreSQL (no longer blocks event loop since PostgreSQL handles concurrency)
### Scaling Scenarios
| Scenario | Instances | Connections | Events/hour | Setup |
|----------|-----------|-------------|-------------|-------|
| **Current** | 1 | ~1,200 | 56 | Single process + SQLite |
| **Small upgrade** | 2 | ~2,500 | 500 | 2 instances + PostgreSQL |
| **Medium relay** | 4 | ~5,000 | 5,000 | 4 instances + PostgreSQL + PgBouncer |
| **Large relay** | 8 | ~10,000 | 50,000 | 8 instances + PostgreSQL + read replica |
| **Multi-region** | 24 per region | ~50,000 | 500,000 | Multiple servers + PostgreSQL replication |
### Cost Perspective
Running multiple relay instances with PostgreSQL on a single VPS:
- **PostgreSQL**: ~200500 MB RAM baseline
- **PgBouncer**: ~10 MB RAM
- **Each relay instance**: ~50100 MB RAM
- **Dashboard web server**: ~50100 MB RAM
- **4 instances + PostgreSQL + PgBouncer + dashboard**: ~12 GB total RAM
- A **$20/month VPS** with 4 cores and 4 GB RAM handles this easily
- A **$40/month VPS** with 8 cores and 8 GB RAM handles 8 instances comfortably
---
## Risk Mitigation
| Risk | Mitigation |
|------|-----------|
| PostgreSQL connection failures | `db_ops` returns error codes. Relay logs errors but doesn't crash. Reconnection logic with exponential backoff. |
| Performance regression | Benchmark before/after. PostgreSQL should be faster for complex queries, similar for simple ones. |
| Data loss during migration | Migration tool is read-only on SQLite. PostgreSQL import is idempotent (INSERT ON CONFLICT). |
| SQLite fallback needed | Keep `db_ops_sqlite.c` working. Compile-time flag switches backends. Can always go back. |
| LISTEN/NOTIFY message loss | NOTIFY is transactional — if the INSERT commits, the NOTIFY is guaranteed. Missed notifications during reconnect handled by periodic full-sync. |
| PgBouncer adds latency | Transaction pooling mode adds <0.1ms. Negligible compared to query time. |
---
## Relationship to c-relay
This project depends on the `db_ops.h` abstraction layer built in [c-relay thread pool plan](c_relay_thread_pool_plan.md) Phase 1. The interface is identical — only the backend implementation changes:
- **c-relay**: `db_ops.c` → SQLite calls via `sqlite3_*`
- **c-relay-pg**: `db_ops_postgres.c` → PostgreSQL calls via `libpq` (`PQexec`, `PQgetvalue`, etc.)
The `db_ops.h` header file is shared between both projects. Changes to the interface should be coordinated.
+332
View File
@@ -0,0 +1,332 @@
# c-relay Thread Pool Plan — db_ops Abstraction + SQLite Thread Pool
## Overview
This plan covers improvements to **c-relay** (this repo) — the lean, single-binary, embedded-SQLite Nostr relay. Two phases:
1. **Phase 1: Database Abstraction Layer** — Consolidate all scattered `sqlite3_*` calls into a single `db_ops.h` / `db_ops.c` module. Pure refactor, no behavior change.
2. **Phase 2: SQLite Thread Pool** — Add worker threads for concurrent reads, unblocking the `lws_service()` event loop.
This is the **final form of c-relay**: an embedded-SQLite relay with clean architecture and non-blocking database access. The abstraction layer also enables a future fork to PostgreSQL — see [c-relay-pg plan](c_relay_pg_plan.md).
### Why This First?
The current architecture has a fundamental bottleneck documented in the [database architecture analysis](database_architecture_analysis.md): the single-threaded event loop blocks on every database call. A 672ms REQ query freezes every connected client. The thread pool fixes this without changing the database engine or deployment model.
| Metric | Current | After Thread Pool |
|--------|---------|-------------------|
| **Event loop blocking** | 0.1672ms per query | Near-zero |
| **Concurrent reads** | 1 | 48 |
| **Deployment** | Single binary + DB file | Same |
| **Database** | SQLite | Same |
| **Code risk** | N/A | Low — additive change |
## Architecture
```mermaid
flowchart TD
subgraph c-relay - Final Form
RELAY[c-relay binary] -->|db_ops API| DBOPS[db_ops.c - abstraction layer]
DBOPS -->|sqlite3 calls| SQLITE[SQLite WAL database]
end
```
```mermaid
flowchart TD
subgraph c-relay with Thread Pool
LWS[lws_service event loop] -->|REQ arrives| Q[Thread-safe job queue]
LWS -->|EVENT arrives| WQ[Write queue - single writer]
Q --> T1[Reader Thread 1 - own sqlite3*]
Q --> T2[Reader Thread 2 - own sqlite3*]
Q --> T3[Reader Thread 3 - own sqlite3*]
Q --> T4[Reader Thread 4 - own sqlite3*]
WQ --> TW[Writer Thread - own sqlite3*]
T1 -->|results| CB[Callback to lws event loop]
T2 -->|results| CB
T3 -->|results| CB
T4 -->|results| CB
TW -->|OK/error| CB
CB -->|queue_message| LWS
end
```
---
## Phase 1: Database Abstraction Layer
### Goal
Consolidate all 258 scattered `sqlite3_*` calls across 8 files into a single `db_ops.h` / `db_ops.c` module with a backend-agnostic interface. SQLite continues to work — this is a pure refactor.
### Files That Currently Touch SQLite Directly
| File | `sqlite3_*` calls | Operations |
|------|-------------------|------------|
| [`src/main.c`](../src/main.c) | ~80 | Event store/retrieve, tag storage, REQ queries, COUNT queries, DB init, schema migration |
| [`src/config.c`](../src/config.c) | ~60 | Config CRUD, auth rules CRUD, WoT sync, relay key storage, startup sequence |
| [`src/api.c`](../src/api.c) | ~40 | Stats queries, monitoring views, admin SQL execution, config management |
| [`src/dm_admin.c`](../src/dm_admin.c) | ~20 | Auth rule management, WoT whitelist operations |
| [`src/websockets.c`](../src/websockets.c) | ~15 | COUNT queries, IP ban stats, connection tracking |
| [`src/subscriptions.c`](../src/subscriptions.c) | ~15 | Subscription logging — create/close/disconnect |
| [`src/nip009.c`](../src/nip009.c) | ~10 | Event deletion — by ID and by address |
| [`src/request_validator.c`](../src/request_validator.c) | ~8 | Blacklist/whitelist checks |
| [`src/ip_ban.c`](../src/ip_ban.c) | ~15 | IP ban table CRUD, persistence |
### Abstraction Layer Design
New files: `src/db_ops.h` and `src/db_ops.c`
```c
// src/db_ops.h — Database operations abstraction layer
#ifndef DB_OPS_H
#define DB_OPS_H
#include "../nostr_core_lib/cjson/cJSON.h"
// ============================================================
// Lifecycle
// ============================================================
int db_init(const char* connection_string); // SQLite: file path, PG: connection string
void db_close(void);
int db_is_available(void);
// ============================================================
// Schema / Migration
// ============================================================
int db_ensure_schema(void);
int db_get_schema_version(void);
int db_execute_raw(const char* sql); // For schema DDL only
// ============================================================
// Event Operations
// ============================================================
int db_store_event(cJSON* event);
int db_event_exists(const char* event_id);
char* db_get_event_json(const char* event_id); // Caller must free
int db_delete_event_by_id(const char* event_id, const char* requester_pubkey);
int db_delete_events_by_address(const char* pubkey, int kind,
const char* d_tag, long before_timestamp);
// ============================================================
// Event Queries - REQ handling
// ============================================================
typedef struct {
int* kinds; int kind_count;
char** authors; int author_count;
char** ids; int id_count;
char** tag_names; // Parallel arrays: tag_names[i] has tag_values[i][]
char*** tag_values;
int* tag_value_counts;
int tag_filter_count;
long since; // 0 = not set
long until; // 0 = not set
int limit; // 0 = default 500
char* search; // NIP-50 search term, NULL = not set
} db_event_filter_t;
typedef struct db_result db_result_t;
db_result_t* db_query_events(const db_event_filter_t* filter);
const char* db_result_next_json(db_result_t* result); // Returns event_json, NULL when done
int db_result_row_count(db_result_t* result);
void db_result_free(db_result_t* result);
int db_count_events(const db_event_filter_t* filter);
// ============================================================
// Config Operations
// ============================================================
char* db_get_config(const char* key); // Caller must free, NULL if not found
int db_set_config(const char* key, const char* value, const char* data_type,
const char* description, const char* category, int requires_restart);
int db_update_config(const char* key, const char* value);
int db_config_exists(const char* key);
int db_get_config_count(void);
// ============================================================
// Auth Rules Operations
// ============================================================
int db_add_auth_rule(const char* rule_type, const char* pattern_type, const char* pattern_value);
int db_remove_auth_rule(const char* rule_type, const char* pattern_type, const char* pattern_value);
int db_auth_rule_exists(const char* rule_type, const char* pattern_type, const char* pattern_value);
int db_clear_auth_rules(const char* rule_type); // NULL = clear all
int db_is_blacklisted(const char* pubkey);
int db_is_whitelisted(const char* pubkey);
int db_has_any_whitelist(void);
// ============================================================
// Relay Key Storage
// ============================================================
int db_store_relay_private_key(const char* privkey_hex);
char* db_get_relay_private_key(void); // Caller must free
// ============================================================
// Subscription Logging
// ============================================================
int db_log_subscription_created(const char* sub_id, const char* wsi_ptr,
const char* client_ip, const char* filter_json);
int db_log_subscription_closed(const char* sub_id, const char* client_ip);
int db_log_subscription_disconnected(const char* client_ip);
int db_update_subscription_events_sent(const char* sub_id, int events_sent);
int db_cleanup_orphaned_subscriptions(void);
// ============================================================
// IP Ban Persistence
// ============================================================
int db_ensure_ip_ban_table(void);
int db_load_ip_bans(void* ban_table, int table_size); // Populates in-memory table
int db_save_ip_bans(const void* ban_table, int table_size);
// ============================================================
// Analytics / Stats - for dashboard
// ============================================================
cJSON* db_get_event_kind_distribution(void);
cJSON* db_get_time_based_stats(void);
cJSON* db_get_top_pubkeys(int limit);
cJSON* db_get_subscription_details(void);
int db_get_total_event_count(void);
// ============================================================
// Admin SQL Query
// ============================================================
cJSON* db_execute_admin_query(const char* sql, char* error_msg, size_t error_size);
#endif // DB_OPS_H
```
### Migration Strategy for Phase 1
The key principle: **change the interface, not the behavior**. Each function in `db_ops.c` initially just wraps the existing SQLite calls.
**Step-by-step for each file:**
1. **Create `db_ops.h` and `db_ops.c`** with the interface above
2. **Implement each `db_ops` function** by moving the existing SQLite code from the source files into `db_ops.c`
3. **Replace direct `sqlite3_*` calls** in each source file with `db_ops_*` calls
4. **Remove `extern sqlite3* g_db`** from each file — only `db_ops.c` knows about `g_db`
5. **Remove `#include <sqlite3.h>`** from each file — only `db_ops.c` includes it
6. **Update Makefile** to compile `db_ops.c`
### Order of Migration (by risk, lowest first)
1. **`src/ip_ban.c`** — Self-contained, simple CRUD. Good warmup.
2. **`src/subscriptions.c`** — Logging only, no critical path.
3. **`src/nip009.c`** — Event deletion, small file.
4. **`src/request_validator.c`** — Auth checks, small file.
5. **`src/api.c`** — Stats/monitoring queries. Larger but read-only.
6. **`src/dm_admin.c`** — Auth rules + WoT. Medium complexity.
7. **`src/config.c`** — Config CRUD. Large but well-structured.
8. **`src/websockets.c`** — COUNT queries, minimal DB usage.
9. **`src/main.c`** — Event store/retrieve, REQ queries. The big one — do last.
### Testing Phase 1
After Phase 1, the relay should behave **identically** to before. Run:
- `tests/run_all_tests.sh` — Full test suite
- `tests/performance_benchmarks.sh` — Verify no performance regression
- Manual testing with production-like traffic
---
## Phase 2: SQLite Thread Pool
### Goal
Add a pool of N worker threads, each with its own `sqlite3*` connection to the same database file. SQLite WAL mode (already enabled) supports **concurrent readers**. The event loop dispatches database work to the pool and remains responsive.
### Key Design Points
1. **Read path**: REQ queries dispatched to thread pool. Each worker opens its own `sqlite3*` connection. SQLite WAL allows unlimited concurrent readers.
2. **Write path**: EVENT inserts go through a single dedicated writer thread. SQLite only allows one writer at a time anyway — this serializes writes cleanly.
3. **Result delivery**: Worker threads cannot call `lws_write()` directly (libwebsockets is not thread-safe). Instead, they push results into a per-session message queue and call `lws_cancel_service()` to wake the event loop, which then drains the queue.
4. **Connection lifecycle**: Each thread opens its own connection with `PRAGMA journal_mode=WAL` and `PRAGMA busy_timeout=5000`.
### What Changes in the Codebase
| Component | Current | Thread Pool |
|-----------|---------|-------------|
| [`g_db`](../src/main.c:49) | Single global connection | One per thread + writer connection |
| [`handle_req_message()`](../src/main.c:1101) | Synchronous SQL in callback | Package filter into job, dispatch to pool |
| [`store_event()`](../src/main.c:787) | Synchronous INSERT in callback | Dispatch to writer thread |
| [`handle_count_message()`](../src/websockets.c:2863) | Synchronous COUNT in callback | Dispatch to pool |
| Result delivery | Direct `queue_message()` | Worker pushes to queue + `lws_cancel_service()` |
| Config reads | Direct `sqlite3_prepare` on `g_db` | Can stay synchronous with own connection or cache |
### New Files
- `src/thread_pool.h` — Thread pool interface
- `src/thread_pool.c` — Thread pool implementation (job queue, worker lifecycle, result delivery)
### Thread Pool Interface (sketch)
```c
// src/thread_pool.h
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
typedef enum {
JOB_TYPE_REQ_QUERY,
JOB_TYPE_COUNT_QUERY,
JOB_TYPE_STORE_EVENT,
JOB_TYPE_DELETE_EVENT,
} job_type_t;
typedef struct {
job_type_t type;
void* session; // lws per-session data pointer
db_event_filter_t filter; // For REQ/COUNT jobs
cJSON* event; // For STORE jobs
char event_id[65]; // For DELETE jobs
} db_job_t;
int thread_pool_init(int num_readers, const char* db_path);
void thread_pool_shutdown(void);
int thread_pool_submit_read(db_job_t* job);
int thread_pool_submit_write(db_job_t* job);
#endif // THREAD_POOL_H
```
### Performance Characteristics
| Metric | Value |
|--------|-------|
| **Concurrent reads** | N readers in parallel (N = thread count, typically 48) |
| **Write throughput** | Same as current — SQLite serializes writes regardless |
| **Event loop latency** | Near-zero — REQ no longer blocks the loop |
| **Max theoretical read throughput** | ~48x current (limited by disk I/O, not CPU) |
| **Memory overhead** | ~50100 MB per connection (page cache) |
| **Latency per query** | Same as current per-query, but no head-of-line blocking |
### Limitations
- **Write contention**: SQLite still allows only ONE writer at a time. With WAL, readers don't block writers and writers don't block readers, but two simultaneous writes will serialize. At the current write rate (~56 events/hour), this is a non-issue.
- **Database size**: The 2.7 GB index bloat problem remains. Thread pool doesn't fix the schema — it fixes the concurrency.
- **Scaling ceiling**: Beyond ~8 reader threads, diminishing returns due to disk I/O contention on a single SQLite file.
- **Complexity**: Need a proper job queue, thread lifecycle management, and careful handling of the lws ↔ worker thread boundary.
---
## Risk Mitigation
| Risk | Mitigation |
|------|-----------|
| Phase 1 introduces bugs | Each file migrated independently, tested after each. Full test suite runs after each file. |
| Thread pool race conditions | Worker threads only touch their own `sqlite3*` connection. Result delivery uses a mutex-protected queue. `lws_cancel_service()` is documented as thread-safe. |
| Performance regression from abstraction | Abstraction layer is thin wrappers — no extra allocations or copies. Benchmark before/after. |
| lws thread safety issues | Workers never call `lws_write()` directly. They push to a queue and wake the event loop. This is the documented pattern for libwebsockets multi-threading. |
| Rollback needed | Phase 1 is a pure refactor — easy to revert. Phase 2 thread pool is additive — can be disabled with a compile flag. |
---
## Relationship to c-relay-pg
After Phase 1 (abstraction layer) is complete, the codebase is ready to be forked into a separate **c-relay-pg** project that replaces the SQLite backend with PostgreSQL. See [c-relay-pg plan](c_relay_pg_plan.md) for details.
The `db_ops.h` interface is designed to work with any backend:
- **SQLite** (this plan): `db_result_next_json()` copies from `sqlite3_column_text()`
- **PostgreSQL** (c-relay-pg): `db_result_next_json()` returns from `PQgetvalue()`
- **LMDB** (future possibility): `db_result_next_json()` returns a zero-copy pointer into mmap'd memory
File diff suppressed because it is too large Load Diff
+194
View File
@@ -0,0 +1,194 @@
# Main Thread CPU Offload Plan
## Problem Statement
The main `c_relay` thread consumes ~56% CPU while the DB worker threads (`db-read-1..4`, `db-write`) sit near 0%. All protocol handling, JSON parsing, event validation, subscription matching, and message queueing happens synchronously inside `nostr_relay_callback()` on the main libwebsockets event-loop thread.
## Current Architecture
```mermaid
flowchart TD
LWS[lws_service - main thread] --> CB[nostr_relay_callback]
CB --> PARSE[JSON parse - cJSON_Parse]
CB --> VALIDATE[Signature verify - nostr_validate_unified_request]
CB --> STORE[store_event - builds payload]
STORE --> SYNC_WRITE[thread_pool_execute_store_event_sync]
SYNC_WRITE --> |blocks main thread| DB_WRITE[db-write thread]
DB_WRITE --> |signal| SYNC_WRITE
CB --> REQ[handle_req_message - builds SQL]
REQ --> SYNC_READ[thread_pool_execute_req_sync]
SYNC_READ --> |blocks main thread| DB_READ[db-read-N thread]
DB_READ --> |signal| SYNC_READ
CB --> BROADCAST[broadcast_event_to_subscriptions]
BROADCAST --> QUEUE[queue_message per subscriber]
CB --> WRITEABLE[LWS_CALLBACK_SERVER_WRITEABLE]
WRITEABLE --> DRAIN[process_message_queue - lws_write]
```
### Why the main thread is hot
| Work item | Where | Cost |
|-----------|-------|------|
| JSON parsing | `cJSON_Parse` in `LWS_CALLBACK_RECEIVE` | Medium - per message |
| Signature verification | `nostr_validate_unified_request` - ed25519 crypto | **High** - per EVENT |
| SQL query building | `handle_req_message` filter-to-SQL loop | Low-Medium |
| Sync DB wait | `thread_pool_execute_*_sync` - pthread_cond_wait | Blocks but yields CPU |
| Result iteration + expiration check | Row loop in `handle_req_message` with `cJSON_Parse` per row | Medium-High for large result sets |
| Subscription matching | `broadcast_event_to_subscriptions` - filter matching | Medium - scales with subscriber count |
| Message serialization + queueing | `snprintf` + `queue_message_take_ownership` per subscriber | Medium |
| Config lookups | `get_config_int/bool` called repeatedly in hot paths | Low but frequent |
### Key constraint: libwebsockets is single-threaded
libwebsockets requires that **all `lws_write`, `lws_callback_on_writable`, and `lws_close_reason` calls happen from the service thread** (the thread running `lws_service`). This means we cannot directly write to WebSocket connections from worker threads. However, we **can** do computation on worker threads and post results back to the main thread for I/O.
## Offload Strategy
### Phase 1: Async EVENT Processing (Highest Impact)
Convert EVENT handling from synchronous to async. Currently the main thread does: parse → validate → store → broadcast, all blocking. Instead:
1. **Main thread**: Parse JSON (fast), extract event ID for dedup check, then submit a job to a new "event processing" worker thread
2. **Worker thread**: Signature verification (expensive crypto), store_event (already goes to DB thread), prepare broadcast payload
3. **Main thread callback**: Receive result via `wake_loop_cb` + `lws_cancel_service`, send OK response and broadcast to subscribers
```mermaid
flowchart TD
LWS[lws_service - main thread] --> RECEIVE[LWS_CALLBACK_RECEIVE]
RECEIVE --> PARSE[JSON parse + dedup check]
PARSE --> SUBMIT[Submit to event-worker queue]
SUBMIT --> LWS
WORKER[event-worker thread] --> VERIFY[Signature verification]
VERIFY --> STORE_DB[store_event via DB pool]
STORE_DB --> PREP[Prepare broadcast payload]
PREP --> RESULT_Q[Push result to completion queue]
RESULT_Q --> WAKE[lws_cancel_service]
LWS2[lws_service wakes] --> POLL[Poll completion queue]
POLL --> OK[Send OK response via queue_message]
POLL --> BCAST[broadcast_event_to_subscriptions]
```
**What this offloads**: ed25519 signature verification (~the most expensive per-event operation), event classification, tag serialization, and the synchronous DB store wait.
**What stays on main thread**: JSON parse (needed to extract event ID for dedup), OK response writing, broadcast fan-out (requires lws access).
### Phase 2: Async REQ Query Execution (Medium Impact)
Convert REQ handling from sync to async:
1. **Main thread**: Parse filters, build SQL, create subscription, submit query job
2. **DB reader thread**: Execute query (already happens, but currently blocks main thread via `_sync`)
3. **Main thread callback**: Iterate results, queue EVENT messages, send EOSE
This is simpler than Phase 1 because the thread pool already supports async submission via `thread_pool_submit_read` with a `result_cb`. The `_sync` wrappers just add a condvar wait on top. We need to:
- Use `thread_pool_submit_read` directly instead of `thread_pool_execute_req_sync`
- Store pending REQ context (sub_id, wsi, pss) so the callback can complete the work
- In the result callback, push results to a completion queue and call `lws_cancel_service`
- On the main thread, drain the completion queue and send EVENT + EOSE messages
### Phase 3: Reduce Per-Row Overhead in REQ Results (Low-Medium Impact)
Currently each row from a REQ query gets `cJSON_Parse` just to check NIP-40 expiration. This is wasteful:
- Option A: Add an `expiration` column to the events table so expiration filtering can be done in SQL
- Option B: Store expiration timestamp in a fast-parse format (extract during INSERT, store as integer column)
- Option C: Use string search on the raw JSON for the expiration tag instead of full parse
### Phase 4: Config Value Caching (Low Impact, Easy Win)
`get_config_int` and `get_config_bool` are called on every message in hot paths. These do SQLite queries. Cache config values in memory with a TTL or invalidation signal, so the main loop only refreshes them periodically (already done for `debug_level` every 60s — extend to all hot-path config values).
## Implementation Priority
| Phase | Impact | Risk | Complexity |
|-------|--------|------|------------|
| Phase 1: Async EVENT | **High** - removes crypto from main thread | Medium - async state management | Medium-High |
| Phase 2: Async REQ | **Medium** - unblocks main thread during queries | Low - infrastructure exists | Medium |
| Phase 3: Expiration optimization | **Low-Medium** - reduces per-row parse cost | Low | Low |
| Phase 4: Config caching | **Low** - reduces DB round-trips | Very Low | Low |
## Detailed Design: Phase 1 (Async EVENT Processing)
### New Components
#### Completion Queue (`src/completion_queue.h/.c`)
A thread-safe FIFO queue for posting results from worker threads back to the main thread:
```c
typedef struct {
int type; // COMPLETION_TYPE_EVENT_RESULT, COMPLETION_TYPE_REQ_RESULT, etc.
void* data; // Type-specific result data
struct lws* wsi; // Target WebSocket connection
void* pss; // Per-session data
} completion_item_t;
int completion_queue_init(void);
int completion_queue_push(completion_item_t* item);
completion_item_t* completion_queue_pop(void); // Non-blocking
void completion_queue_shutdown(void);
```
#### Event Worker Thread
A dedicated pthread that processes EVENT validation/storage:
```c
typedef struct {
cJSON* event; // Parsed event JSON - ownership transferred
cJSON* full_message; // Full message JSON for context
struct lws* wsi;
void* pss;
char sub_id[64]; // For response routing
} event_work_item_t;
```
#### Main Loop Integration
Add a completion queue drain step to the main event loop. After `lws_service` returns (either from timeout or `lws_cancel_service` wake), check the completion queue:
```c
while (g_server_running && !g_shutdown_flag) {
int result = lws_service(ws_context, 1000);
// NEW: Drain completion queue
completion_item_t* item;
while ((item = completion_queue_pop()) != NULL) {
process_completion(item); // Send OK, broadcast, etc.
free(item);
}
// ... existing periodic checks ...
}
```
### Changes to Existing Code
1. **`nostr_relay_callback` EVENT path**: After JSON parse and dedup check, instead of calling `nostr_validate_unified_request` + `store_event` + `broadcast_event_to_subscriptions` synchronously, submit an `event_work_item_t` to the event worker queue and return 0 immediately.
2. **`store_event`**: No changes needed — it already uses `thread_pool_execute_store_event_sync` which will run on the DB writer thread. The event worker thread will call it.
3. **`broadcast_event_to_subscriptions`**: No changes needed — it will be called from the main thread when processing the completion item, which is correct since it calls `queue_message_take_ownership` (requires lws thread).
### Thread Safety Considerations
- The `cJSON* event` object must be fully owned by the worker thread during processing. The main thread must not access it after submission.
- The `wsi` and `pss` pointers could become invalid if the client disconnects while the event is being processed. The completion handler must validate that the connection is still alive before sending the OK response.
- A generation counter or epoch on `pss` can detect stale references.
## What Cannot Be Offloaded
- **`lws_write` / `queue_message`**: Must happen on the lws service thread
- **`lws_callback_on_writable`**: Must happen on the lws service thread
- **`lws_close_reason`**: Must happen on the lws service thread
- **Subscription list iteration for broadcast**: Accesses `lws_wsi_user` which is lws-internal
These are fundamental libwebsockets constraints. The pattern is always: do computation off-thread, post result to completion queue, wake main thread, do I/O on main thread.
## Expected Impact
With Phase 1 alone, the main thread would no longer perform:
- ed25519 signature verification (~100-500μs per event depending on CPU)
- Synchronous DB store wait (~50-200μs per event)
- Event classification, tag serialization, JSON serialization for storage
This should reduce main-thread CPU by roughly 30-50% for EVENT-heavy workloads, shifting that work to the event worker thread and DB threads.
+257
View File
@@ -0,0 +1,257 @@
# PostgreSQL Nostr Relay Architecture Analysis
This document summarizes common approaches and best practices for building Nostr relays backed by PostgreSQL, based on industry-standard designs and existing open-source relay implementations.
## Identified Projects
* **Nostrss**: High-performance Rust-based relay.
* **Nostrgres**: Focused on PostgreSQL integration with complex event filtering.
* **Relay-rs (Postgres branch)**: General purpose high-throughput relay.
* **Nostream** ([github.com/Cameri/nostream](https://github.com/Cameri/nostream)): Production-ready TypeScript relay backed by PostgreSQL and Redis.
* **Nostr-Relay-NestJS** ([github.com/CodyTseng/nostr-relay-nestjs](https://github.com/CodyTseng/nostr-relay-nestjs)): High-performance relay built with NestJS and PostgreSQL, utilizing Kysely for type-safe database interactions.
## Detailed Analysis
### 1. Common Schema Patterns
* **Event Storage (JSONB)**: Most PostgreSQL-backed relays store the raw event as a JSONB object in a central `events` table. This provides flexibility for the evolving Nostr spec while allowing efficient extraction of fields.
* **Tag Denormalization**: While the raw event is in JSONB, performant relays extract tags (like `p`, `e`, `t`, `d`) into a separate `tags` table with foreign key relationships to the `events` table. This avoids slow JSONB traversal during complex filter queries.
* **Author/Publisher Tracking**: A dedicated `authors` or `pubkeys` table is typically used to maintain indexing on the `pubkey` field, enabling quick lookups of user activity.
### 2. Performance Optimization
* **GIN Indexes on JSONB**: Crucial for filtering on specific event tags or custom properties stored within the event object. GIN indexes with `jsonb_path_ops` are generally preferred for equality checks.
* **Time-Series Partitioning**: Given the append-only nature of Nostr, partitioning the `events` table by time (e.g., daily or weekly chunks) is highly recommended. This significantly improves query performance for recent data and simplifies data expiration/deletion.
* **Clustering**: Clustering the `events` table by the timestamp index can reduce disk I/O, as it keeps temporally related events physically adjacent on the disk.
### 3. Schema Management Approaches
* **Migrations**: Most mature relays utilize migration tools (like `Flyway`, `Diesel migrations`, or `Golang-migrate`) to version control database schema changes. This is critical for production stability.
* **Auto-generation**: Some lightweight prototypes auto-generate schemas at startup. This is generally discouraged for production due to the risk of destructive changes, data loss, or blocking DDL operations on large tables.
## Recommendations for Building a New Relay
1. **Prioritize Denormalization**: Do not rely solely on JSONB queries for high-traffic filters. Extract indexed tags into dedicated columns or tables.
2. **Use Partitioning from Day One**: Implementing native PostgreSQL partitioning (e.g., using `pg_partman`) is much easier before the dataset grows to millions of rows.
3. **Connection Pooling**: PostgreSQL requires aggressive connection management. Use a high-performance pooler like `PgBouncer` to handle the large number of concurrent, short-lived connections common in WebSocket-based relay traffic.
4. **Asynchronous Writes**: Decouple the WebSocket ingestion thread from the database writer thread to ensure that slow database writes do not impact the relay's responsiveness.
## Nostream ([github.com/Cameri/nostream](https://github.com/Cameri/nostream))
A production-ready Nostr relay written in TypeScript (Node.js), backed by PostgreSQL 14+ and Redis. Uses Knex.js for versioned migrations.
### PostgreSQL Schema
The schema is fully normalized — no JSONB blob for the whole event. Tags are stored separately and populated by a trigger.
**`events` table** — one row per event, tags kept as a JSONB column for the trigger to process:
```sql
CREATE TABLE events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
event_id BYTEA UNIQUE NOT NULL, -- 32-byte hash
event_pubkey BYTEA NOT NULL,
event_kind INTEGER UNSIGNED NOT NULL,
event_created_at INTEGER UNSIGNED NOT NULL,
event_content TEXT NOT NULL,
event_tags JSONB, -- raw tags array, source of truth for trigger
event_signature BYTEA NOT NULL,
remote_address TEXT,
expires_at INTEGER,
deleted_at TIMESTAMP,
event_deduplication JSONB, -- used for replaceable event conflict key
first_seen TIMESTAMP DEFAULT now()
);
-- Indexes
CREATE INDEX ON events (event_pubkey);
CREATE INDEX ON events (event_kind);
CREATE INDEX ON events (event_created_at);
CREATE INDEX ON events (expires_at);
CREATE INDEX event_tags_idx ON events USING GIN (event_tags); -- GIN on raw JSONB
-- Unique index enforcing NIP-16 replaceable event semantics
CREATE UNIQUE INDEX replaceable_events_idx ON events (event_pubkey, event_kind)
WHERE event_kind = 0 OR event_kind = 3
OR (event_kind >= 10000 AND event_kind < 20000);
```
**`event_tags` table** — denormalized single-letter tags, populated by a PostgreSQL trigger on `events`:
```sql
CREATE TABLE event_tags (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
event_id BYTEA NOT NULL,
tag_name TEXT NOT NULL, -- single-letter only (e.g. 'e', 'p', 't')
tag_value TEXT NOT NULL
);
CREATE INDEX ON event_tags (event_id);
CREATE INDEX ON event_tags (tag_name, tag_value);
-- Trigger: on INSERT/UPDATE/DELETE to events, repopulate event_tags
CREATE OR REPLACE FUNCTION process_event_tags() RETURNS TRIGGER AS $$
DECLARE
tag_element jsonb;
BEGIN
DELETE FROM event_tags WHERE event_id = OLD.event_id;
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
FOR tag_element IN SELECT jsonb_array_elements(NEW.event_tags) LOOP
IF length(trim('"' FROM (tag_element->0)::text)) = 1
AND (tag_element->1)::text IS NOT NULL THEN
INSERT INTO event_tags (event_id, tag_name, tag_value)
VALUES (NEW.event_id,
trim('"' FROM (tag_element->0)::text),
trim('"' FROM (tag_element->1)::text));
END IF;
END LOOP;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER insert_event_tags
AFTER INSERT OR UPDATE OR DELETE ON events
FOR EACH ROW EXECUTE FUNCTION process_event_tags();
```
Tag queries (`#e`, `#p`, etc.) join `event_tags` rather than traversing the JSONB array.
**`users` and `invoices` tables** — for paid relay admission (Lightning payments):
```sql
CREATE TABLE users (
pubkey BYTEA PRIMARY KEY,
is_admitted BOOLEAN DEFAULT FALSE,
balance BIGINT DEFAULT 0, -- in msats
tos_accepted_at DATETIME
);
CREATE TABLE invoices (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
pubkey BYTEA NOT NULL,
bolt11 TEXT NOT NULL,
amount_requested BIGINT UNSIGNED NOT NULL,
amount_paid BIGINT UNSIGNED,
unit ENUM('msats','sats','btc'),
status ENUM('pending','completed','expired'),
description TEXT,
confirmed_at DATETIME,
expires_at DATETIME
);
```
### How Redis Is Used
Redis is **not** used for subscription fan-out. Subscription delivery is handled entirely in-process: each `WebSocketAdapter` holds a `Map<subscriptionId, filters[]>`, and a broadcast event walks all connected clients and filters locally.
Redis has two actual roles:
1. **Sliding-window rate limiting** — The `SlidingWindowRateLimiter` uses Redis sorted sets to track request timestamps per key (pubkey, IP, etc.) within a rolling time window:
- `ZREMRANGEBYSCORE key 0 (now - period)` — evict old entries
- `ZADD key timestamp member` — record this hit
- `ZRANGE key 0 -1` — count hits in window
- `EXPIRE key period` — auto-cleanup
- Keys follow the pattern `<pubkey>:events:<period>` or `<ip>:message:<period>`
2. **General-purpose cache** — A `RedisAdapter` wraps the client with `get`/`set`/`hasKey` helpers, used for caching admission checks (e.g. `<pubkey>:is-admitted`). The event handler has a `TODO` comment noting that the `userRepository.findByPubkey()` call should be replaced with a cache lookup — meaning this is partially implemented.
---
## Schema Definitions
### Relay-rs (Postgres)
```sql
-- Events table
CREATE TABLE "event" (
id bytea NOT NULL,
pub_key bytea NOT NULL,
created_at timestamp with time zone NOT NULL,
kind integer NOT NULL,
"content" bytea NOT NULL,
hidden bit(1) NOT NULL DEFAULT 0::bit(1),
delegated_by bytea NULL,
first_seen timestamp with time zone NOT NULL DEFAULT now(),
expires_at timestamp(0) with time zone,
CONSTRAINT event_pkey PRIMARY KEY (id)
);
CREATE INDEX event_created_at_idx ON "event" (created_at,kind);
CREATE INDEX event_pub_key_idx ON "event" (pub_key);
CREATE INDEX event_delegated_by_idx ON "event" (delegated_by);
CREATE INDEX event_expires_at_idx ON "event" (expires_at);
-- Tags table
CREATE TABLE "tag" (
id int8 NOT NULL GENERATED BY DEFAULT AS IDENTITY,
event_id bytea NOT NULL,
"name" varchar NOT NULL,
value bytea NULL,
value_hex bytea NULL,
CONSTRAINT tag_fk FOREIGN KEY (event_id) REFERENCES "event"(id) ON DELETE CASCADE,
CONSTRAINT unique_constraint_name UNIQUE (event_id, "name", value, value_hex)
);
CREATE INDEX tag_event_id_idx ON tag USING btree (event_id, name);
CREATE INDEX tag_value_idx ON tag USING btree (value);
CREATE INDEX tag_value_hex_idx ON tag USING btree (value_hex);
-- Account table
CREATE TABLE "account" (
pubkey varchar NOT NULL,
is_admitted BOOLEAN NOT NULL DEFAULT FALSE,
balance BIGINT NOT NULL DEFAULT 0,
tos_accepted_at TIMESTAMP,
CONSTRAINT account_pkey PRIMARY KEY (pubkey)
);
-- Invoice table
CREATE TYPE status AS ENUM ('Paid', 'Unpaid', 'Expired');
CREATE TABLE "invoice" (
payment_hash varchar NOT NULL,
pubkey varchar NOT NULL,
invoice varchar NOT NULL,
amount BIGINT NOT NULL,
status status NOT NULL DEFAULT 'Unpaid',
description varchar,
created_at timestamp,
confirmed_at timestamp,
CONSTRAINT invoice_payment_hash PRIMARY KEY (payment_hash),
CONSTRAINT invoice_pubkey_fkey FOREIGN KEY (pubkey) REFERENCES account (pubkey) ON DELETE CASCADE
);
```
### Nostr-Relay-NestJS (PostgreSQL via Kysely)
```sql
-- Events table
CREATE TABLE events (
id CHAR(64) PRIMARY KEY,
pubkey CHAR(64) NOT NULL,
created_at BIGINT NOT NULL,
kind INTEGER NOT NULL,
tags JSONB NOT NULL DEFAULT '[]',
generic_tags TEXT[] NOT NULL DEFAULT '{}',
content TEXT NOT NULL DEFAULT '',
sig CHAR(128) NOT NULL,
expired_at BIGINT,
d_tag_value TEXT,
delegator CHAR(64),
create_date TIMESTAMP NOT NULL DEFAULT now(),
update_date TIMESTAMP NOT NULL DEFAULT now(),
delete_date TIMESTAMP
);
-- Indices
CREATE EXTENSION IF NOT EXISTS btree_gin;
CREATE INDEX generic_tags_kind_idx ON events USING gin (generic_tags, kind);
CREATE INDEX pubkey_kind_created_at_idx ON events (pubkey, kind, created_at);
CREATE INDEX delegator_kind_created_at_idx ON events (delegator, kind, created_at);
CREATE INDEX created_at_kind_idx ON events (created_at, kind);
-- Generic tags table (for optimized tag queries)
CREATE TABLE generic_tags (
id SERIAL PRIMARY KEY,
tag TEXT NOT NULL,
author CHAR(64) NOT NULL,
kind INTEGER NOT NULL,
event_id CHAR(64) NOT NULL REFERENCES events(id),
created_at BIGINT NOT NULL
);
-- Indices for tag filtering
CREATE UNIQUE INDEX g_tag_event_id_idx ON generic_tags (tag, event_id);
CREATE INDEX g_tag_kind_created_at_idx ON generic_tags (tag, kind, created_at);
CREATE INDEX g_tag_created_at_idx ON generic_tags (tag, created_at);
```
Nostr-Relay-NestJS features a hybrid storage model where tags are stored both in a JSONB field (for general lookup) and a normalized `generic_tags` table (for optimized tag query performance). The schema uses Kysely for type-safe interaction.
+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
```
+10
View File
@@ -0,0 +1,10 @@
https://github.com/rushmi0/Fenrir-s
https://github.com/barkyq/gnost-relay
https://github.com/lpicanco/knostr
https://github.com/bezysoftware/netstr
https://github.com/lebrunel/nex
https://github.com/CodyTseng/nostr-relay-nestjs
https://github.com/mattn/nostr-relay
https://github.com/Cameri/nostream
https://github.com/Giszmo/NostrPostr/tree/master/NostrRelay
https://github.com/fiatjaf/relayer/tree/master/examples/basic
+1 -1
View File
@@ -1 +1 @@
3134018
694269
+145 -339
View File
@@ -14,7 +14,6 @@ extern void log_query_execution(const char* query_type, const char* sub_id,
#include <libwebsockets.h>
#include <cjson/cJSON.h>
int get_active_connection_count(void);
#include <sqlite3.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>
@@ -28,6 +27,7 @@ int get_active_connection_count(void);
#include "../nostr_core_lib/nostr_core/nip017.h"
#include "../nostr_core_lib/nostr_core/nip044.h"
#include "subscriptions.h"
#include "db_ops.h"
// External subscription manager (from main.c via subscriptions.c)
extern subscription_manager_t g_subscription_manager;
@@ -62,22 +62,21 @@ int get_monitoring_throttle_seconds(void) {
// Query event kind distribution from database
cJSON* query_event_kind_distribution(void) {
extern sqlite3* g_db;
if (!g_db) {
if (!db_is_available()) {
DEBUG_ERROR("Database not available for monitoring query");
return NULL;
}
// Query event kinds distribution with total count
sqlite3_stmt* stmt;
const char* sql = "SELECT kind, COUNT(*) as count FROM events GROUP BY kind ORDER BY count DESC";
// Start timing
struct timespec query_start, query_end;
clock_gettime(CLOCK_MONOTONIC, &query_start);
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
DEBUG_ERROR("Failed to prepare event kind distribution query");
long long total_events = 0;
cJSON* kinds_array = db_get_event_kind_distribution_rows(&total_events);
if (!kinds_array) {
DEBUG_ERROR("Failed to query event kind distribution rows");
return NULL;
}
@@ -85,29 +84,12 @@ cJSON* query_event_kind_distribution(void) {
cJSON_AddStringToObject(distribution, "data_type", "event_kinds");
cJSON_AddNumberToObject(distribution, "timestamp", (double)time(NULL));
cJSON* kinds_array = cJSON_CreateArray();
long long total_events = 0;
int row_count = 0;
while (sqlite3_step(stmt) == SQLITE_ROW) {
row_count++;
int kind = sqlite3_column_int(stmt, 0);
long long count = sqlite3_column_int64(stmt, 1);
total_events += count;
cJSON* kind_obj = cJSON_CreateObject();
cJSON_AddNumberToObject(kind_obj, "kind", kind);
cJSON_AddNumberToObject(kind_obj, "count", count);
cJSON_AddItemToArray(kinds_array, kind_obj);
}
sqlite3_finalize(stmt);
// Stop 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;
int row_count = cJSON_GetArraySize(kinds_array);
log_query_execution("MONITOR", "event_kinds", NULL, sql, elapsed_us, row_count);
cJSON_AddNumberToObject(distribution, "total_events", total_events);
@@ -118,8 +100,7 @@ cJSON* query_event_kind_distribution(void) {
// Query time-based statistics from database
cJSON* query_time_based_statistics(void) {
extern sqlite3* g_db;
if (!g_db) {
if (!db_is_available()) {
DEBUG_ERROR("Database not available for time stats query");
return NULL;
}
@@ -143,38 +124,18 @@ cJSON* query_time_based_statistics(void) {
{NULL, 0, NULL}
};
// Get total events count
sqlite3_stmt* total_stmt;
const char* total_sql = "SELECT COUNT(*) FROM events";
long long total_events = 0;
if (sqlite3_prepare_v2(g_db, total_sql, -1, &total_stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(total_stmt) == SQLITE_ROW) {
total_events = sqlite3_column_int64(total_stmt, 0);
}
sqlite3_finalize(total_stmt);
}
(void)db_get_total_event_count_ll(&total_events);
// Query each time period
for (int i = 0; periods[i].period != NULL; i++) {
sqlite3_stmt* stmt;
const char* sql = "SELECT COUNT(*) FROM events WHERE created_at >= ?";
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
DEBUG_ERROR("Failed to prepare time stats query");
time_t cutoff = now - periods[i].seconds;
long long count = 0;
if (db_get_event_count_since(cutoff, &count) != 0) {
DEBUG_ERROR("Failed to query time stats count");
continue;
}
time_t cutoff = now - periods[i].seconds;
sqlite3_bind_int64(stmt, 1, cutoff);
long long count = 0;
if (sqlite3_step(stmt) == SQLITE_ROW) {
count = sqlite3_column_int64(stmt, 0);
}
sqlite3_finalize(stmt);
cJSON* period_obj = cJSON_CreateObject();
cJSON_AddStringToObject(period_obj, "period", periods[i].period);
cJSON_AddNumberToObject(period_obj, "count", count);
@@ -190,51 +151,39 @@ cJSON* query_time_based_statistics(void) {
// Query top pubkeys by event count from database
cJSON* query_top_pubkeys(void) {
extern sqlite3* g_db;
if (!g_db) {
if (!db_is_available()) {
DEBUG_ERROR("Database not available for top pubkeys query");
return NULL;
}
// Query top 10 pubkeys by event count
sqlite3_stmt* stmt;
const char* sql = "SELECT pubkey, COUNT(*) as count FROM events GROUP BY pubkey ORDER BY count DESC LIMIT 10";
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
DEBUG_ERROR("Failed to prepare top pubkeys query");
return NULL;
}
cJSON* top_pubkeys = cJSON_CreateObject();
cJSON_AddStringToObject(top_pubkeys, "data_type", "top_pubkeys");
cJSON_AddNumberToObject(top_pubkeys, "timestamp", (double)time(NULL));
cJSON* pubkeys_array = cJSON_CreateArray();
// Get total events count for percentage calculation
sqlite3_stmt* total_stmt;
const char* total_sql = "SELECT COUNT(*) FROM events";
long long total_events = 0;
if (sqlite3_prepare_v2(g_db, total_sql, -1, &total_stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(total_stmt) == SQLITE_ROW) {
total_events = sqlite3_column_int64(total_stmt, 0);
}
sqlite3_finalize(total_stmt);
cJSON* rows = db_get_top_pubkeys_rows(10);
if (!rows) {
cJSON_Delete(top_pubkeys);
DEBUG_ERROR("Failed to query top pubkeys rows");
return NULL;
}
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char* pubkey = (const char*)sqlite3_column_text(stmt, 0);
long long count = sqlite3_column_int64(stmt, 1);
cJSON* pubkeys_array = cJSON_CreateArray();
cJSON* row = NULL;
cJSON_ArrayForEach(row, rows) {
cJSON* pubkey_obj = cJSON_CreateObject();
cJSON_AddStringToObject(pubkey_obj, "pubkey", pubkey ? pubkey : "");
cJSON_AddNumberToObject(pubkey_obj, "event_count", count);
// Percentage will be calculated by frontend using total_events
cJSON* pubkey_item = cJSON_GetObjectItemCaseSensitive(row, "pubkey");
cJSON* count_item = cJSON_GetObjectItemCaseSensitive(row, "count");
cJSON_AddStringToObject(pubkey_obj, "pubkey",
cJSON_IsString(pubkey_item) ? pubkey_item->valuestring : "");
cJSON_AddNumberToObject(pubkey_obj, "event_count",
cJSON_IsNumber(count_item) ? count_item->valuedouble : 0.0);
cJSON_AddItemToArray(pubkeys_array, pubkey_obj);
}
cJSON_Delete(rows);
sqlite3_finalize(stmt);
long long total_events = 0;
(void)db_get_total_event_count_ll(&total_events);
cJSON_AddItemToObject(top_pubkeys, "pubkeys", pubkeys_array);
cJSON_AddNumberToObject(top_pubkeys, "total_events", total_events);
@@ -246,15 +195,11 @@ cJSON* query_top_pubkeys(void) {
// Query detailed subscription information from database log (ADMIN ONLY)
// Uses subscriptions table instead of in-memory iteration to avoid mutex contention
cJSON* query_subscription_details(void) {
extern sqlite3* g_db;
if (!g_db) {
if (!db_is_available()) {
DEBUG_ERROR("Database not available for subscription details query");
return NULL;
}
// Query active subscriptions from the active_subscriptions_log view
// This view properly handles deduplication of closed/expired subscriptions
sqlite3_stmt* stmt;
const char* sql =
"SELECT * "
"FROM active_subscriptions_log "
@@ -268,11 +213,6 @@ cJSON* query_subscription_details(void) {
struct timespec query_start, query_end;
clock_gettime(CLOCK_MONOTONIC, &query_start);
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
DEBUG_ERROR("Failed to prepare subscription details query");
return NULL;
}
time_t current_time = time(NULL);
cJSON* subscriptions_data = cJSON_CreateObject();
cJSON_AddStringToObject(subscriptions_data, "data_type", "subscription_details");
@@ -281,34 +221,51 @@ cJSON* query_subscription_details(void) {
cJSON* data = cJSON_CreateObject();
cJSON* subscriptions_array = cJSON_CreateArray();
cJSON* rows = db_get_subscription_details_rows();
if (!rows) {
cJSON_Delete(subscriptions_data);
cJSON_Delete(data);
cJSON_Delete(subscriptions_array);
DEBUG_ERROR("Failed to query subscription details rows");
return NULL;
}
// Iterate through query results
int row_count = 0;
while (sqlite3_step(stmt) == SQLITE_ROW) {
cJSON* row = NULL;
cJSON_ArrayForEach(row, rows) {
row_count++;
cJSON* sub_obj = cJSON_CreateObject();
// Extract subscription data from database
const char* sub_id = (const char*)sqlite3_column_text(stmt, 0);
const char* client_ip = (const char*)sqlite3_column_text(stmt, 1);
const char* filter_json = (const char*)sqlite3_column_text(stmt, 2);
long long events_sent = sqlite3_column_int64(stmt, 3);
long long created_at = sqlite3_column_int64(stmt, 4);
long long duration_seconds = sqlite3_column_int64(stmt, 5);
const char* wsi_pointer = (const char*)sqlite3_column_text(stmt, 6);
cJSON* sub_id_item = cJSON_GetObjectItemCaseSensitive(row, "id");
cJSON* client_ip_item = cJSON_GetObjectItemCaseSensitive(row, "client_ip");
cJSON* filter_json_item = cJSON_GetObjectItemCaseSensitive(row, "filter_json");
cJSON* events_sent_item = cJSON_GetObjectItemCaseSensitive(row, "events_sent");
cJSON* created_at_item = cJSON_GetObjectItemCaseSensitive(row, "created_at");
cJSON* duration_item = cJSON_GetObjectItemCaseSensitive(row, "duration_seconds");
cJSON* wsi_pointer_item = cJSON_GetObjectItemCaseSensitive(row, "wsi_pointer");
const char* sub_id = cJSON_IsString(sub_id_item) ? sub_id_item->valuestring : "";
const char* client_ip = cJSON_IsString(client_ip_item) ? client_ip_item->valuestring : "";
const char* filter_json = cJSON_IsString(filter_json_item) ? filter_json_item->valuestring : "[]";
long long events_sent = cJSON_IsNumber(events_sent_item) ? (long long)events_sent_item->valuedouble : 0;
long long created_at = cJSON_IsNumber(created_at_item) ? (long long)created_at_item->valuedouble : 0;
long long duration_seconds = cJSON_IsNumber(duration_item) ? (long long)duration_item->valuedouble : 0;
const char* wsi_pointer = cJSON_IsString(wsi_pointer_item) ? wsi_pointer_item->valuestring : "";
// DEBUG: Log each subscription found
DEBUG_LOG("Row %d: sub_id=%s, client_ip=%s, events_sent=%lld, created_at=%lld",
row_count, sub_id ? sub_id : "NULL", client_ip ? client_ip : "NULL",
row_count, sub_id[0] ? sub_id : "NULL", client_ip[0] ? client_ip : "NULL",
events_sent, created_at);
// Add basic subscription info
cJSON_AddStringToObject(sub_obj, "id", sub_id ? sub_id : "");
cJSON_AddStringToObject(sub_obj, "client_ip", client_ip ? client_ip : "");
cJSON_AddStringToObject(sub_obj, "id", sub_id);
cJSON_AddStringToObject(sub_obj, "client_ip", client_ip);
cJSON_AddNumberToObject(sub_obj, "created_at", (double)created_at);
cJSON_AddNumberToObject(sub_obj, "duration_seconds", (double)duration_seconds);
cJSON_AddNumberToObject(sub_obj, "events_sent", events_sent);
cJSON_AddBoolToObject(sub_obj, "active", 1); // All from this view are active
cJSON_AddStringToObject(sub_obj, "wsi_pointer", wsi_pointer ? wsi_pointer : "N/A");
cJSON_AddStringToObject(sub_obj, "wsi_pointer", wsi_pointer[0] ? wsi_pointer : "N/A");
// Extract query stats from per_session_data if wsi is still valid
int db_queries = 0;
@@ -317,7 +274,7 @@ cJSON* query_subscription_details(void) {
double row_rate = 0.0;
double avg_rows_per_query = 0.0;
if (wsi_pointer && strlen(wsi_pointer) > 2) { // Check for valid pointer string
if (wsi_pointer[0] != '\0' && strlen(wsi_pointer) > 2) { // Check for valid pointer string
// Parse wsi pointer from hex string
struct lws* wsi = NULL;
if (sscanf(wsi_pointer, "%p", (void**)&wsi) == 1 && wsi != NULL) {
@@ -326,7 +283,7 @@ cJSON* query_subscription_details(void) {
if (pss) {
db_queries = pss->db_queries_executed;
db_rows = pss->db_rows_returned;
// Calculate rates (per minute)
time_t connection_duration = current_time - pss->query_tracking_start;
if (connection_duration > 0) {
@@ -334,7 +291,7 @@ cJSON* query_subscription_details(void) {
query_rate = db_queries / minutes;
row_rate = db_rows / minutes;
}
// Calculate average rows per query
if (db_queries > 0) {
avg_rows_per_query = (double)db_rows / (double)db_queries;
@@ -365,8 +322,7 @@ cJSON* query_subscription_details(void) {
cJSON_AddItemToArray(subscriptions_array, sub_obj);
}
sqlite3_finalize(stmt);
cJSON_Delete(rows);
// Add subscriptions array and count to data
cJSON_AddItemToObject(data, "subscriptions", subscriptions_array);
@@ -378,9 +334,9 @@ cJSON* query_subscription_details(void) {
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("MONITOR", "subscription_details", NULL, sql, elapsed_us, row_count);
// DEBUG: Log final summary
DEBUG_LOG("Total subscriptions found: %d", row_count);
DEBUG_LOG("=== END SUBSCRIPTION_DETAILS QUERY DEBUG ===");
@@ -731,7 +687,6 @@ static const config_definition_t known_configs[] = {
{NULL, NULL, 0, 0}
};
// External database connection (from main.c)
extern sqlite3* g_db;
extern char g_database_path[512];
// Forward declarations for database functions
@@ -1036,138 +991,12 @@ char* execute_sql_query(const char* query, const char* request_id, char* error_m
return NULL;
}
if (!g_db) {
if (!db_is_available()) {
snprintf(error_message, error_size, "Database not available");
return NULL;
}
// Set busy timeout to prevent long-running queries (5 seconds)
sqlite3_busy_timeout(g_db, 5000);
// Prepare statement
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(g_db, query, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
const char* err_msg = sqlite3_errmsg(g_db);
snprintf(error_message, error_size, "SQL prepare failed: %s", err_msg);
return NULL;
}
// Execute query and collect results
cJSON* response = cJSON_CreateObject();
cJSON_AddStringToObject(response, "query_type", "sql_query");
cJSON_AddStringToObject(response, "request_id", request_id);
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
cJSON_AddStringToObject(response, "query", query);
// Get column information
int col_count = sqlite3_column_count(stmt);
cJSON* columns = cJSON_CreateArray();
for (int i = 0; i < col_count; i++) {
const char* col_name = sqlite3_column_name(stmt, i);
cJSON_AddItemToArray(columns, cJSON_CreateString(col_name ? col_name : ""));
}
cJSON_AddItemToObject(response, "columns", columns);
// Execute and collect rows (with limit)
cJSON* rows = cJSON_CreateArray();
int row_count = 0;
const int MAX_ROWS = 1000; // Configurable limit
struct timespec start_time;
clock_gettime(CLOCK_MONOTONIC, &start_time);
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW && row_count < MAX_ROWS) {
cJSON* row = cJSON_CreateArray();
for (int i = 0; i < col_count; i++) {
int col_type = sqlite3_column_type(stmt, i);
switch (col_type) {
case SQLITE_INTEGER:
cJSON_AddItemToArray(row, cJSON_CreateNumber((double)sqlite3_column_int64(stmt, i)));
break;
case SQLITE_FLOAT:
cJSON_AddItemToArray(row, cJSON_CreateNumber(sqlite3_column_double(stmt, i)));
break;
case SQLITE_TEXT: {
const char* text = (const char*)sqlite3_column_text(stmt, i);
cJSON_AddItemToArray(row, cJSON_CreateString(text ? text : ""));
break;
}
case SQLITE_BLOB: {
// Convert blob to hex string for JSON compatibility
const void* blob = sqlite3_column_blob(stmt, i);
int blob_size = sqlite3_column_bytes(stmt, i);
if (blob && blob_size > 0) {
char* hex_str = malloc(blob_size * 2 + 1);
if (hex_str) {
for (int j = 0; j < blob_size; j++) {
sprintf(hex_str + j * 2, "%02x", ((unsigned char*)blob)[j]);
}
hex_str[blob_size * 2] = '\0';
cJSON_AddItemToArray(row, cJSON_CreateString(hex_str));
free(hex_str);
} else {
cJSON_AddItemToArray(row, cJSON_CreateString("[BLOB]"));
}
} else {
cJSON_AddItemToArray(row, cJSON_CreateString(""));
}
break;
}
case SQLITE_NULL:
cJSON_AddItemToArray(row, cJSON_CreateNull());
break;
default:
cJSON_AddItemToArray(row, cJSON_CreateString("[UNKNOWN]"));
break;
}
}
cJSON_AddItemToArray(rows, row);
row_count++;
// Check timeout (additional safety check)
struct timespec current_time;
clock_gettime(CLOCK_MONOTONIC, &current_time);
double elapsed = (current_time.tv_sec - start_time.tv_sec) +
(current_time.tv_nsec - start_time.tv_nsec) / 1e9;
if (elapsed > 4.5) { // 4.5 seconds to allow for cleanup
break;
}
}
sqlite3_finalize(stmt);
// Check for execution errors
if (rc != SQLITE_DONE && rc != SQLITE_ROW) {
const char* err_msg = sqlite3_errmsg(g_db);
snprintf(error_message, error_size, "SQL execution failed: %s", err_msg);
cJSON_Delete(response);
return NULL;
}
// Check row limit
if (row_count >= MAX_ROWS) {
cJSON_AddStringToObject(response, "warning", "Result truncated to maximum row limit");
}
// Add metadata
cJSON_AddNumberToObject(response, "row_count", row_count);
cJSON_AddNumberToObject(response, "execution_time_ms", 0); // Will be set by caller
cJSON_AddItemToObject(response, "rows", rows);
// Convert to JSON string
char* json_result = cJSON_Print(response);
cJSON_Delete(response);
if (!json_result) {
snprintf(error_message, error_size, "Failed to generate JSON response");
return NULL;
}
return json_result;
return db_execute_readonly_query_json(query, request_id, error_message, error_size, 1000, 5000);
}
// Unified handler for SQL query commands
@@ -1336,8 +1165,7 @@ cJSON* query_cpu_metrics(void) {
// Generate stats JSON from database queries
char* generate_stats_json(void) {
extern sqlite3* g_db;
if (!g_db) {
if (!db_is_available()) {
DEBUG_ERROR("Database not available for stats generation");
return NULL;
}
@@ -1348,10 +1176,10 @@ char* generate_stats_json(void) {
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
// Get database file size
extern char g_database_path[512];
const char* db_path = db_get_database_path();
struct stat db_stat;
long long db_size = 0;
if (stat(g_database_path, &db_stat) == 0) {
if (db_path && db_path[0] != '\0' && stat(db_path, &db_stat) == 0) {
db_size = db_stat.st_size;
}
cJSON_AddNumberToObject(response, "database_size_bytes", db_size);
@@ -1362,85 +1190,80 @@ char* generate_stats_json(void) {
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
cJSON_AddNumberToObject(response, "active_subscriptions", active_subs);
// Query total events count
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM events", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
cJSON_AddNumberToObject(response, "total_events", sqlite3_column_int64(stmt, 0));
}
sqlite3_finalize(stmt);
long long total_events = 0;
if (db_get_total_event_count_ll(&total_events) == 0) {
cJSON_AddNumberToObject(response, "total_events", total_events);
}
// Query event kinds distribution
cJSON* event_kinds = cJSON_CreateArray();
if (sqlite3_prepare_v2(g_db, "SELECT kind, count, percentage FROM event_kinds_view ORDER BY count DESC", -1, &stmt, NULL) == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
long long kinds_total = 0;
cJSON* kind_rows = db_get_event_kind_distribution_rows(&kinds_total);
if (kind_rows) {
cJSON* kind_row = NULL;
cJSON_ArrayForEach(kind_row, kind_rows) {
cJSON* kind_obj = cJSON_CreateObject();
cJSON_AddNumberToObject(kind_obj, "kind", sqlite3_column_int(stmt, 0));
cJSON_AddNumberToObject(kind_obj, "count", sqlite3_column_int64(stmt, 1));
cJSON_AddNumberToObject(kind_obj, "percentage", sqlite3_column_double(stmt, 2));
cJSON* kind = cJSON_GetObjectItemCaseSensitive(kind_row, "kind");
cJSON* count = cJSON_GetObjectItemCaseSensitive(kind_row, "count");
double count_val = cJSON_IsNumber(count) ? count->valuedouble : 0.0;
double pct = (kinds_total > 0) ? ((count_val * 100.0) / (double)kinds_total) : 0.0;
cJSON_AddNumberToObject(kind_obj, "kind", cJSON_IsNumber(kind) ? kind->valuedouble : 0.0);
cJSON_AddNumberToObject(kind_obj, "count", count_val);
cJSON_AddNumberToObject(kind_obj, "percentage", pct);
cJSON_AddItemToArray(event_kinds, kind_obj);
}
sqlite3_finalize(stmt);
cJSON_Delete(kind_rows);
}
cJSON_AddItemToObject(response, "event_kinds", event_kinds);
// Query time-based statistics
cJSON* time_stats = cJSON_CreateObject();
if (sqlite3_prepare_v2(g_db, "SELECT period, total_events FROM time_stats_view", -1, &stmt, NULL) == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char* period = (const char*)sqlite3_column_text(stmt, 0);
sqlite3_int64 count = sqlite3_column_int64(stmt, 1);
if (strcmp(period, "total") == 0) {
cJSON_AddNumberToObject(time_stats, "total", count);
} else if (strcmp(period, "24h") == 0) {
cJSON_AddNumberToObject(time_stats, "last_24h", count);
} else if (strcmp(period, "7d") == 0) {
cJSON_AddNumberToObject(time_stats, "last_7d", count);
} else if (strcmp(period, "30d") == 0) {
cJSON_AddNumberToObject(time_stats, "last_30d", count);
}
}
sqlite3_finalize(stmt);
time_t now = time(NULL);
long long c24h = 0, c7d = 0, c30d = 0;
if (db_get_event_count_since(now - 86400, &c24h) == 0) {
cJSON_AddNumberToObject(time_stats, "last_24h", c24h);
}
if (db_get_event_count_since(now - 604800, &c7d) == 0) {
cJSON_AddNumberToObject(time_stats, "last_7d", c7d);
}
if (db_get_event_count_since(now - 2592000, &c30d) == 0) {
cJSON_AddNumberToObject(time_stats, "last_30d", c30d);
}
cJSON_AddNumberToObject(time_stats, "total", total_events);
cJSON_AddItemToObject(response, "time_stats", time_stats);
// Query top pubkeys
cJSON* top_pubkeys = cJSON_CreateArray();
if (sqlite3_prepare_v2(g_db, "SELECT pubkey, event_count, percentage FROM top_pubkeys_view ORDER BY event_count DESC LIMIT 10", -1, &stmt, NULL) == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
cJSON* pubkey_rows = db_get_top_pubkeys_rows(10);
if (pubkey_rows) {
cJSON* row = NULL;
cJSON_ArrayForEach(row, pubkey_rows) {
cJSON* pubkey_obj = cJSON_CreateObject();
const char* pubkey = (const char*)sqlite3_column_text(stmt, 0);
cJSON_AddStringToObject(pubkey_obj, "pubkey", pubkey ? pubkey : "");
cJSON_AddNumberToObject(pubkey_obj, "event_count", sqlite3_column_int64(stmt, 1));
cJSON_AddNumberToObject(pubkey_obj, "percentage", sqlite3_column_double(stmt, 2));
cJSON* pubkey_item = cJSON_GetObjectItemCaseSensitive(row, "pubkey");
cJSON* count_item = cJSON_GetObjectItemCaseSensitive(row, "count");
double count_val = cJSON_IsNumber(count_item) ? count_item->valuedouble : 0.0;
double pct = (total_events > 0) ? ((count_val * 100.0) / (double)total_events) : 0.0;
cJSON_AddStringToObject(pubkey_obj, "pubkey", cJSON_IsString(pubkey_item) ? pubkey_item->valuestring : "");
cJSON_AddNumberToObject(pubkey_obj, "event_count", count_val);
cJSON_AddNumberToObject(pubkey_obj, "percentage", pct);
cJSON_AddItemToArray(top_pubkeys, pubkey_obj);
}
sqlite3_finalize(stmt);
cJSON_Delete(pubkey_rows);
}
cJSON_AddItemToObject(response, "top_pubkeys", top_pubkeys);
// Get database creation timestamp (oldest event)
if (sqlite3_prepare_v2(g_db, "SELECT MIN(created_at) FROM events", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
sqlite3_int64 oldest_timestamp = sqlite3_column_int64(stmt, 0);
if (oldest_timestamp > 0) {
cJSON_AddNumberToObject(response, "database_created_at", (double)oldest_timestamp);
}
// Get database creation/latest timestamps
long long min_created_at = 0;
long long max_created_at = 0;
if (db_get_event_time_bounds(&min_created_at, &max_created_at) == 0) {
if (min_created_at > 0) {
cJSON_AddNumberToObject(response, "database_created_at", (double)min_created_at);
}
sqlite3_finalize(stmt);
}
// Get latest event timestamp
if (sqlite3_prepare_v2(g_db, "SELECT MAX(created_at) FROM events", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
sqlite3_int64 latest_timestamp = sqlite3_column_int64(stmt, 0);
if (latest_timestamp > 0) {
cJSON_AddNumberToObject(response, "latest_event_at", (double)latest_timestamp);
}
if (max_created_at > 0) {
cJSON_AddNumberToObject(response, "latest_event_at", (double)max_created_at);
}
sqlite3_finalize(stmt);
}
// Convert to JSON string
@@ -1567,8 +1390,7 @@ int send_nip17_response(const char* sender_pubkey, const char* response_content,
// Generate config text from database
char* generate_config_text(void) {
extern sqlite3* g_db;
if (!g_db) {
if (!db_is_available()) {
DEBUG_ERROR("NIP-17: Database not available for config query");
return NULL;
}
@@ -1587,25 +1409,29 @@ char* generate_config_text(void) {
"🔧 Relay Configuration\n"
"━━━━━━━━━━━━━━━━━━━━━━━━\n");
// Query all config values from database
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(g_db, "SELECT key, value FROM config ORDER BY key", -1, &stmt, NULL) == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW && offset < 8192 - 200) {
const char* key = (const char*)sqlite3_column_text(stmt, 0);
const char* value = (const char*)sqlite3_column_text(stmt, 1);
if (key && value) {
offset += snprintf(config_text + offset, 8192 - offset,
"%s: %s\n", key, value);
}
}
sqlite3_finalize(stmt);
} else {
cJSON* rows = db_get_all_config_rows();
if (!rows) {
free(config_text);
DEBUG_ERROR("NIP-17: Failed to query config from database");
return NULL;
}
cJSON* row = NULL;
cJSON_ArrayForEach(row, rows) {
if (offset >= 8192 - 200) break;
cJSON* key_item = cJSON_GetObjectItemCaseSensitive(row, "key");
cJSON* value_item = cJSON_GetObjectItemCaseSensitive(row, "value");
const char* key = cJSON_IsString(key_item) ? key_item->valuestring : NULL;
const char* value = cJSON_IsString(value_item) ? value_item->valuestring : NULL;
if (key && value) {
offset += snprintf(config_text + offset, 8192 - offset,
"%s: %s\n", key, value);
}
}
cJSON_Delete(rows);
// Footer
offset += snprintf(config_text + offset, 8192 - offset, "\n");
@@ -2166,8 +1992,7 @@ int apply_config_change(const char* key, const char* value) {
return -1;
}
extern sqlite3* g_db;
if (!g_db) {
if (!db_is_available()) {
DEBUG_ERROR("Database not available for config change");
return -1;
}
@@ -2200,30 +2025,11 @@ int apply_config_change(const char* key, const char* value) {
}
// Update or insert the configuration value
sqlite3_stmt* stmt;
const char* sql = "INSERT OR REPLACE INTO config (key, value, data_type) VALUES (?, ?, ?)";
if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL) != SQLITE_OK) {
DEBUG_ERROR("Failed to prepare config update statement");
const char* err_msg = sqlite3_errmsg(g_db);
DEBUG_ERROR(err_msg);
return -1;
}
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, normalized_value, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, data_type, -1, SQLITE_STATIC);
int result = sqlite3_step(stmt);
if (result != SQLITE_DONE) {
if (db_upsert_config_value(key, normalized_value, data_type) != 0) {
DEBUG_ERROR("Failed to update configuration in database");
const char* err_msg = sqlite3_errmsg(g_db);
DEBUG_ERROR(err_msg);
sqlite3_finalize(stmt);
return -1;
}
sqlite3_finalize(stmt);
return 0;
}
+451 -638
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -1,7 +1,6 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <sqlite3.h>
#include <cjson/cJSON.h>
#include <time.h>
#include <pthread.h>
+1318
View File
File diff suppressed because it is too large Load Diff
+114
View File
@@ -0,0 +1,114 @@
#ifndef DB_OPS_H
#define DB_OPS_H
#include <stddef.h>
#include <time.h>
#include <cjson/cJSON.h>
// Generic helpers
int db_init(const char* connection_string);
void db_close(void);
int db_is_available(void);
const char* db_last_error(void);
const char* db_get_database_path(void);
// Per-thread connection override (used by thread pool workers)
int db_set_thread_connection(void* connection);
void db_clear_thread_connection(void);
// DB result codes (backend-agnostic)
#define DB_OK 0
#define DB_ERROR 1
#define DB_CONSTRAINT 19
#define DB_MISUSE 21
#define DB_ROW 100
#define DB_DONE 101
typedef struct db_stmt db_stmt_t;
// Generic statement helpers (Phase 1 migration)
int db_prepare(const char* sql, db_stmt_t** out_stmt);
int db_bind_text_param(db_stmt_t* stmt, int index, const char* value);
int db_bind_int_param(db_stmt_t* stmt, int index, int value);
int db_bind_int64_param(db_stmt_t* stmt, int index, long long value);
int db_step_stmt(db_stmt_t* stmt);
int db_reset_stmt(db_stmt_t* stmt);
const char* db_column_text_value(db_stmt_t* stmt, int col);
int db_column_int_value(db_stmt_t* stmt, int col);
long long db_column_int64_value(db_stmt_t* stmt, int col);
double db_column_double_value(db_stmt_t* stmt, int col);
void db_finalize_stmt(db_stmt_t* stmt);
// Subscription logging
int db_log_subscription_created(const char* sub_id, const char* wsi_ptr,
const char* client_ip, const char* filter_json);
int db_log_subscription_closed(const char* sub_id, const char* client_ip);
int db_log_subscription_disconnected(const char* client_ip);
int db_update_subscription_events_sent(const char* sub_id, int events_sent);
int db_cleanup_orphaned_subscriptions(void);
// NIP-09 event deletion helpers
int db_get_event_pubkey(const char* event_id, char* pubkey_out, size_t pubkey_out_size);
int db_delete_event_by_id(const char* event_id, const char* requester_pubkey);
int db_delete_events_by_address(const char* pubkey, int kind,
const char* d_tag, long before_timestamp);
// Auth rule checks for request validator
int db_is_pubkey_blacklisted(const char* pubkey);
int db_is_hash_blacklisted(const char* resource_hash);
int db_is_pubkey_whitelisted(const char* pubkey);
int db_count_active_whitelist_rules(void);
// Generic prepared COUNT helper
int db_count_with_sql(const char* sql, const char** bind_params, int bind_param_count, int* out_count);
char* db_execute_readonly_query_json(const char* query, const char* request_id,
char* error_message, size_t error_size,
int max_rows, int timeout_ms);
// Monitoring/stat helpers (Phase 1 api.c migration)
int db_get_total_event_count_ll(long long* out_count);
int db_get_event_count_since(time_t cutoff, long long* out_count);
cJSON* db_get_event_kind_distribution_rows(long long* out_total_events);
cJSON* db_get_top_pubkeys_rows(int limit);
cJSON* db_get_subscription_details_rows(void);
// Config helpers
cJSON* db_get_all_config_rows(void);
char* db_get_config_value_dup(const char* key);
int db_set_config_value_full(const char* key, const char* value, const char* data_type,
const char* description, const char* category, int requires_restart);
int db_update_config_value_only(const char* key, const char* value);
int db_upsert_config_value(const char* key, const char* value, const char* data_type);
int db_store_relay_private_key_hex(const char* relay_privkey_hex);
char* db_get_relay_private_key_hex_dup(void);
int db_store_config_event(const cJSON* event);
// Event storage/timestamp/retrieval helpers
int db_insert_event_with_json(const char* id, const char* pubkey, long long created_at,
int kind, const char* event_type, const char* content,
const char* sig, const char* tags_json, const char* event_json,
int* out_step_rc, int* out_extended_errcode);
int db_get_event_time_bounds(long long* out_min_created_at, long long* out_max_created_at);
int db_event_id_exists(const char* event_id, int* out_exists);
cJSON* db_retrieve_event_by_id(const char* event_id);
char* db_get_latest_event_pubkey_for_kind_dup(int kind);
// Config table helpers
int db_get_config_row_count(int* out_count);
// Event tag helpers
int db_store_event_tags_cjson(const char* event_id, const cJSON* tags);
int db_populate_event_tags_from_existing(void);
// Auth/WoT rule helpers
int db_add_auth_rule(const char* rule_type, const char* pattern_type, const char* pattern_value);
int db_remove_auth_rule(const char* rule_type, const char* pattern_type, const char* pattern_value);
int db_delete_wot_whitelist_rules(void);
int db_count_wot_whitelist_rules(void);
// Schema/DDL helpers
int db_table_exists(const char* table_name, int* out_exists);
char* db_get_schema_version_dup(void);
int db_exec_sql(const char* sql);
#endif // DB_OPS_H
+3 -15
View File
@@ -5,6 +5,7 @@
#include "config.h"
#include "debug.h"
#include "api.h"
#include "db_ops.h"
#include "../nostr_core_lib/nostr_core/nostr_core.h"
#include "../nostr_core_lib/nostr_core/nip017.h"
#include "../nostr_core_lib/nostr_core/nip044.h"
@@ -17,9 +18,6 @@
#include <cjson/cJSON.h>
#include <libwebsockets.h>
// External database connection (from main.c)
extern sqlite3* g_db;
// Forward declarations for unified handlers
extern int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi);
extern int handle_config_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi);
@@ -622,8 +620,7 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
update_config_in_table("nip42_auth_required_subscriptions", "false");
// Clear wot_whitelist rules
const char* delete_sql = "DELETE FROM auth_rules WHERE rule_type = 'wot_whitelist'";
sqlite3_exec(g_db, delete_sql, NULL, NULL, NULL);
db_delete_wot_whitelist_rules();
snprintf(response_msg, sizeof(response_msg),
"🔓 Web of Trust Disabled\n"
@@ -695,16 +692,7 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
else if (strcmp(wot_cmd, "status") == 0 || strlen(wot_cmd) == 0) {
// Show status
// Count whitelisted pubkeys
sqlite3_stmt* stmt;
const char* count_sql = "SELECT COUNT(*) FROM auth_rules WHERE rule_type = 'wot_whitelist' AND active = 1";
int whitelist_count = 0;
if (sqlite3_prepare_v2(g_db, count_sql, -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
whitelist_count = sqlite3_column_int(stmt, 0);
}
sqlite3_finalize(stmt);
}
int whitelist_count = db_count_wot_whitelist_rules();
const char* level_str;
if (wot_level == 0) level_str = "Off (open relay)";
+54 -57
View File
@@ -2,6 +2,7 @@
#include "ip_ban.h"
#include "debug.h"
#include "config.h"
#include "db_ops.h"
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
@@ -100,8 +101,8 @@ void ip_ban_init(void) {
DEBUG_LOG("IP ban table initialized (%d slots)", IP_BAN_TABLE_SIZE);
}
void ip_ban_load_from_db(sqlite3* db) {
if (!db || !g_initialized) return;
void ip_ban_load_from_db(void) {
if (!db_is_available() || !g_initialized) return;
// Create table if it doesn't exist (handles existing databases)
const char* create_sql =
@@ -119,18 +120,16 @@ void ip_ban_load_from_db(sqlite3* db) {
" first_seen INTEGER NOT NULL DEFAULT 0,"
" updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))"
");";
char* err = NULL;
if (sqlite3_exec(db, create_sql, NULL, NULL, &err) != SQLITE_OK) {
DEBUG_ERROR("Failed to create ip_bans table: %s", err ? err : "unknown");
if (err) sqlite3_free(err);
if (db_exec_sql(create_sql) != 0) {
DEBUG_ERROR("Failed to create ip_bans table: %s", db_last_error());
return;
}
// Migration: Add idle_* columns if they don't exist (ignore errors if already exists)
sqlite3_exec(db, "ALTER TABLE ip_bans ADD COLUMN idle_failure_count INTEGER NOT NULL DEFAULT 0", NULL, NULL, NULL);
sqlite3_exec(db, "ALTER TABLE ip_bans ADD COLUMN idle_ban_count INTEGER NOT NULL DEFAULT 0", NULL, NULL, NULL);
sqlite3_exec(db, "ALTER TABLE ip_bans ADD COLUMN idle_banned_until INTEGER NOT NULL DEFAULT 0", NULL, NULL, NULL);
sqlite3_exec(db, "ALTER TABLE ip_bans ADD COLUMN idle_first_failure INTEGER NOT NULL DEFAULT 0", NULL, NULL, NULL);
(void)db_exec_sql("ALTER TABLE ip_bans ADD COLUMN idle_failure_count INTEGER NOT NULL DEFAULT 0");
(void)db_exec_sql("ALTER TABLE ip_bans ADD COLUMN idle_ban_count INTEGER NOT NULL DEFAULT 0");
(void)db_exec_sql("ALTER TABLE ip_bans ADD COLUMN idle_banned_until INTEGER NOT NULL DEFAULT 0");
(void)db_exec_sql("ALTER TABLE ip_bans ADD COLUMN idle_first_failure INTEGER NOT NULL DEFAULT 0");
const char* sql =
"SELECT ip, failure_count, ban_count, banned_until, first_failure,"
@@ -139,16 +138,16 @@ void ip_ban_load_from_db(sqlite3* db) {
" idle_failure_count, idle_ban_count, idle_banned_until, idle_first_failure"
" FROM ip_bans";
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
db_stmt_t* stmt;
if (db_prepare(sql, &stmt) != DB_OK) {
DEBUG_ERROR("Failed to prepare ip_bans load query");
return;
}
int loaded = 0;
pthread_mutex_lock(&g_ban_mutex);
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char* ip = (const char*)sqlite3_column_text(stmt, 0);
while (db_step_stmt(stmt) == DB_ROW) {
const char* ip = db_column_text_value(stmt, 0);
if (!ip) continue;
int idx = find_slot(ip);
@@ -158,25 +157,25 @@ void ip_ban_load_from_db(sqlite3* db) {
entry->state = IP_BAN_ACTIVE;
strncpy(entry->ip, ip, sizeof(entry->ip) - 1);
entry->ip[sizeof(entry->ip) - 1] = '\0';
entry->failure_count = sqlite3_column_int(stmt, 1);
entry->ban_count = sqlite3_column_int(stmt, 2);
entry->banned_until = (time_t)sqlite3_column_int64(stmt, 3);
entry->first_failure = (time_t)sqlite3_column_int64(stmt, 4);
entry->has_authed_successfully = sqlite3_column_int(stmt, 5);
entry->last_success_at = (time_t)sqlite3_column_int64(stmt, 6);
entry->total_connections = sqlite3_column_int(stmt, 7);
entry->total_failures = sqlite3_column_int(stmt, 8);
entry->total_successes = sqlite3_column_int(stmt, 9);
entry->first_seen = (time_t)sqlite3_column_int64(stmt, 10);
entry->failure_count = db_column_int_value(stmt, 1);
entry->ban_count = db_column_int_value(stmt, 2);
entry->banned_until = (time_t)db_column_int64_value(stmt, 3);
entry->first_failure = (time_t)db_column_int64_value(stmt, 4);
entry->has_authed_successfully = db_column_int_value(stmt, 5);
entry->last_success_at = (time_t)db_column_int64_value(stmt, 6);
entry->total_connections = db_column_int_value(stmt, 7);
entry->total_failures = db_column_int_value(stmt, 8);
entry->total_successes = db_column_int_value(stmt, 9);
entry->first_seen = (time_t)db_column_int64_value(stmt, 10);
// Load idle tracking fields (default to 0 if columns don't exist yet)
entry->idle_failure_count = sqlite3_column_int(stmt, 11);
entry->idle_ban_count = sqlite3_column_int(stmt, 12);
entry->idle_banned_until = (time_t)sqlite3_column_int64(stmt, 13);
entry->idle_first_failure = (time_t)sqlite3_column_int64(stmt, 14);
entry->idle_failure_count = db_column_int_value(stmt, 11);
entry->idle_ban_count = db_column_int_value(stmt, 12);
entry->idle_banned_until = (time_t)db_column_int64_value(stmt, 13);
entry->idle_first_failure = (time_t)db_column_int64_value(stmt, 14);
loaded++;
}
pthread_mutex_unlock(&g_ban_mutex);
sqlite3_finalize(stmt);
db_finalize_stmt(stmt);
// Count how many are still actively banned
time_t now = time(NULL);
@@ -193,8 +192,8 @@ void ip_ban_load_from_db(sqlite3* db) {
DEBUG_WARN("IP ban table loaded: %d IPs restored (%d still banned)", loaded, still_banned);
}
void ip_ban_save_to_db(sqlite3* db) {
if (!db || !g_initialized) return;
void ip_ban_save_to_db(void) {
if (!db_is_available() || !g_initialized) return;
const char* upsert_sql =
"INSERT OR REPLACE INTO ip_bans"
@@ -204,13 +203,13 @@ void ip_ban_save_to_db(sqlite3* db) {
" idle_failure_count, idle_ban_count, idle_banned_until, idle_first_failure)"
" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, strftime('%s', 'now'), ?, ?, ?, ?)";
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db, upsert_sql, -1, &stmt, NULL) != SQLITE_OK) {
db_stmt_t* stmt;
if (db_prepare(upsert_sql, &stmt) != DB_OK) {
DEBUG_ERROR("Failed to prepare ip_bans save query");
return;
}
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL);
(void)db_exec_sql("BEGIN TRANSACTION");
int saved = 0;
pthread_mutex_lock(&g_ban_mutex);
@@ -218,25 +217,25 @@ void ip_ban_save_to_db(sqlite3* db) {
ip_ban_entry_t* entry = &g_ban_table[i];
if (entry->state != IP_BAN_ACTIVE) continue;
sqlite3_reset(stmt);
sqlite3_bind_text(stmt, 1, entry->ip, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 2, entry->failure_count);
sqlite3_bind_int(stmt, 3, entry->ban_count);
sqlite3_bind_int64(stmt, 4, (sqlite3_int64)entry->banned_until);
sqlite3_bind_int64(stmt, 5, (sqlite3_int64)entry->first_failure);
sqlite3_bind_int(stmt, 6, entry->has_authed_successfully);
sqlite3_bind_int64(stmt, 7, (sqlite3_int64)entry->last_success_at);
sqlite3_bind_int(stmt, 8, entry->total_connections);
sqlite3_bind_int(stmt, 9, entry->total_failures);
sqlite3_bind_int(stmt, 10, entry->total_successes);
sqlite3_bind_int64(stmt, 11, (sqlite3_int64)entry->first_seen);
(void)db_reset_stmt(stmt);
db_bind_text_param(stmt, 1, entry->ip);
db_bind_int_param(stmt, 2, entry->failure_count);
db_bind_int_param(stmt, 3, entry->ban_count);
db_bind_int64_param(stmt, 4, (long long)entry->banned_until);
db_bind_int64_param(stmt, 5, (long long)entry->first_failure);
db_bind_int_param(stmt, 6, entry->has_authed_successfully);
db_bind_int64_param(stmt, 7, (long long)entry->last_success_at);
db_bind_int_param(stmt, 8, entry->total_connections);
db_bind_int_param(stmt, 9, entry->total_failures);
db_bind_int_param(stmt, 10, entry->total_successes);
db_bind_int64_param(stmt, 11, (long long)entry->first_seen);
// Idle tracking fields
sqlite3_bind_int(stmt, 12, entry->idle_failure_count);
sqlite3_bind_int(stmt, 13, entry->idle_ban_count);
sqlite3_bind_int64(stmt, 14, (sqlite3_int64)entry->idle_banned_until);
sqlite3_bind_int64(stmt, 15, (sqlite3_int64)entry->idle_first_failure);
db_bind_int_param(stmt, 12, entry->idle_failure_count);
db_bind_int_param(stmt, 13, entry->idle_ban_count);
db_bind_int64_param(stmt, 14, (long long)entry->idle_banned_until);
db_bind_int64_param(stmt, 15, (long long)entry->idle_first_failure);
if (sqlite3_step(stmt) != SQLITE_DONE) {
if (db_step_stmt(stmt) != DB_DONE) {
DEBUG_WARN("Failed to save ip_ban entry for %s", entry->ip);
} else {
saved++;
@@ -244,8 +243,8 @@ void ip_ban_save_to_db(sqlite3* db) {
}
pthread_mutex_unlock(&g_ban_mutex);
sqlite3_finalize(stmt);
sqlite3_exec(db, "COMMIT", NULL, NULL, NULL);
db_finalize_stmt(stmt);
(void)db_exec_sql("COMMIT");
DEBUG_TRACE("IP ban table saved: %d entries written to DB", saved);
}
@@ -547,7 +546,7 @@ int ip_ban_get_tracked_count(void) {
return count;
}
void ip_ban_log_stats(sqlite3* db) {
void ip_ban_log_stats(void) {
if (!g_initialized) return;
static time_t last_log = 0;
@@ -557,9 +556,7 @@ void ip_ban_log_stats(sqlite3* db) {
last_log = now;
// Save to DB every 5 minutes
if (db) {
ip_ban_save_to_db(db);
}
ip_ban_save_to_db();
pthread_mutex_lock(&g_ban_mutex);
int auth_banned_count = 0;
+3 -4
View File
@@ -15,7 +15,6 @@
// auth_fail_ban_duration_sec int default: 300 (initial ban duration, doubles each time)
#include <time.h>
#include <sqlite3.h>
// Maximum number of IPs tracked simultaneously (fixed-size, no malloc)
#define IP_BAN_TABLE_SIZE 4096
@@ -25,11 +24,11 @@ void ip_ban_init(void);
// Load ban state from the ip_bans database table.
// Call after ip_ban_init() and after the database is open.
void ip_ban_load_from_db(sqlite3* db);
void ip_ban_load_from_db(void);
// Save current ban state to the ip_bans database table.
// Called every 5 minutes from the maintenance timer.
void ip_ban_save_to_db(sqlite3* db);
void ip_ban_save_to_db(void);
// Check if an IP is currently banned.
// Returns 1 if banned (connection should be rejected), 0 if allowed.
@@ -57,7 +56,7 @@ void ip_ban_cleanup(void);
// Emit a periodic WARN-level log summary of banned IPs (every 5 minutes).
// Also saves state to DB.
void ip_ban_log_stats(sqlite3* db);
void ip_ban_log_stats(void);
// Get stats for logging/monitoring
int ip_ban_get_banned_count(void);
+529 -463
View File
File diff suppressed because it is too large Load Diff
+22 -4
View File
@@ -11,10 +11,10 @@
// Version information (auto-updated by build system)
// Using CRELAY_ prefix to avoid conflicts with nostr_core_lib VERSION macros
#define CRELAY_VERSION_MAJOR 1
#define CRELAY_VERSION_MINOR 2
#define CRELAY_VERSION_PATCH 56
#define CRELAY_VERSION "v1.2.56"
#define CRELAY_VERSION_MAJOR 2
#define CRELAY_VERSION_MINOR 1
#define CRELAY_VERSION_PATCH 5
#define CRELAY_VERSION "v2.1.5"
// Relay metadata (authoritative source for NIP-11 information)
#define RELAY_NAME "C-Relay"
@@ -28,4 +28,22 @@
#define POSTING_POLICY ""
#define PAYMENTS_URL ""
// Forward declaration to avoid pulling cJSON headers into all includers.
typedef struct cJSON cJSON;
// Async REQ handler status used by websocket callback to defer EOSE until worker completion
#define HANDLE_REQ_ASYNC_PENDING (-2)
// Async EVENT storage split:
// - store_event_core(): worker-safe core DB write path
// returns 0 when inserted, 1 when handled without insert (duplicate/ephemeral), -1 on error
// - store_event_post_actions(): main-thread-only follow-up actions (monitoring/tags/WoT sync)
// - store_event(): legacy wrapper preserving original behavior for synchronous call sites
int store_event_core(cJSON* event);
void store_event_post_actions(cJSON* event);
int store_event(cJSON* event);
// Drains completed async REQ jobs and queues EVENT/EOSE on the lws service thread.
void process_req_async_completions(void);
#endif /* MAIN_H */
+33 -89
View File
@@ -7,7 +7,7 @@
/////////////////////////////////////////////////////////////////////////////////////////
#include <cjson/cJSON.h>
#include "debug.h"
#include <sqlite3.h>
#include "db_ops.h"
#include <string.h>
#include <stdlib.h>
#include <time.h>
@@ -21,10 +21,6 @@ int store_event(cJSON* event);
int delete_events_by_id(const char* requester_pubkey, cJSON* event_ids);
int delete_events_by_address(const char* requester_pubkey, cJSON* addresses, long deletion_timestamp);
// Global database variable
extern sqlite3* g_db;
// Handle NIP-09 deletion request event (kind 5)
int handle_deletion_request(cJSON* event, char* error_message, size_t error_size) {
if (!event) {
@@ -146,97 +142,72 @@ int handle_deletion_request(cJSON* event, char* error_message, size_t error_size
// Delete events by ID (with pubkey authorization)
int delete_events_by_id(const char* requester_pubkey, cJSON* event_ids) {
if (!g_db || !requester_pubkey || !event_ids || !cJSON_IsArray(event_ids)) {
if (!db_is_available() || !requester_pubkey || !event_ids || !cJSON_IsArray(event_ids)) {
return 0;
}
int deleted_count = 0;
cJSON* event_id = NULL;
cJSON_ArrayForEach(event_id, event_ids) {
if (!cJSON_IsString(event_id)) {
continue;
}
const char* id = cJSON_GetStringValue(event_id);
// First check if event exists and if requester is authorized
const char* check_sql = "SELECT pubkey FROM events WHERE id = ?";
sqlite3_stmt* check_stmt;
int rc = sqlite3_prepare_v2(g_db, check_sql, -1, &check_stmt, NULL);
if (rc != SQLITE_OK) {
char event_pubkey[65] = {0};
int exists = db_get_event_pubkey(id, event_pubkey, sizeof(event_pubkey));
if (exists <= 0) {
continue;
}
sqlite3_bind_text(check_stmt, 1, id, -1, SQLITE_STATIC);
if (sqlite3_step(check_stmt) == SQLITE_ROW) {
const char* event_pubkey = (char*)sqlite3_column_text(check_stmt, 0);
// Only delete if the requester is the author
if (event_pubkey && strcmp(event_pubkey, requester_pubkey) == 0) {
sqlite3_finalize(check_stmt);
// Delete the event
const char* delete_sql = "DELETE FROM events WHERE id = ? AND pubkey = ?";
sqlite3_stmt* delete_stmt;
rc = sqlite3_prepare_v2(g_db, delete_sql, -1, &delete_stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(delete_stmt, 1, id, -1, SQLITE_STATIC);
sqlite3_bind_text(delete_stmt, 2, requester_pubkey, -1, SQLITE_STATIC);
if (sqlite3_step(delete_stmt) == SQLITE_DONE && sqlite3_changes(g_db) > 0) {
deleted_count++;
}
sqlite3_finalize(delete_stmt);
}
} else {
sqlite3_finalize(check_stmt);
char warning_msg[128];
snprintf(warning_msg, sizeof(warning_msg), "Unauthorized deletion attempt for event: %.16s...", id);
DEBUG_WARN(warning_msg);
// Only delete if the requester is the author
if (strcmp(event_pubkey, requester_pubkey) == 0) {
int changes = db_delete_event_by_id(id, requester_pubkey);
if (changes > 0) {
deleted_count += changes;
}
} else {
sqlite3_finalize(check_stmt);
char warning_msg[128];
snprintf(warning_msg, sizeof(warning_msg), "Unauthorized deletion attempt for event: %.16s...", id);
DEBUG_WARN(warning_msg);
}
}
return deleted_count;
}
// Delete events by addressable reference (kind:pubkey:d-identifier)
int delete_events_by_address(const char* requester_pubkey, cJSON* addresses, long deletion_timestamp) {
if (!g_db || !requester_pubkey || !addresses || !cJSON_IsArray(addresses)) {
if (!db_is_available() || !requester_pubkey || !addresses || !cJSON_IsArray(addresses)) {
return 0;
}
int deleted_count = 0;
cJSON* address = NULL;
cJSON_ArrayForEach(address, addresses) {
if (!cJSON_IsString(address)) {
continue;
}
const char* addr = cJSON_GetStringValue(address);
// Parse address format: kind:pubkey:d-identifier
char* addr_copy = strdup(addr);
if (!addr_copy) continue;
char* kind_str = strtok(addr_copy, ":");
char* pubkey_str = strtok(NULL, ":");
char* d_identifier = strtok(NULL, ":");
if (!kind_str || !pubkey_str) {
free(addr_copy);
continue;
}
int kind = atoi(kind_str);
// Only delete if the requester is the author
if (strcmp(pubkey_str, requester_pubkey) != 0) {
free(addr_copy);
@@ -245,42 +216,15 @@ int delete_events_by_address(const char* requester_pubkey, cJSON* addresses, lon
DEBUG_WARN(warning_msg);
continue;
}
// Build deletion query based on whether we have d-identifier
const char* delete_sql;
sqlite3_stmt* delete_stmt;
if (d_identifier && strlen(d_identifier) > 0) {
// Delete specific addressable event with d-tag
delete_sql = "DELETE FROM events WHERE kind = ? AND pubkey = ? AND created_at <= ? "
"AND json_extract(tags, '$[*]') LIKE '%[\"d\",\"' || ? || '\"]%'";
} else {
// Delete all events of this kind by this author up to deletion timestamp
delete_sql = "DELETE FROM events WHERE kind = ? AND pubkey = ? AND created_at <= ?";
int changes = db_delete_events_by_address(requester_pubkey, kind, d_identifier, deletion_timestamp);
if (changes > 0) {
deleted_count += changes;
}
int rc = sqlite3_prepare_v2(g_db, delete_sql, -1, &delete_stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_int(delete_stmt, 1, kind);
sqlite3_bind_text(delete_stmt, 2, requester_pubkey, -1, SQLITE_STATIC);
sqlite3_bind_int64(delete_stmt, 3, deletion_timestamp);
if (d_identifier && strlen(d_identifier) > 0) {
sqlite3_bind_text(delete_stmt, 4, d_identifier, -1, SQLITE_STATIC);
}
if (sqlite3_step(delete_stmt) == SQLITE_DONE) {
int changes = sqlite3_changes(g_db);
if (changes > 0) {
deleted_count += changes;
}
}
sqlite3_finalize(delete_stmt);
}
free(addr_copy);
}
return deleted_count;
}
+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) {
+39 -96
View File
@@ -17,17 +17,13 @@
#include "../nostr_core_lib/nostr_core/utils.h"
#include "debug.h" // C-relay debug system
#include "config.h" // C-relay configuration system
#include <sqlite3.h>
#include "db_ops.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
// External references to C-relay global state
extern sqlite3* g_db;
extern char g_database_path[512];
// Forward declaration for C-relay event storage function
extern int store_event(cJSON* event);
@@ -133,6 +129,14 @@ typedef struct {
static nip42_challenge_manager_t g_challenge_manager = {0};
static int g_validator_initialized = 0;
typedef struct {
int has_active_whitelist_rules;
time_t last_refresh;
} auth_rules_fast_cache_t;
static auth_rules_fast_cache_t g_auth_rules_fast_cache = {0};
#define AUTH_RULES_CACHE_TTL_SEC 5
// Last rule violation details for status code mapping
struct {
char violation_type[100]; // "pubkey_blacklist", "hash_blacklist",
@@ -506,10 +510,10 @@ void nostr_request_result_free_file_data(nostr_request_result_t *result) {
/**
* Force cache refresh - cache no longer exists, function kept for compatibility
* Force cache refresh for auth rule fast cache.
*/
void nostr_request_validator_force_cache_refresh(void) {
// Cache no longer exists - direct database queries are used
g_auth_rules_fast_cache.last_refresh = 0;
}
/**
@@ -524,118 +528,57 @@ static int reload_auth_config(void) {
// Note: Blossom protocol validation removed - C-relay uses standard Nostr events only
static int has_active_whitelist_rules_cached(void) {
time_t now = time(NULL);
if (g_auth_rules_fast_cache.last_refresh == 0 ||
(now - g_auth_rules_fast_cache.last_refresh) >= AUTH_RULES_CACHE_TTL_SEC) {
g_auth_rules_fast_cache.has_active_whitelist_rules =
(db_count_active_whitelist_rules() > 0) ? 1 : 0;
g_auth_rules_fast_cache.last_refresh = now;
}
return g_auth_rules_fast_cache.has_active_whitelist_rules;
}
/**
* Check database authentication rules for the request
* Implements the 6-step rule evaluation engine from AUTH_API.md
*/
int check_database_auth_rules(const char *pubkey, const char *operation __attribute__((unused)),
const char *resource_hash) {
sqlite3 *db = NULL;
sqlite3_stmt *stmt = NULL;
int rc;
DEBUG_TRACE("Checking auth rules for pubkey: %s", pubkey);
if (!pubkey) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Open database using global database path
if (strlen(g_database_path) == 0) {
return NOSTR_SUCCESS; // Default allow on DB error
}
rc = sqlite3_open_v2(g_database_path, &db, SQLITE_OPEN_READONLY, NULL);
if (rc != SQLITE_OK) {
return NOSTR_SUCCESS; // Default allow on DB error
}
// Step 1: Check pubkey blacklist (highest priority)
const char *blacklist_sql =
"SELECT rule_type FROM auth_rules WHERE rule_type = "
"'blacklist' AND pattern_type = 'pubkey' AND pattern_value = ? AND active = 1 LIMIT 1";
DEBUG_TRACE("Blacklist SQL: %s", blacklist_sql);
rc = sqlite3_prepare_v2(db, blacklist_sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC);
int step_result = sqlite3_step(stmt);
DEBUG_TRACE("Blacklist query result: %s", step_result == SQLITE_ROW ? "FOUND" : "NOT_FOUND");
if (step_result == SQLITE_ROW) {
DEBUG_TRACE("BLACKLIST HIT: Denying access for pubkey: %s", pubkey);
// Set specific violation details for status code mapping
strcpy(g_last_rule_violation.violation_type, "pubkey_blacklist");
sprintf(g_last_rule_violation.reason, "Public key blacklisted");
sqlite3_finalize(stmt);
sqlite3_close(db);
return NOSTR_ERROR_AUTH_REQUIRED;
}
sqlite3_finalize(stmt);
if (db_is_pubkey_blacklisted(pubkey)) {
DEBUG_TRACE("BLACKLIST HIT: Denying access for pubkey: %s", pubkey);
strcpy(g_last_rule_violation.violation_type, "pubkey_blacklist");
sprintf(g_last_rule_violation.reason, "Public key blacklisted");
return NOSTR_ERROR_AUTH_REQUIRED;
}
// Step 2: Check hash blacklist
if (resource_hash) {
const char *hash_blacklist_sql =
"SELECT rule_type FROM auth_rules WHERE rule_type = "
"'blacklist' AND pattern_type = 'hash' AND pattern_value = ? AND active = 1 LIMIT 1";
rc = sqlite3_prepare_v2(db, hash_blacklist_sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, resource_hash, -1, SQLITE_STATIC);
if (sqlite3_step(stmt) == SQLITE_ROW) {
// Set specific violation details for status code mapping
strcpy(g_last_rule_violation.violation_type, "hash_blacklist");
sprintf(g_last_rule_violation.reason, "File hash blacklisted");
sqlite3_finalize(stmt);
sqlite3_close(db);
return NOSTR_ERROR_AUTH_REQUIRED;
}
sqlite3_finalize(stmt);
}
if (resource_hash && db_is_hash_blacklisted(resource_hash)) {
strcpy(g_last_rule_violation.violation_type, "hash_blacklist");
sprintf(g_last_rule_violation.reason, "File hash blacklisted");
return NOSTR_ERROR_AUTH_REQUIRED;
}
// Step 3: Check pubkey whitelist (includes wot_whitelist)
const char *whitelist_sql =
"SELECT rule_type FROM auth_rules WHERE rule_type IN "
"('whitelist', 'wot_whitelist') AND pattern_type = 'pubkey' AND pattern_value = ? AND active = 1 LIMIT 1";
rc = sqlite3_prepare_v2(db, whitelist_sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC);
if (sqlite3_step(stmt) == SQLITE_ROW) {
sqlite3_finalize(stmt);
sqlite3_close(db);
return NOSTR_SUCCESS; // Allow whitelisted pubkey
}
sqlite3_finalize(stmt);
if (db_is_pubkey_whitelisted(pubkey)) {
return NOSTR_SUCCESS; // Allow whitelisted pubkey
}
// Step 4: Check if any whitelist rules exist - if yes, deny by default (includes wot_whitelist)
const char *whitelist_exists_sql =
"SELECT COUNT(*) FROM auth_rules WHERE rule_type IN ('whitelist', 'wot_whitelist') "
"AND pattern_type = 'pubkey' AND active = 1 LIMIT 1";
rc = sqlite3_prepare_v2(db, whitelist_exists_sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
int whitelist_count = sqlite3_column_int(stmt, 0);
if (whitelist_count > 0) {
// Set specific violation details for status code mapping
strcpy(g_last_rule_violation.violation_type, "whitelist_violation");
strcpy(g_last_rule_violation.reason,
"Public key not whitelisted for this operation");
sqlite3_finalize(stmt);
sqlite3_close(db);
return NOSTR_ERROR_AUTH_REQUIRED;
}
}
sqlite3_finalize(stmt);
// Step 4: If any whitelist rules exist, deny by default
if (has_active_whitelist_rules_cached()) {
strcpy(g_last_rule_violation.violation_type, "whitelist_violation");
strcpy(g_last_rule_violation.reason,
"Public key not whitelisted for this operation");
return NOSTR_ERROR_AUTH_REQUIRED;
}
sqlite3_close(db);
return NOSTR_SUCCESS; // Default allow if no restrictive rules matched
}
+25 -124
View File
@@ -1,7 +1,7 @@
#define _GNU_SOURCE
#include <cjson/cJSON.h>
#include "debug.h"
#include <sqlite3.h>
#include "db_ops.h"
#include <string.h>
#include <stdlib.h>
#include <time.h>
@@ -28,9 +28,6 @@ int validate_search_term(const char* search_term, char* error_message, size_t er
// Forward declaration for monitoring function
void monitoring_on_subscription_change(void);
// Global database variable
extern sqlite3* g_db;
// Configuration functions from config.c
extern int get_config_bool(const char* key, int default_value);
@@ -991,21 +988,21 @@ int has_subscriptions_for_kind(int event_kind) {
// Log subscription creation to database
void log_subscription_created(const subscription_t* sub) {
if (!g_db || !sub) return;
if (!sub) return;
// Convert wsi pointer to string
char wsi_str[32];
snprintf(wsi_str, sizeof(wsi_str), "%p", (void*)sub->wsi);
// Create filter JSON for logging
char* filter_json = NULL;
if (sub->filters) {
cJSON* filters_array = cJSON_CreateArray();
subscription_filter_t* filter = sub->filters;
while (filter) {
cJSON* filter_obj = cJSON_CreateObject();
if (filter->kinds) {
cJSON_AddItemToObject(filter_obj, "kinds", cJSON_Duplicate(filter->kinds, 1));
}
@@ -1034,100 +1031,33 @@ void log_subscription_created(const subscription_t* sub) {
}
cJSON_Delete(tags_obj);
}
cJSON_AddItemToArray(filters_array, filter_obj);
filter = filter->next;
}
filter_json = cJSON_Print(filters_array);
cJSON_Delete(filters_array);
}
// Use INSERT OR REPLACE to handle duplicates automatically
const char* sql =
"INSERT OR REPLACE INTO subscriptions (subscription_id, wsi_pointer, client_ip, event_type, filter_json) "
"VALUES (?, ?, ?, 'created', ?)";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, sub->id, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, wsi_str, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 3, sub->client_ip, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 4, filter_json ? filter_json : "[]", -1, SQLITE_TRANSIENT);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
db_log_subscription_created(sub->id, wsi_str, sub->client_ip, filter_json ? filter_json : "[]");
if (filter_json) free(filter_json);
}
// Log subscription closure to database
void log_subscription_closed(const char* sub_id, const char* client_ip, const char* reason) {
(void)reason; // Mark as intentionally unused
if (!g_db || !sub_id) return;
const char* sql =
"INSERT INTO subscriptions (subscription_id, wsi_pointer, client_ip, event_type) "
"VALUES (?, '', ?, 'closed')";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, sub_id, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, client_ip ? client_ip : "unknown", -1, SQLITE_STATIC);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
// Update the corresponding 'created' entry with end time and events sent
const char* update_sql =
"UPDATE subscriptions "
"SET ended_at = strftime('%s', 'now') "
"WHERE subscription_id = ? AND event_type = 'created' AND ended_at IS NULL";
rc = sqlite3_prepare_v2(g_db, update_sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, sub_id, -1, SQLITE_STATIC);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
if (!sub_id) return;
db_log_subscription_closed(sub_id, client_ip);
}
// Log subscription disconnection to database
void log_subscription_disconnected(const char* client_ip) {
if (!g_db || !client_ip) return;
// Mark all active subscriptions for this client as disconnected
const char* sql =
"UPDATE subscriptions "
"SET ended_at = strftime('%s', 'now') "
"WHERE client_ip = ? AND event_type = 'created' AND ended_at IS NULL";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, client_ip, -1, SQLITE_STATIC);
int changes = sqlite3_changes(g_db);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (changes > 0) {
// Log a disconnection event
const char* insert_sql =
"INSERT INTO subscriptions (subscription_id, wsi_pointer, client_ip, event_type) "
"VALUES ('disconnect', '', ?, 'disconnected')";
rc = sqlite3_prepare_v2(g_db, insert_sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, client_ip, -1, SQLITE_STATIC);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
}
}
if (!client_ip) return;
db_log_subscription_disconnected(client_ip);
}
// Log event broadcast to database (optional, can be resource intensive)
@@ -1153,55 +1083,26 @@ void log_subscription_disconnected(const char* client_ip) {
// Update events sent counter for a subscription
void update_subscription_events_sent(const char* sub_id, int events_sent) {
if (!g_db || !sub_id) return;
if (!sub_id) return;
const char* sql =
"UPDATE subscriptions "
"SET events_sent = ? "
"WHERE subscription_id = ? AND event_type = 'created'";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_int(stmt, 1, events_sent);
sqlite3_bind_text(stmt, 2, sub_id, -1, SQLITE_STATIC);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
db_update_subscription_events_sent(sub_id, events_sent);
}
// Cleanup all subscriptions on startup
void cleanup_all_subscriptions_on_startup(void) {
if (!g_db) {
if (!db_is_available()) {
DEBUG_ERROR("Database not available for startup cleanup");
return;
}
DEBUG_LOG("Performing startup subscription cleanup");
// Mark all active subscriptions as disconnected
const char* sql =
"UPDATE subscriptions "
"SET ended_at = strftime('%s', 'now') "
"WHERE event_type = 'created' AND ended_at IS NULL";
sqlite3_stmt* stmt;
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
DEBUG_ERROR("Failed to prepare startup cleanup query");
return;
}
rc = sqlite3_step(stmt);
int changes = sqlite3_changes(g_db);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
int changes = db_cleanup_orphaned_subscriptions();
if (changes < 0) {
DEBUG_ERROR("Failed to execute startup cleanup");
return;
}
if (changes > 0) {
DEBUG_LOG("Startup cleanup: marked %d orphaned subscriptions as disconnected", changes);
} else {
+844
View File
@@ -0,0 +1,844 @@
#define _GNU_SOURCE
#include "thread_pool.h"
#include "debug.h"
#include "db_ops.h"
#include <sqlite3.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct thread_pool_job_node {
uint64_t job_id;
thread_pool_job_t job;
struct thread_pool_job_node* next;
} thread_pool_job_node_t;
typedef struct {
thread_pool_job_node_t* head;
thread_pool_job_node_t* tail;
int size;
int max_size;
pthread_mutex_t mutex;
pthread_cond_t cond;
} thread_pool_queue_t;
typedef struct {
int running;
uint64_t next_job_id;
int reader_count;
pthread_t* readers;
pthread_t writer;
thread_pool_queue_t read_q;
thread_pool_queue_t write_q;
thread_pool_wake_loop_cb wake_loop_cb;
void* wake_loop_ctx;
char db_path[512];
pthread_mutex_t state_mutex;
} thread_pool_state_t;
static thread_pool_state_t g_pool = {0};
typedef struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
int done;
thread_pool_status_t status;
void* result_data;
size_t result_size;
} thread_pool_wait_ctx_t;
static void wait_ctx_init(thread_pool_wait_ctx_t* ctx) {
memset(ctx, 0, sizeof(*ctx));
pthread_mutex_init(&ctx->mutex, NULL);
pthread_cond_init(&ctx->cond, NULL);
}
static void wait_ctx_destroy(thread_pool_wait_ctx_t* ctx) {
pthread_mutex_destroy(&ctx->mutex);
pthread_cond_destroy(&ctx->cond);
}
static void wait_ctx_result_cb(const thread_pool_result_t* result, void* user_ctx) {
thread_pool_wait_ctx_t* ctx = (thread_pool_wait_ctx_t*)user_ctx;
if (!ctx || !result) return;
pthread_mutex_lock(&ctx->mutex);
ctx->status = result->status;
ctx->result_data = result->result_data;
ctx->result_size = result->result_size;
ctx->done = 1;
pthread_cond_signal(&ctx->cond);
pthread_mutex_unlock(&ctx->mutex);
}
static void wait_ctx_wait(thread_pool_wait_ctx_t* ctx) {
pthread_mutex_lock(&ctx->mutex);
while (!ctx->done) {
pthread_cond_wait(&ctx->cond, &ctx->mutex);
}
pthread_mutex_unlock(&ctx->mutex);
}
static void free_count_payload(void* p) {
thread_pool_count_payload_t* payload = (thread_pool_count_payload_t*)p;
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 free_req_payload(void* p) {
thread_pool_req_payload_t* payload = (thread_pool_req_payload_t*)p;
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 free_store_event_payload(void* p) {
thread_pool_store_event_payload_t* payload = (thread_pool_store_event_payload_t*)p;
if (!payload) return;
free(payload->id);
free(payload->pubkey);
free(payload->event_type);
free(payload->content);
free(payload->sig);
free(payload->tags_json);
free(payload->event_json);
free(payload);
}
static void queue_init(thread_pool_queue_t* q, int max_size) {
memset(q, 0, sizeof(*q));
q->max_size = (max_size > 0) ? max_size : 4096;
pthread_mutex_init(&q->mutex, NULL);
pthread_cond_init(&q->cond, NULL);
}
static void queue_destroy(thread_pool_queue_t* q) {
pthread_mutex_lock(&q->mutex);
thread_pool_job_node_t* cur = q->head;
while (cur) {
thread_pool_job_node_t* next = cur->next;
if (cur->job.payload_free && cur->job.payload) {
cur->job.payload_free(cur->job.payload);
}
free(cur);
cur = next;
}
q->head = q->tail = NULL;
q->size = 0;
pthread_mutex_unlock(&q->mutex);
pthread_mutex_destroy(&q->mutex);
pthread_cond_destroy(&q->cond);
}
static int queue_push(thread_pool_queue_t* q, thread_pool_job_node_t* node) {
pthread_mutex_lock(&q->mutex);
if (q->size >= q->max_size) {
pthread_mutex_unlock(&q->mutex);
return 0;
}
node->next = NULL;
if (!q->tail) {
q->head = q->tail = node;
} else {
q->tail->next = node;
q->tail = node;
}
q->size++;
pthread_cond_signal(&q->cond);
pthread_mutex_unlock(&q->mutex);
return 1;
}
static thread_pool_job_node_t* queue_pop(thread_pool_queue_t* q) {
pthread_mutex_lock(&q->mutex);
while (g_pool.running && q->size == 0) {
pthread_cond_wait(&q->cond, &q->mutex);
}
if (!g_pool.running && q->size == 0) {
pthread_mutex_unlock(&q->mutex);
return NULL;
}
thread_pool_job_node_t* node = q->head;
q->head = node->next;
if (!q->head) q->tail = NULL;
q->size--;
pthread_mutex_unlock(&q->mutex);
return node;
}
static void wake_event_loop(void) {
if (g_pool.wake_loop_cb) {
g_pool.wake_loop_cb(g_pool.wake_loop_ctx);
}
}
static sqlite3* open_worker_connection(void) {
if (g_pool.db_path[0] == '\0') {
return NULL;
}
sqlite3* db = NULL;
int rc = sqlite3_open_v2(g_pool.db_path, &db, SQLITE_OPEN_READWRITE, NULL);
if (rc != SQLITE_OK) {
if (db) {
sqlite3_close(db);
}
return NULL;
}
sqlite3_exec(db, "PRAGMA journal_mode=WAL;", NULL, NULL, NULL);
sqlite3_busy_timeout(db, 5000);
return db;
}
static void complete_job_with_result(thread_pool_job_node_t* node,
thread_pool_status_t status,
const char* message,
void* result_data,
size_t result_size) {
thread_pool_result_t result;
memset(&result, 0, sizeof(result));
result.job_id = node->job_id;
result.type = node->job.type;
result.status = status;
result.session = node->job.session;
result.result_data = result_data;
result.result_size = result_size;
result.message = message;
if (node->job.result_cb) {
node->job.result_cb(&result, node->job.result_cb_ctx);
}
wake_event_loop();
if (node->job.payload_free && node->job.payload) {
node->job.payload_free(node->job.payload);
}
free(node);
}
static void execute_count_job(thread_pool_job_node_t* node) {
thread_pool_count_payload_t* payload = (thread_pool_count_payload_t*)node->job.payload;
if (!payload || !payload->sql) {
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
"COUNT payload missing", NULL, 0);
return;
}
int count = 0;
if (db_count_with_sql(payload->sql, (const char**)payload->bind_params,
payload->bind_param_count, &count) != 0) {
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
"COUNT query failed", NULL, 0);
return;
}
thread_pool_count_result_t* out = calloc(1, sizeof(*out));
if (!out) {
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
"COUNT result allocation failed", NULL, 0);
return;
}
out->count = count;
complete_job_with_result(node, THREAD_POOL_STATUS_OK,
"COUNT query completed", out, sizeof(*out));
}
static void execute_req_job(thread_pool_job_node_t* node) {
thread_pool_req_payload_t* payload = (thread_pool_req_payload_t*)node->job.payload;
if (!payload || !payload->sql) {
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
"REQ payload missing", NULL, 0);
return;
}
db_stmt_t* stmt = NULL;
if (db_prepare(payload->sql, &stmt) != DB_OK) {
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
"REQ prepare failed", NULL, 0);
return;
}
for (int i = 0; i < payload->bind_param_count; i++) {
const char* v = (payload->bind_params && payload->bind_params[i]) ? payload->bind_params[i] : "";
(void)db_bind_text_param(stmt, i + 1, v);
}
int cap = (payload->row_limit > 0) ? payload->row_limit : 500;
if (cap < 1) cap = 1;
thread_pool_req_result_t* out = calloc(1, sizeof(*out));
if (!out) {
db_finalize_stmt(stmt);
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
"REQ result allocation failed", NULL, 0);
return;
}
out->event_json_rows = calloc((size_t)cap, sizeof(char*));
if (!out->event_json_rows) {
db_finalize_stmt(stmt);
free(out);
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
"REQ row array allocation failed", NULL, 0);
return;
}
int rc = DB_OK;
while ((rc = db_step_stmt(stmt)) == DB_ROW) {
if (out->row_count >= cap) break;
const char* row = db_column_text_value(stmt, 0);
if (!row) continue;
out->event_json_rows[out->row_count] = strdup(row);
if (!out->event_json_rows[out->row_count]) {
break;
}
out->row_count++;
}
db_finalize_stmt(stmt);
complete_job_with_result(node, THREAD_POOL_STATUS_OK,
"REQ query completed", out, sizeof(*out));
}
static void execute_store_event_job(thread_pool_job_node_t* node) {
thread_pool_store_event_payload_t* payload = (thread_pool_store_event_payload_t*)node->job.payload;
if (!payload || !payload->id || !payload->pubkey || !payload->content || !payload->sig) {
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
"STORE_EVENT payload missing required fields", NULL, 0);
return;
}
thread_pool_store_event_result_t* out = calloc(1, sizeof(*out));
if (!out) {
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
"STORE_EVENT result allocation failed", NULL, 0);
return;
}
int step_rc = DB_ERROR;
int extended_errcode = 0;
if (db_insert_event_with_json(payload->id,
payload->pubkey,
payload->created_at,
payload->kind,
payload->event_type ? payload->event_type : "regular",
payload->content,
payload->sig,
payload->tags_json ? payload->tags_json : "[]",
payload->event_json ? payload->event_json : "{}",
&step_rc,
&extended_errcode) != 0) {
free(out);
complete_job_with_result(node, THREAD_POOL_STATUS_INTERNAL_ERROR,
"STORE_EVENT execution failed", NULL, 0);
return;
}
out->step_rc = step_rc;
out->extended_errcode = extended_errcode;
complete_job_with_result(node, THREAD_POOL_STATUS_OK,
"STORE_EVENT completed", out, sizeof(*out));
}
static void execute_read_job(thread_pool_job_node_t* node) {
switch (node->job.type) {
case THREAD_POOL_JOB_REQ_QUERY:
execute_req_job(node);
break;
case THREAD_POOL_JOB_COUNT_QUERY:
execute_count_job(node);
break;
default:
complete_job_with_result(node, THREAD_POOL_STATUS_NOT_IMPLEMENTED,
"Read job type not implemented", NULL, 0);
break;
}
}
static void execute_write_job(thread_pool_job_node_t* node) {
switch (node->job.type) {
case THREAD_POOL_JOB_STORE_EVENT:
execute_store_event_job(node);
break;
default:
complete_job_with_result(node, THREAD_POOL_STATUS_NOT_IMPLEMENTED,
"Write job type not implemented", NULL, 0);
break;
}
}
static void* reader_worker_main(void* arg) {
int reader_index = (int)(intptr_t)arg;
char thread_name[16];
snprintf(thread_name, sizeof(thread_name), "db-read-%d", reader_index);
pthread_setname_np(pthread_self(), thread_name);
sqlite3* worker_db = open_worker_connection();
if (!worker_db) {
DEBUG_ERROR("Reader worker failed to open SQLite connection");
return NULL;
}
db_set_thread_connection(worker_db);
while (g_pool.running) {
thread_pool_job_node_t* node = queue_pop(&g_pool.read_q);
if (!node) break;
execute_read_job(node);
}
db_clear_thread_connection();
sqlite3_close(worker_db);
return NULL;
}
static void* writer_worker_main(void* arg) {
(void)arg;
pthread_setname_np(pthread_self(), "db-write");
sqlite3* worker_db = open_worker_connection();
if (!worker_db) {
DEBUG_ERROR("Writer worker failed to open SQLite connection");
return NULL;
}
db_set_thread_connection(worker_db);
while (g_pool.running) {
thread_pool_job_node_t* node = queue_pop(&g_pool.write_q);
if (!node) break;
execute_write_job(node);
}
db_clear_thread_connection();
sqlite3_close(worker_db);
return NULL;
}
int thread_pool_init(const thread_pool_config_t* config) {
if (!config) return -1;
pthread_mutex_lock(&g_pool.state_mutex);
if (g_pool.running) {
pthread_mutex_unlock(&g_pool.state_mutex);
return 0;
}
g_pool.reader_count = (config->reader_threads > 0) ? config->reader_threads : 4;
g_pool.readers = calloc((size_t)g_pool.reader_count, sizeof(pthread_t));
if (!g_pool.readers) {
pthread_mutex_unlock(&g_pool.state_mutex);
return -1;
}
queue_init(&g_pool.read_q, config->max_queue_depth);
queue_init(&g_pool.write_q, config->max_queue_depth);
g_pool.next_job_id = 1;
g_pool.wake_loop_cb = config->wake_loop_cb;
g_pool.wake_loop_ctx = config->wake_loop_ctx;
const char* db_path = (config->db_path && config->db_path[0] != '\0') ? config->db_path : db_get_database_path();
if (!db_path || db_path[0] == '\0') {
free(g_pool.readers);
g_pool.readers = NULL;
pthread_mutex_unlock(&g_pool.state_mutex);
return -1;
}
strncpy(g_pool.db_path, db_path, sizeof(g_pool.db_path) - 1);
g_pool.db_path[sizeof(g_pool.db_path) - 1] = '\0';
g_pool.running = 1;
for (int i = 0; i < g_pool.reader_count; i++) {
if (pthread_create(&g_pool.readers[i], NULL, reader_worker_main, (void*)(intptr_t)(i + 1)) != 0) {
g_pool.running = 0;
pthread_cond_broadcast(&g_pool.read_q.cond);
pthread_cond_broadcast(&g_pool.write_q.cond);
pthread_mutex_unlock(&g_pool.state_mutex);
return -1;
}
}
if (pthread_create(&g_pool.writer, NULL, writer_worker_main, NULL) != 0) {
g_pool.running = 0;
pthread_cond_broadcast(&g_pool.read_q.cond);
pthread_cond_broadcast(&g_pool.write_q.cond);
pthread_mutex_unlock(&g_pool.state_mutex);
return -1;
}
pthread_mutex_unlock(&g_pool.state_mutex);
DEBUG_LOG("Thread pool initialized: %d readers + 1 writer", g_pool.reader_count);
return 0;
}
void thread_pool_shutdown(void) {
pthread_mutex_lock(&g_pool.state_mutex);
if (!g_pool.running) {
pthread_mutex_unlock(&g_pool.state_mutex);
return;
}
g_pool.running = 0;
pthread_cond_broadcast(&g_pool.read_q.cond);
pthread_cond_broadcast(&g_pool.write_q.cond);
pthread_mutex_unlock(&g_pool.state_mutex);
for (int i = 0; i < g_pool.reader_count; i++) {
pthread_join(g_pool.readers[i], NULL);
}
pthread_join(g_pool.writer, NULL);
free(g_pool.readers);
g_pool.readers = NULL;
g_pool.reader_count = 0;
queue_destroy(&g_pool.read_q);
queue_destroy(&g_pool.write_q);
DEBUG_LOG("Thread pool shutdown complete");
}
int thread_pool_is_running(void) {
return g_pool.running;
}
static thread_pool_status_t submit_to_queue(thread_pool_queue_t* q, const thread_pool_job_t* job, uint64_t* out_job_id) {
if (!g_pool.running) return THREAD_POOL_STATUS_SHUTTING_DOWN;
if (!job) return THREAD_POOL_STATUS_INTERNAL_ERROR;
thread_pool_job_node_t* node = calloc(1, sizeof(*node));
if (!node) return THREAD_POOL_STATUS_INTERNAL_ERROR;
node->job = *job;
pthread_mutex_lock(&g_pool.state_mutex);
node->job_id = g_pool.next_job_id++;
pthread_mutex_unlock(&g_pool.state_mutex);
if (out_job_id) *out_job_id = node->job_id;
if (!queue_push(q, node)) {
if (job->payload_free && job->payload) {
job->payload_free(job->payload);
}
free(node);
return THREAD_POOL_STATUS_QUEUE_FULL;
}
return THREAD_POOL_STATUS_OK;
}
thread_pool_status_t thread_pool_submit_read(const thread_pool_job_t* job, uint64_t* out_job_id) {
return submit_to_queue(&g_pool.read_q, job, out_job_id);
}
thread_pool_status_t thread_pool_submit_write(const thread_pool_job_t* job, uint64_t* out_job_id) {
return submit_to_queue(&g_pool.write_q, job, out_job_id);
}
int thread_pool_execute_count_sync(const char* sql, const char** bind_params, int bind_param_count, int* out_count) {
if (!sql || !out_count) {
return -1;
}
if (!thread_pool_is_running()) {
return db_count_with_sql(sql, bind_params, bind_param_count, out_count);
}
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(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(payload);
return -1;
}
for (int i = 0; i < bind_param_count; i++) {
const char* v = (bind_params && bind_params[i]) ? bind_params[i] : "";
payload->bind_params[i] = strdup(v);
if (!payload->bind_params[i]) {
free_count_payload(payload);
return -1;
}
}
}
thread_pool_wait_ctx_t wait_ctx;
wait_ctx_init(&wait_ctx);
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;
job.result_cb = wait_ctx_result_cb;
job.result_cb_ctx = &wait_ctx;
thread_pool_status_t submit_rc = thread_pool_submit_read(&job, NULL);
if (submit_rc != THREAD_POOL_STATUS_OK) {
free_count_payload(payload);
wait_ctx_destroy(&wait_ctx);
return -1;
}
wait_ctx_wait(&wait_ctx);
int rc = -1;
if (wait_ctx.status == THREAD_POOL_STATUS_OK && wait_ctx.result_data) {
thread_pool_count_result_t* result = (thread_pool_count_result_t*)wait_ctx.result_data;
*out_count = result->count;
free(result);
rc = 0;
}
wait_ctx_destroy(&wait_ctx);
return rc;
}
int thread_pool_execute_req_sync(const char* sql, const char** bind_params, int bind_param_count,
int row_limit, thread_pool_req_result_t** out_result) {
if (!sql || !out_result) {
return -1;
}
if (!thread_pool_is_running()) {
db_stmt_t* stmt = NULL;
if (db_prepare(sql, &stmt) != DB_OK) {
return -1;
}
for (int i = 0; i < bind_param_count; i++) {
const char* v = (bind_params && bind_params[i]) ? bind_params[i] : "";
(void)db_bind_text_param(stmt, i + 1, v);
}
int cap = (row_limit > 0) ? row_limit : 500;
if (cap < 1) cap = 1;
thread_pool_req_result_t* result = calloc(1, sizeof(*result));
if (!result) {
db_finalize_stmt(stmt);
return -1;
}
result->event_json_rows = calloc((size_t)cap, sizeof(char*));
if (!result->event_json_rows) {
free(result);
db_finalize_stmt(stmt);
return -1;
}
int step_rc = DB_OK;
while ((step_rc = db_step_stmt(stmt)) == DB_ROW) {
if (result->row_count >= cap) break;
const char* row = db_column_text_value(stmt, 0);
if (!row) continue;
result->event_json_rows[result->row_count] = strdup(row);
if (!result->event_json_rows[result->row_count]) {
break;
}
result->row_count++;
}
db_finalize_stmt(stmt);
*out_result = result;
return 0;
}
thread_pool_req_payload_t* payload = calloc(1, sizeof(*payload));
if (!payload) return -1;
payload->sql = strdup(sql);
payload->bind_param_count = bind_param_count;
payload->row_limit = row_limit;
if (!payload->sql) {
free_req_payload(payload);
return -1;
}
if (bind_param_count > 0) {
payload->bind_params = calloc((size_t)bind_param_count, sizeof(char*));
if (!payload->bind_params) {
free_req_payload(payload);
return -1;
}
for (int i = 0; i < bind_param_count; i++) {
const char* v = (bind_params && bind_params[i]) ? bind_params[i] : "";
payload->bind_params[i] = strdup(v);
if (!payload->bind_params[i]) {
free_req_payload(payload);
return -1;
}
}
}
thread_pool_wait_ctx_t wait_ctx;
wait_ctx_init(&wait_ctx);
thread_pool_job_t job;
memset(&job, 0, sizeof(job));
job.type = THREAD_POOL_JOB_REQ_QUERY;
job.payload = payload;
job.payload_free = free_req_payload;
job.result_cb = wait_ctx_result_cb;
job.result_cb_ctx = &wait_ctx;
thread_pool_status_t submit_rc = thread_pool_submit_read(&job, NULL);
if (submit_rc != THREAD_POOL_STATUS_OK) {
free_req_payload(payload);
wait_ctx_destroy(&wait_ctx);
return -1;
}
wait_ctx_wait(&wait_ctx);
int rc = -1;
if (wait_ctx.status == THREAD_POOL_STATUS_OK && wait_ctx.result_data) {
*out_result = (thread_pool_req_result_t*)wait_ctx.result_data;
rc = 0;
}
wait_ctx_destroy(&wait_ctx);
return rc;
}
void thread_pool_free_req_result(thread_pool_req_result_t* result) {
if (!result) return;
if (result->event_json_rows) {
for (int i = 0; i < result->row_count; i++) {
free(result->event_json_rows[i]);
}
free(result->event_json_rows);
}
free(result);
}
int thread_pool_execute_store_event_sync(const thread_pool_store_event_payload_t* payload,
thread_pool_store_event_result_t* out_result) {
if (!payload || !out_result || !payload->id || !payload->pubkey || !payload->content || !payload->sig) {
return -1;
}
if (!thread_pool_is_running()) {
int step_rc = DB_ERROR;
int extended_errcode = 0;
if (db_insert_event_with_json(payload->id,
payload->pubkey,
payload->created_at,
payload->kind,
payload->event_type ? payload->event_type : "regular",
payload->content,
payload->sig,
payload->tags_json ? payload->tags_json : "[]",
payload->event_json ? payload->event_json : "{}",
&step_rc,
&extended_errcode) != 0) {
return -1;
}
out_result->step_rc = step_rc;
out_result->extended_errcode = extended_errcode;
return 0;
}
thread_pool_store_event_payload_t* payload_copy = calloc(1, sizeof(*payload_copy));
if (!payload_copy) return -1;
payload_copy->id = payload->id ? strdup(payload->id) : NULL;
payload_copy->pubkey = payload->pubkey ? strdup(payload->pubkey) : NULL;
payload_copy->created_at = payload->created_at;
payload_copy->kind = payload->kind;
payload_copy->event_type = payload->event_type ? strdup(payload->event_type) : strdup("regular");
payload_copy->content = payload->content ? strdup(payload->content) : NULL;
payload_copy->sig = payload->sig ? strdup(payload->sig) : NULL;
payload_copy->tags_json = payload->tags_json ? strdup(payload->tags_json) : strdup("[]");
payload_copy->event_json = payload->event_json ? strdup(payload->event_json) : strdup("{}");
if (!payload_copy->id || !payload_copy->pubkey || !payload_copy->event_type ||
!payload_copy->content || !payload_copy->sig || !payload_copy->tags_json || !payload_copy->event_json) {
free_store_event_payload(payload_copy);
return -1;
}
thread_pool_wait_ctx_t wait_ctx;
wait_ctx_init(&wait_ctx);
thread_pool_job_t job;
memset(&job, 0, sizeof(job));
job.type = THREAD_POOL_JOB_STORE_EVENT;
job.payload = payload_copy;
job.payload_free = free_store_event_payload;
job.result_cb = wait_ctx_result_cb;
job.result_cb_ctx = &wait_ctx;
thread_pool_status_t submit_rc = thread_pool_submit_write(&job, NULL);
if (submit_rc != THREAD_POOL_STATUS_OK) {
free_store_event_payload(payload_copy);
wait_ctx_destroy(&wait_ctx);
return -1;
}
wait_ctx_wait(&wait_ctx);
int rc = -1;
if (wait_ctx.status == THREAD_POOL_STATUS_OK && wait_ctx.result_data) {
thread_pool_store_event_result_t* result = (thread_pool_store_event_result_t*)wait_ctx.result_data;
out_result->step_rc = result->step_rc;
out_result->extended_errcode = result->extended_errcode;
free(result);
rc = 0;
}
wait_ctx_destroy(&wait_ctx);
return rc;
}
__attribute__((constructor))
static void thread_pool_state_init_once(void) {
pthread_mutex_init(&g_pool.state_mutex, NULL);
}
+116
View File
@@ -0,0 +1,116 @@
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
THREAD_POOL_JOB_REQ_QUERY = 0,
THREAD_POOL_JOB_COUNT_QUERY,
THREAD_POOL_JOB_STORE_EVENT,
THREAD_POOL_JOB_DELETE_EVENT,
THREAD_POOL_JOB_CUSTOM
} thread_pool_job_type_t;
typedef enum {
THREAD_POOL_STATUS_OK = 0,
THREAD_POOL_STATUS_NOT_IMPLEMENTED,
THREAD_POOL_STATUS_QUEUE_FULL,
THREAD_POOL_STATUS_SHUTTING_DOWN,
THREAD_POOL_STATUS_INTERNAL_ERROR
} thread_pool_status_t;
typedef struct {
uint64_t job_id;
thread_pool_job_type_t type;
thread_pool_status_t status;
void* session;
void* result_data;
size_t result_size;
const char* message;
} thread_pool_result_t;
typedef void (*thread_pool_result_cb)(const thread_pool_result_t* result, void* user_ctx);
typedef void (*thread_pool_payload_free_cb)(void* payload);
typedef void (*thread_pool_wake_loop_cb)(void* wake_ctx);
typedef struct {
thread_pool_job_type_t type;
void* session;
void* payload;
size_t payload_size;
thread_pool_payload_free_cb payload_free;
thread_pool_result_cb result_cb;
void* result_cb_ctx;
} thread_pool_job_t;
typedef struct {
int reader_threads;
int max_queue_depth;
const char* db_path;
thread_pool_wake_loop_cb wake_loop_cb;
void* wake_loop_ctx;
} thread_pool_config_t;
typedef struct {
char* sql;
char** bind_params;
int bind_param_count;
} thread_pool_count_payload_t;
typedef struct {
char* sql;
char** bind_params;
int bind_param_count;
int row_limit;
} thread_pool_req_payload_t;
typedef struct {
char* id;
char* pubkey;
long long created_at;
int kind;
char* event_type;
char* content;
char* sig;
char* tags_json;
char* event_json;
} thread_pool_store_event_payload_t;
typedef struct {
int count;
} thread_pool_count_result_t;
typedef struct {
char** event_json_rows;
int row_count;
} thread_pool_req_result_t;
typedef struct {
int step_rc;
int extended_errcode;
} thread_pool_store_event_result_t;
int thread_pool_init(const thread_pool_config_t* config);
void thread_pool_shutdown(void);
int thread_pool_is_running(void);
thread_pool_status_t thread_pool_submit_read(const thread_pool_job_t* job, uint64_t* out_job_id);
thread_pool_status_t thread_pool_submit_write(const thread_pool_job_t* job, uint64_t* out_job_id);
int thread_pool_execute_count_sync(const char* sql, const char** bind_params, int bind_param_count, int* out_count);
int thread_pool_execute_req_sync(const char* sql, const char** bind_params, int bind_param_count,
int row_limit, thread_pool_req_result_t** out_result);
void thread_pool_free_req_result(thread_pool_req_result_t* result);
int thread_pool_execute_store_event_sync(const thread_pool_store_event_payload_t* payload,
thread_pool_store_event_result_t* out_result);
#ifdef __cplusplus
}
#endif
#endif // THREAD_POOL_H
+936 -223
View File
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
2026-04-01 09:57:22 - ==========================================
2026-04-01 09:57:22 - C-Relay Comprehensive Test Suite Runner
2026-04-01 09:57:22 - ==========================================
2026-04-01 09:57:22 - Relay URL: ws://127.0.0.1:8888
2026-04-01 09:57:22 - Log file: test_results_20260401_095722.log
2026-04-01 09:57:22 - Report file: test_report_20260401_095722.html
2026-04-01 09:57:22 -
2026-04-01 09:57:22 - Checking relay status at ws://127.0.0.1:8888...
2026-04-01 09:57:22 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
2026-04-01 09:57:22 -
2026-04-01 09:57:22 - Starting comprehensive test execution...
2026-04-01 09:57:22 -
2026-04-01 09:57:22 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
2026-04-01 09:57:22 - ==========================================
2026-04-01 09:57:22 - Running Test Suite: SQL Injection Tests
2026-04-01 09:57:22 - Description: Comprehensive SQL injection vulnerability testing
2026-04-01 09:57:22 - ==========================================
2026-04-01 09:57:22 - \033[0;31mERROR: Test script sql_injection_tests.sh not found\033[0m
+18
View File
@@ -0,0 +1,18 @@
2026-04-01 11:01:47 - ==========================================
2026-04-01 11:01:47 - C-Relay Comprehensive Test Suite Runner
2026-04-01 11:01:47 - ==========================================
2026-04-01 11:01:47 - Relay URL: ws://127.0.0.1:8888
2026-04-01 11:01:47 - Log file: test_results_20260401_110147.log
2026-04-01 11:01:47 - Report file: test_report_20260401_110147.html
2026-04-01 11:01:47 -
2026-04-01 11:01:47 - Checking relay status at ws://127.0.0.1:8888...
2026-04-01 11:01:47 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
2026-04-01 11:01:47 -
2026-04-01 11:01:47 - Starting comprehensive test execution...
2026-04-01 11:01:47 -
2026-04-01 11:01:47 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
2026-04-01 11:01:47 - ==========================================
2026-04-01 11:01:47 - Running Test Suite: SQL Injection Tests
2026-04-01 11:01:47 - Description: Comprehensive SQL injection vulnerability testing
2026-04-01 11:01:47 - ==========================================
2026-04-01 11:01:47 - \033[0;31mERROR: Test script sql_injection_tests.sh not found\033[0m
+15 -6
View File
@@ -54,6 +54,8 @@ BASELINE_KIND1=0
BASELINE_KIND0=0
BASELINE_KIND30001=0
BASELINE_AUTHOR=0
BASELINE_AUTHOR_KIND0=0
BASELINE_AUTHOR_KIND30001=0
BASELINE_TYPE_REGULAR=0
BASELINE_TEST_NIP45=0
BASELINE_KINDS_01=0
@@ -253,9 +255,11 @@ run_count_test() {
publish_event "$regular1" "regular" "Regular event #1"
# Now that we have the pubkey, get the author baseline
# Now that we have the pubkey, get author baselines
local test_pubkey=$(echo "$regular1" | jq -r '.pubkey' 2>/dev/null)
BASELINE_AUTHOR=$(get_baseline_count "{\"authors\":[\"$test_pubkey\"]}" "events by test author")
BASELINE_AUTHOR_KIND0=$(get_baseline_count "{\"authors\":[\"$test_pubkey\"],\"kinds\":[0]}" "kind 0 events by test author")
BASELINE_AUTHOR_KIND30001=$(get_baseline_count "{\"authors\":[\"$test_pubkey\"],\"kinds\":[30001]}" "kind 30001 events by test author")
publish_event "$regular2" "regular" "Regular event #2"
publish_event "$regular3" "regular" "Regular event #3"
@@ -314,10 +318,13 @@ run_count_test() {
fi
# Test 3: Count events by author (pubkey)
# BASELINE_AUTHOR includes the first regular event, we add 2 more regular
# Replaceable and addressable replace existing events from this author
# BASELINE_AUTHOR is measured after regular1 is already published.
# Net additions after baseline:
# +2 regular (regular2, regular3)
# +(1 - BASELINE_AUTHOR_KIND0) from replaceable upsert behavior
# +(1 - BASELINE_AUTHOR_KIND30001) from addressable replacement behavior
local test_pubkey=$(echo "$regular1" | jq -r '.pubkey' 2>/dev/null)
local expected_author=$((2 + BASELINE_AUTHOR))
local expected_author=$((BASELINE_AUTHOR + 2 + (1 - BASELINE_AUTHOR_KIND0) + (1 - BASELINE_AUTHOR_KIND30001)))
if ! test_count "count_author" "{\"authors\":[\"$test_pubkey\"]}" "Count events by specific author" "$expected_author"; then
((test_failures++))
fi
@@ -340,8 +347,10 @@ run_count_test() {
fi
# Test 6: Count multiple kinds
# BASELINE_KINDS_01 + 3 regular events = total for kinds 0+1
local expected_kinds_01=$((3 + BASELINE_KINDS_01))
# Net additions for kinds 0+1 after baseline:
# +3 kind-1 regular events
# +(1 - BASELINE_AUTHOR_KIND0) for kind-0 replaceable upsert behavior
local expected_kinds_01=$((BASELINE_KINDS_01 + 3 + (1 - BASELINE_AUTHOR_KIND0)))
if ! test_count "count_multi_kinds" '{"kinds":[0,1]}' "Count multiple kinds (0,1)" "$expected_kinds_01"; then
((test_failures++))
fi
+16 -12
View File
@@ -8,7 +8,7 @@ set -e
# Configuration
RELAY_HOST="127.0.0.1"
RELAY_PORT="8888"
TEST_TIMEOUT=5
TEST_TIMEOUT=10
# Colors for output
RED='\033[0;31m'
@@ -34,7 +34,7 @@ test_websocket_message() {
# Send message via websocat and capture response
local response
response=$(echo "$message" | timeout $TEST_TIMEOUT websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null || echo 'CONNECTION_FAILED')
response=$(echo "$message" | timeout $TEST_TIMEOUT websocat -B 1048576 ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1 || echo 'CONNECTION_FAILED')
if [[ "$response" == "CONNECTION_FAILED" ]]; then
echo -e "${RED}FAILED${NC} - Could not connect to relay"
@@ -42,8 +42,8 @@ test_websocket_message() {
return 1
fi
if [[ "$response" == "TIMEOUT" ]]; then
echo -e "${RED}FAILED${NC} - Connection timeout"
if [[ -z "$response" ]]; then
echo -e "${RED}FAILED${NC} - Empty response"
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
fi
@@ -119,8 +119,9 @@ test_websocket_message "Invalid author type" '["REQ","sub1",{"authors":[123]}]'
# Test 6: Invalid author hex
test_websocket_message "Invalid author hex" '["REQ","sub1",{"authors":["invalid-hex"]}]' "error: invalid author hex string"
# Test 7: Too many authors
test_websocket_message "Too many authors" '["REQ","sub1",{"authors":["a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a"]}]' "error: too many authors"
# Test 7: Too many authors (exceed MAX_AUTHORS_PER_FILTER with compact payload)
too_many_authors=$(yes '"a"' | head -n 1001 | paste -sd, -)
test_websocket_message "Too many authors" "[\"REQ\",\"sub1\",{\"authors\":[${too_many_authors}]}]" "error: too many authors"
echo
echo "=== Testing IDs Validation ==="
@@ -131,8 +132,9 @@ test_websocket_message "Invalid ID type" '["REQ","sub1",{"ids":[123]}]' "error:
# Test 9: Invalid ID hex
test_websocket_message "Invalid ID hex" '["REQ","sub1",{"ids":["invalid-hex"]}]' "error: invalid id hex string"
# Test 10: Too many IDs
test_websocket_message "Too many IDs" '["REQ","sub1",{"ids":["a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a","a"]}]' "error: too many ids"
# Test 10: Too many IDs (exceed MAX_IDS_PER_FILTER with compact payload)
too_many_ids=$(yes '"a"' | head -n 1001 | paste -sd, -)
test_websocket_message "Too many IDs" "[\"REQ\",\"sub1\",{\"ids\":[${too_many_ids}]}]" "error: too many ids"
echo
echo "=== Testing Kinds Validation ==="
@@ -146,8 +148,9 @@ test_websocket_message "Negative kind" '["REQ","sub1",{"kinds":[-1]}]' "error: i
# Test 13: Too large kind
test_websocket_message "Too large kind" '["REQ","sub1",{"kinds":[70000]}]' "error: invalid kind value"
# Test 14: Too many kinds
test_websocket_message "Too many kinds" '["REQ","sub1",{"kinds":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52]}]' "error: too many kinds"
# Test 14: Too many kinds (exceed MAX_KINDS_PER_FILTER=500)
too_many_kinds=$(seq -s, 1 501)
test_websocket_message "Too many kinds" "[\"REQ\",\"sub1\",{\"kinds\":[${too_many_kinds}]}]" "error: too many kinds"
echo
echo "=== Testing Timestamp Validation ==="
@@ -194,8 +197,9 @@ echo "=== Testing Tag Filter Validation ==="
# Test 25: Invalid tag filter type
test_websocket_message "Invalid tag filter type" '["REQ","sub1",{"#e":"not-an-array"}]' "error: #e must be an array"
# Test 26: Too many tag values
test_websocket_message "Too many tag values" '["REQ","sub1",{"#e":['$(printf '"a%.0s",' {1..101})'"a"]}]' "error: too many #e values"
# Test 26: Too many tag values (exceed MAX_TAG_VALUES_PER_FILTER=1000)
too_many_tag_values=$(yes '"a"' | head -n 1001 | paste -sd, -)
test_websocket_message "Too many tag values" "[\"REQ\",\"sub1\",{\"#e\":[${too_many_tag_values}]}]" "error: too many #e values"
# Test 27: Tag value too long
test_websocket_message "Tag value too long" '["REQ","sub1",{"#e":["'$(printf 'a%.0s' {1..1025})'"]}]' "error: #e value too long"
+29 -30
View File
@@ -1,11 +1,13 @@
=== NIP-42 Authentication Test Started ===
2026-02-24 09:45:30 - Starting NIP-42 authentication tests
2026-04-01 10:00:44 - Starting NIP-42 authentication tests
[INFO] === Starting NIP-42 Authentication Tests ===
[INFO] Checking dependencies...
[WARNING] wscat not found. Some manual WebSocket tests will be skipped
[WARNING] Install with: npm install -g wscat
[SUCCESS] Dependencies check complete
[INFO] Test 1: Checking NIP-42 support in relay info
[SUCCESS] NIP-42 is advertised in supported NIPs
2026-02-24 09:45:30 - Supported NIPs: 1,2,4,9,11,12,13,15,16,20,22,33,40,42,50,70
2026-04-01 10:00:44 - Supported NIPs: 1,2,4,9,11,12,13,15,16,20,22,33,40,42,50,70
[INFO] Test 2: Testing AUTH challenge generation
[WARNING] Could not extract admin private key from relay.log - using manual test approach
[INFO] Manual test: Connect to relay and send an event without auth to trigger challenge
@@ -13,64 +15,61 @@
[INFO] Generated test keypair: test_pubkey
[INFO] Attempting to publish event without authentication...
[INFO] Publishing test event to relay...
2026-02-24 09:45:31 - Event publish result: connecting to ws://localhost:8888... ok.
{"kind":1,"id":"74a0b723797569b2483d47457d2f6e2378aed6ccd4cca0f553038e0431ade0e8","pubkey":"7a2f213c220c46a194c5730e5bb2fe9b27304f0c27226f380086d93ff10bf7da","created_at":1771940731,"tags":[],"content":"NIP-42 test event - should require auth","sig":"319466df2167cd7f33c83d41b09bdd06f23c961380fd205a0a5dc5a923f11e45b6d158d79e251239bef364430c5d45e6a9e43fe93fd629864a88de00e6766a44"}
2026-04-01 10:00:45 - Event publish result: connecting to localhost:8888... ok.
{"kind":1,"id":"28174ed72f9ef5484a6596db6c6be181d2ed10e46d8c22a9f7a267becbc9a47a","pubkey":"b5f9adc3b362c0e81569a46648a3996708d6b47754d36c617683f18e1f1daf6b","created_at":1775052045,"tags":[],"content":"NIP-42 test event - should require auth","sig":"0041347b233a21d6542a263634f52f8ca7e3b5ecf07bc37c86f390f1a7ab0bc2051306b14aa70edff020bd7311b259d03c2c35a035b285f9e19051a924546666"}
publishing to ws://localhost:8888... success.
[SUCCESS] Relay requested authentication as expected
[INFO] Test 4: Testing WebSocket AUTH message handling
[INFO] Testing WebSocket connection and AUTH message...
[INFO] Sending test message via WebSocket...
2026-02-24 09:45:31 - WebSocket response:
[INFO] No AUTH challenge in WebSocket response
[WARNING] Skipping WebSocket tests - wscat not available
[INFO] Test 5: Testing NIP-42 configuration options
[INFO] Retrieving current relay configuration...
[WARNING] Could not retrieve configuration events
[INFO] Test 6: Testing NIP-42 performance and stability
[INFO] Testing multiple authentication attempts...
2026-02-24 09:45:33 - Attempt 1: .264126491s - connecting to ws://localhost:8888... ok.
{"kind":1,"id":"9cb8f626ced97e8fc406bd2d2358074fe33542d983d11a7f039a26919ded6039","pubkey":"7c098dbaeeaca6fb27798625835dfc5c13fb31096eed76c6d77269a2a08cd22b","created_at":1771940733,"tags":[],"content":"Performance test event 1","sig":"87025c881004a7936589785bbba6171542135348e4f798c490c033eb462c9a0322882cfe1a2b742e3644b6c69ee4fee76feb3cffecd6d64c096fa8038bcd84c4"}
2026-04-01 10:00:46 - Attempt 1: .195528002s - connecting to localhost:8888... ok.
{"kind":1,"id":"72526c57e50d721bfcdcca23856f2c5d3021c3100f0121538d9d829042e9b8b5","pubkey":"29af457d7eea750c11c836dfbeed3076fa0bf1f1b67a404ee4c25733d9b54238","created_at":1775052046,"tags":[],"content":"Performance test event 1","sig":"1a10db86a39aaf47bda6d0cca5f888691bae32bfd85df8c178dddbf24861503a491bfe4da8d66dfe3e5e0afec7bf8644e7eb69c9f051c601673ae13284f9ab31"}
publishing to ws://localhost:8888... success.
2026-02-24 09:45:33 - Attempt 2: .262980094s - connecting to ws://localhost:8888... ok.
{"kind":1,"id":"bb1b3da4cccbcdc6be73646e29ecf060b68d8cb9340f8b23da0f167ffac0831d","pubkey":"7c098dbaeeaca6fb27798625835dfc5c13fb31096eed76c6d77269a2a08cd22b","created_at":1771940733,"tags":[],"content":"Performance test event 2","sig":"1349f7af97edb9f507081782cfb2d055eb810ec96056b97692920060091ccf39b8d1ba8590d25e1b3c92f4b785f6362ac0d734373d29d4159e5778f8982b086f"}
2026-04-01 10:00:46 - Attempt 2: .185198180s - connecting to localhost:8888... ok.
{"kind":1,"id":"84eeb907205806bb88fc353bb83f1bf0149f3c93c18695306d8480d36752878b","pubkey":"29af457d7eea750c11c836dfbeed3076fa0bf1f1b67a404ee4c25733d9b54238","created_at":1775052046,"tags":[],"content":"Performance test event 2","sig":"e568dbca3113966f61e69eff05576d296fac42b5ff2ab7341778acc02cf6e9440b59bf651a8a2995d89180a62ec20ffc1143e211cf2960c5abaa358a82992ac6"}
publishing to ws://localhost:8888... success.
2026-02-24 09:45:34 - Attempt 3: .296311100s - connecting to ws://localhost:8888... ok.
{"kind":1,"id":"3b171de7c00e918ad0bcbedea4c07c69f59a9d20c31b0de957488c919fa5c116","pubkey":"7c098dbaeeaca6fb27798625835dfc5c13fb31096eed76c6d77269a2a08cd22b","created_at":1771940734,"tags":[],"content":"Performance test event 3","sig":"a1699f12064d4da7ba15519eb7666cd2801f333ee837df70ce1298ac97477e900d095dce8fed0bade8f3e3e2e2b68e22800d10ba213a1de6cd1537112d6a6c2a"}
2026-04-01 10:00:47 - Attempt 3: .197159540s - connecting to localhost:8888... ok.
{"kind":1,"id":"0874716ba0b2a845d379e703226513f90db6228ce958369f4dce5aff3df64c5a","pubkey":"29af457d7eea750c11c836dfbeed3076fa0bf1f1b67a404ee4c25733d9b54238","created_at":1775052046,"tags":[],"content":"Performance test event 3","sig":"e74e885eb464998bddf655c24c95a18d7299908c63fb82a47f8d14af65992f90e73794a9f289b550bc1dec1c0298112d2b25941be7e26e3f17dd66b4c5dc742e"}
publishing to ws://localhost:8888... success.
2026-02-24 09:45:35 - Attempt 4: .300140904s - connecting to ws://localhost:8888... ok.
{"kind":1,"id":"f3587b9955204284ec65f2b171d1fa3f330e0bbfc2d95fe7ad0550a63a6aabe6","pubkey":"7c098dbaeeaca6fb27798625835dfc5c13fb31096eed76c6d77269a2a08cd22b","created_at":1771940734,"tags":[],"content":"Performance test event 4","sig":"fb78cd3bce49097eea1c67c0a1ee92bf4cdf5bf6396ce20519481d0e4d09b03dca05c70da2ddf5ba51d23aa3253b21ec3b57bc41d000f274bb1123a4c3d64c45"}
2026-04-01 10:00:47 - Attempt 4: .202520319s - connecting to localhost:8888... ok.
{"kind":1,"id":"5614d1b795cd028e096720418bb4dbc64870d442c6295b9bb2931564a332099a","pubkey":"29af457d7eea750c11c836dfbeed3076fa0bf1f1b67a404ee4c25733d9b54238","created_at":1775052047,"tags":[],"content":"Performance test event 4","sig":"1c0645a791ab9df573b152b782884bcb5ec2a18b8f4e1c18d7dc2175977b7d1d572674169ea1d2b3e3fa8d228de90f6cc281d56a3ef471ba4f9510e920ed586e"}
publishing to ws://localhost:8888... success.
2026-02-24 09:45:35 - Attempt 5: .373738147s - connecting to ws://localhost:8888... ok.
{"kind":1,"id":"e1fdf2d0579f0e2b446bdd9dbe5f5e53913b7d328d6f527ac7142636979c1ff2","pubkey":"7c098dbaeeaca6fb27798625835dfc5c13fb31096eed76c6d77269a2a08cd22b","created_at":1771940735,"tags":[],"content":"Performance test event 5","sig":"a11a11991434511737092d8cf2806a700a207c81c08e341f8d98c8b3369845df35adb9247ff094378e6712d68e5b2657d628e809b17bc9f838f6f5d9c21ce96c"}
2026-04-01 10:00:47 - Attempt 5: .222530325s - connecting to localhost:8888... ok.
{"kind":1,"id":"9139f1eb39c31c43cb9f02e60a0f75a0f99f6c7d9693a69a225c8b3e530f774a","pubkey":"29af457d7eea750c11c836dfbeed3076fa0bf1f1b67a404ee4c25733d9b54238","created_at":1775052047,"tags":[],"content":"Performance test event 5","sig":"bd245a7629c33619c0ed426f631e0c2413adb4488c281bea09886ceb6407b40e247d42f82e374e2f53ee5281f0d8361661a4c613c6414c59d09e5bb23eeeb02d"}
publishing to ws://localhost:8888... success.
[SUCCESS] Performance test completed: 5/5 successful responses
[INFO] Test 7: Testing kind-specific NIP-42 authentication requirements
[INFO] Generated test keypair for kind-specific tests: test_pubkey
[INFO] Testing kind 1 event (regular note) - should work without authentication...
2026-02-24 09:45:36 - Kind 1 event result: connecting to ws://localhost:8888... ok.
{"kind":1,"id":"85430aa1bb17e0aa7f5f05568448e35570a0d8446a79600f033e2683b17f8902","pubkey":"1cee941c80f1fa0037047151cf577503a1243cbef748f572b9fd6062a36807db","created_at":1771940736,"tags":[],"content":"Regular note - should not require auth","sig":"118a2dec1256aca39b22b9027c8f56e0b8f60bae1d95f38408ac9d9a6eb6590c48e4a37bf561e29f993b435ceecc1ff2218fcc9dec3e2dcf305cd456ac6a0ee4"}
2026-04-01 10:00:48 - Kind 1 event result: connecting to localhost:8888... ok.
{"kind":1,"id":"def386c66576a91f144a7321e9520a3ed46f9fecc56d9e79d32a162f249cace9","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052048,"tags":[],"content":"Regular note - should not require auth","sig":"7ee8fbf45e96d040df1a688e9bf028faaa49b81b26eeae6452b3a32cd9fd941c27b41734f14268d3deef14dbb19e514d29789211fffb27bfa9b73d20cddcc83e"}
publishing to ws://localhost:8888... success.
[SUCCESS] Kind 1 event accepted without authentication (correct behavior)
[INFO] Testing kind 4 event (direct message) - should require authentication...
2026-02-24 09:45:47 - Kind 4 event result: connecting to ws://localhost:8888... ok.
{"kind":4,"id":"32f20767dd83d6f1ee5e70f72c33c7caa8a5a03acbfbb4de5899f6b27b850be4","pubkey":"1cee941c80f1fa0037047151cf577503a1243cbef748f572b9fd6062a36807db","created_at":1771940737,"tags":[["p,test_pubkey"]],"content":"This is a direct message - should require auth","sig":"cee96adf8c266120d98579734134cca652eeefa5f090d119b794a964bae4d2568e3aa39d648e5a897bb7f8d65be19f338291af3ffe87accdbf502f5eaae453ab"}
2026-04-01 10:00:58 - Kind 4 event result: connecting to localhost:8888... ok.
{"kind":4,"id":"a2f2d176a835d17a0c9229c0ce3b1ba61a5eb81308e4a3b3446634f468721238","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052048,"tags":[["p,test_pubkey"]],"content":"This is a direct message - should require auth","sig":"441f9caba6ebcec6b6c55d13ffd9571f3fe441a54ddd0692f2868429a5087378d791c97289b8b990560dcf40f5c66e86ba6b41f1de78220a5f389c1f119bb887"}
publishing to ws://localhost:8888...
[SUCCESS] Kind 4 event requested authentication (correct behavior for DMs)
[INFO] Testing kind 14 event (chat message) - should require authentication...
2026-02-24 09:45:57 - Kind 14 event result: connecting to ws://localhost:8888... ok.
{"kind":14,"id":"0a6d37f32fc10dae793b61d8f3afb9d28bdf12d59cf25d73eb90461d8efaa117","pubkey":"1cee941c80f1fa0037047151cf577503a1243cbef748f572b9fd6062a36807db","created_at":1771940747,"tags":[["p,test_pubkey"]],"content":"Chat message - should require auth","sig":"5337ded22499a8c361445d6c660f297e636cc5adf2735a85bb7d629e5771e0c47176b2afde02f4af9645198d441fb0aedd78931d0200d8b5ed8f721e6460d5c9"}
2026-04-01 10:01:09 - Kind 14 event result: connecting to localhost:8888... ok.
{"kind":14,"id":"e234a6faa63f95cd24f8596fbfac573730533bb0abcde47b4d341ff0acace657","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052059,"tags":[["p,test_pubkey"]],"content":"Chat message - should require auth","sig":"fd6ba0ddaa7e2379aa49c5cda6599d0a38264799b228a8e907b948bef645080603f7f910c587f6cfb1ef1bac946c47dc681fdab6abd248376cd552bcd535ef9e"}
publishing to ws://localhost:8888...
[SUCCESS] Kind 14 event requested authentication (correct behavior for DMs)
[INFO] Testing other event kinds - should work without authentication...
2026-02-24 09:45:58 - Kind 0 event result: connecting to ws://localhost:8888... ok.
{"kind":0,"id":"5836e2da48eb458c8faca084afc3827ee6e481574d0295dc7bfdc50d9495891a","pubkey":"1cee941c80f1fa0037047151cf577503a1243cbef748f572b9fd6062a36807db","created_at":1771940757,"tags":[],"content":"Test event kind 0 - should not require auth","sig":"e454d5990a157b6211b655a9219ad494c58d49a59d61c4e1fb0aa91b96e5ff2368efc57e2acd0a24c7198b1181c37d0ab535f608289ec83afdc0b68e076def7c"}
2026-04-01 10:01:09 - Kind 0 event result: connecting to localhost:8888... ok.
{"kind":0,"id":"d87b69e8a2c8f6d98229b55cde3f0fe1b70c6cafd080e4462209849338eafc76","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052069,"tags":[],"content":"Test event kind 0 - should not require auth","sig":"e3222bd4882c881d039e6790a6a8d9f8e20a71d0f9d5089e98604acaa4feff7c1b1b2d9b4171d640da3bba955408a9391a0fd52334cc4a4165fa932e5163f9dd"}
publishing to ws://localhost:8888... success.
[SUCCESS] Kind 0 event accepted without authentication (correct)
2026-02-24 09:45:58 - Kind 3 event result: connecting to ws://localhost:8888... ok.
{"kind":3,"id":"d11a038044f9ea859338c6ab879c4628aece258f2cad0ed7ca88808b35ec899c","pubkey":"1cee941c80f1fa0037047151cf577503a1243cbef748f572b9fd6062a36807db","created_at":1771940758,"tags":[],"content":"Test event kind 3 - should not require auth","sig":"9fbf410cd609f9f7a6bf3118ecfe554776bb647d5ed988c0d4df06f83c1283c8baeaa0b62da900934cc8f617c73572fbce9f7232c77bb87942e4471228b06c73"}
2026-04-01 10:01:09 - Kind 3 event result: connecting to localhost:8888... ok.
{"kind":3,"id":"ac0d45d7db2818bb5e674d4760711349928cf3d5dc8f1fc3717d9e7274e26fb0","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052069,"tags":[],"content":"Test event kind 3 - should not require auth","sig":"b65b317c81bc733ee2ff5921430b34d722e8d177147c5d1cddf14036573223582a3b7232810fc13169eb7a61ab3b99cd37ce13991673fce142d1ea10c702305e"}
publishing to ws://localhost:8888... success.
[SUCCESS] Kind 3 event accepted without authentication (correct)
2026-02-24 09:45:59 - Kind 7 event result: connecting to ws://localhost:8888... ok.
{"kind":7,"id":"b819482f287f6370fac7804c2cdcdac8340eafd86799b24af0a11f74c6190fed","pubkey":"1cee941c80f1fa0037047151cf577503a1243cbef748f572b9fd6062a36807db","created_at":1771940758,"tags":[],"content":"Test event kind 7 - should not require auth","sig":"f197c7c072af111dd92220c489472473f5b4fb30e908501e4d2d0037f1a0064960550a4e51af4168572a192295d870f6a6c49473308a25dacbc0deb703003547"}
2026-04-01 10:01:10 - Kind 7 event result: connecting to localhost:8888... ok.
{"kind":7,"id":"ad7e77948a4742abfca2d63c5bb748f3db3940c85841f521b3b7d634bf09955a","pubkey":"7c9b00749c966e50d18d4b807b44d4612bf9a8f67925fac3f00d21918b6e7f9c","created_at":1775052070,"tags":[],"content":"Test event kind 7 - should not require auth","sig":"8a5e5a03956cec04d56934314a0876fccbd221546432aaccdbc249b5f9cedc221219abf6e2856b98675de205afaa0e91ea642933ec6724576a40db43d17aa758"}
publishing to ws://localhost:8888... success.
[SUCCESS] Kind 7 event accepted without authentication (correct)
[INFO] Kind-specific authentication test completed
+392
View File
@@ -0,0 +1,392 @@
2026-04-01 09:11:07 - ==========================================
2026-04-01 09:11:07 - C-Relay Comprehensive Test Suite Runner
2026-04-01 09:11:07 - ==========================================
2026-04-01 09:11:07 - Relay URL: ws://127.0.0.1:8888
2026-04-01 09:11:07 - Log file: test_results_20260401_091107.log
2026-04-01 09:11:07 - Report file: test_report_20260401_091107.html
2026-04-01 09:11:07 -
2026-04-01 09:11:07 - Checking relay status at ws://127.0.0.1:8888...
2026-04-01 09:11:07 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
2026-04-01 09:11:07 -
2026-04-01 09:11:07 - Starting comprehensive test execution...
2026-04-01 09:11:07 -
2026-04-01 09:11:07 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
2026-04-01 09:11:07 - ==========================================
2026-04-01 09:11:07 - Running Test Suite: SQL Injection Tests
2026-04-01 09:11:07 - Description: Comprehensive SQL injection vulnerability testing
2026-04-01 09:11:07 - ==========================================
==========================================
C-Relay SQL Injection Test Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
=== Basic Connectivity Test ===
Testing Basic connectivity... PASSED - Valid query works
=== Authors Filter SQL Injection Tests ===
Testing Authors filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: */... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: #... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== IDs Filter SQL Injection Tests ===
Testing IDs filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: */... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: #... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== Kinds Filter SQL Injection Tests ===
Testing Kinds filter with string injection... PASSED - SQL injection blocked (rejected with error)
Testing Kinds filter with negative value... PASSED - SQL injection blocked (rejected with error)
Testing Kinds filter with very large value... PASSED - SQL injection blocked (rejected with error)
=== Search Filter SQL Injection Tests ===
Testing Search filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: */... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== Tag Filter SQL Injection Tests ===
Testing #e tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
=== Timestamp Filter SQL Injection Tests ===
Testing Since parameter injection... PASSED - SQL injection blocked (rejected with error)
Testing Until parameter injection... PASSED - SQL injection blocked (rejected with error)
=== Limit Parameter SQL Injection Tests ===
Testing Limit parameter injection... PASSED - SQL injection blocked (rejected with error)
Testing Limit with UNION... PASSED - SQL injection blocked (rejected with error)
=== Complex Multi-Filter SQL Injection Tests ===
Testing Multi-filter with authors injection... PASSED - SQL injection blocked (rejected with error)
Testing Multi-filter with search injection... PASSED - SQL injection blocked (rejected with error)
Testing Multi-filter with tag injection... PASSED - SQL injection blocked (query sanitized)
=== COUNT Message SQL Injection Tests ===
Testing COUNT with authors payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: */... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: */... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: #... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: #... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== Edge Case SQL Injection Tests ===
Testing Empty string injection... PASSED - SQL injection blocked (rejected with error)
Testing Null byte injection... PASSED - SQL injection blocked (silently rejected)
Testing Unicode injection... PASSED - SQL injection blocked (rejected with error)
Testing Very long injection payload... PASSED - SQL injection blocked (rejected with error)
=== Subscription ID SQL Injection Tests ===
Testing Subscription ID injection... PASSED - SQL injection blocked (rejected with error)
Testing Subscription ID with quotes... PASSED - SQL injection blocked (silently rejected)
=== CLOSE Message SQL Injection Tests ===
Testing CLOSE with injection... PASSED - SQL injection blocked (rejected with error)
=== Test Results ===
Total tests: 318
Passed: 318
Failed: 0
✓ All SQL injection tests passed!
The relay appears to be protected against SQL injection attacks.
2026-04-01 09:11:14 - \033[0;32m✓ SQL Injection Tests PASSED\033[0m (Duration: 7s)
2026-04-01 09:11:14 - ==========================================
2026-04-01 09:11:14 - Running Test Suite: Filter Validation Tests
2026-04-01 09:11:14 - Description: Input validation for REQ and COUNT messages
2026-04-01 09:11:14 - ==========================================
=== C-Relay Filter Validation Tests ===
Testing against relay at ws://127.0.0.1:8888
Testing Valid REQ message... PASSED
Testing Valid COUNT message... PASSED
=== Testing Filter Array Validation ===
Testing Non-object filter... PASSED
Testing Too many filters... PASSED
=== Testing Authors Validation ===
Testing Invalid author type... PASSED
Testing Invalid author hex... PASSED
Testing Too many authors... FAILED - Expected error 'error: too many authors', got: ["NOTICE", "error: invalid author hex string"]
2026-04-01 09:11:14 - \033[0;31m✗ Filter Validation Tests FAILED\033[0m (Duration: 0s)
+733
View File
@@ -0,0 +1,733 @@
2026-04-01 09:57:29 - ==========================================
2026-04-01 09:57:30 - C-Relay Comprehensive Test Suite Runner
2026-04-01 09:57:30 - ==========================================
2026-04-01 09:57:30 - Relay URL: ws://127.0.0.1:8888
2026-04-01 09:57:30 - Log file: test_results_20260401_095729.log
2026-04-01 09:57:30 - Report file: test_report_20260401_095729.html
2026-04-01 09:57:30 -
2026-04-01 09:57:30 - Checking relay status at ws://127.0.0.1:8888...
2026-04-01 09:57:30 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
2026-04-01 09:57:30 -
2026-04-01 09:57:30 - Starting comprehensive test execution...
2026-04-01 09:57:30 -
2026-04-01 09:57:30 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
2026-04-01 09:57:30 - ==========================================
2026-04-01 09:57:30 - Running Test Suite: SQL Injection Tests
2026-04-01 09:57:30 - Description: Comprehensive SQL injection vulnerability testing
2026-04-01 09:57:30 - ==========================================
==========================================
C-Relay SQL Injection Test Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
=== Basic Connectivity Test ===
Testing Basic connectivity... PASSED - Valid query works
=== Authors Filter SQL Injection Tests ===
Testing Authors filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: */... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: #... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== IDs Filter SQL Injection Tests ===
Testing IDs filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: */... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: #... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== Kinds Filter SQL Injection Tests ===
Testing Kinds filter with string injection... PASSED - SQL injection blocked (rejected with error)
Testing Kinds filter with negative value... PASSED - SQL injection blocked (rejected with error)
Testing Kinds filter with very large value... PASSED - SQL injection blocked (rejected with error)
=== Search Filter SQL Injection Tests ===
Testing Search filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: */... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== Tag Filter SQL Injection Tests ===
Testing #e tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
=== Timestamp Filter SQL Injection Tests ===
Testing Since parameter injection... PASSED - SQL injection blocked (rejected with error)
Testing Until parameter injection... PASSED - SQL injection blocked (rejected with error)
=== Limit Parameter SQL Injection Tests ===
Testing Limit parameter injection... PASSED - SQL injection blocked (rejected with error)
Testing Limit with UNION... PASSED - SQL injection blocked (rejected with error)
=== Complex Multi-Filter SQL Injection Tests ===
Testing Multi-filter with authors injection... PASSED - SQL injection blocked (rejected with error)
Testing Multi-filter with search injection... PASSED - SQL injection blocked (rejected with error)
Testing Multi-filter with tag injection... PASSED - SQL injection blocked (query sanitized)
=== COUNT Message SQL Injection Tests ===
Testing COUNT with authors payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: */... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: */... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: #... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: #... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== Edge Case SQL Injection Tests ===
Testing Empty string injection... PASSED - SQL injection blocked (rejected with error)
Testing Null byte injection... PASSED - SQL injection blocked (silently rejected)
Testing Unicode injection... PASSED - SQL injection blocked (rejected with error)
Testing Very long injection payload... PASSED - SQL injection blocked (rejected with error)
=== Subscription ID SQL Injection Tests ===
Testing Subscription ID injection... PASSED - SQL injection blocked (rejected with error)
Testing Subscription ID with quotes... PASSED - SQL injection blocked (silently rejected)
=== CLOSE Message SQL Injection Tests ===
Testing CLOSE with injection... PASSED - SQL injection blocked (rejected with error)
=== Test Results ===
Total tests: 318
Passed: 318
Failed: 0
✓ All SQL injection tests passed!
The relay appears to be protected against SQL injection attacks.
2026-04-01 09:57:36 - \033[0;32m✓ SQL Injection Tests PASSED\033[0m (Duration: 6s)
2026-04-01 09:57:36 - ==========================================
2026-04-01 09:57:36 - Running Test Suite: Filter Validation Tests
2026-04-01 09:57:36 - Description: Input validation for REQ and COUNT messages
2026-04-01 09:57:36 - ==========================================
=== C-Relay Filter Validation Tests ===
Testing against relay at ws://127.0.0.1:8888
Testing Valid REQ message... PASSED
Testing Valid COUNT message... PASSED
=== Testing Filter Array Validation ===
Testing Non-object filter... PASSED
Testing Too many filters... PASSED
=== Testing Authors Validation ===
Testing Invalid author type... PASSED
Testing Invalid author hex... PASSED
Testing Too many authors... PASSED
=== Testing IDs Validation ===
Testing Invalid ID type... PASSED
Testing Invalid ID hex... PASSED
Testing Too many IDs... PASSED
=== Testing Kinds Validation ===
Testing Invalid kind type... PASSED
Testing Negative kind... PASSED
Testing Too large kind... PASSED
Testing Too many kinds... PASSED
=== Testing Timestamp Validation ===
Testing Invalid since type... PASSED
Testing Negative since... PASSED
Testing Invalid until type... PASSED
Testing Negative until... PASSED
=== Testing Limit Validation ===
Testing Invalid limit type... PASSED
Testing Negative limit... PASSED
Testing Too large limit... PASSED
=== Testing Search Validation ===
Testing Invalid search type... PASSED
Testing Search too long... PASSED
Testing Search SQL injection... PASSED
=== Testing Tag Filter Validation ===
Testing Invalid tag filter type... PASSED
Testing Too many tag values... PASSED
Testing Tag value too long... PASSED
=== Testing Rate Limiting ===
Testing rate limiting with malformed requests... UNCERTAIN - Rate limiting may not have triggered (this could be normal)
=== Test Results ===
Total tests: 28
Passed: 28
Failed: 0
All tests passed!
2026-04-01 09:57:39 - \033[0;32m✓ Filter Validation Tests PASSED\033[0m (Duration: 3s)
2026-04-01 09:57:39 - ==========================================
2026-04-01 09:57:39 - Running Test Suite: Subscription Validation Tests
2026-04-01 09:57:39 - Description: Subscription ID and message validation
2026-04-01 09:57:39 - ==========================================
Testing subscription ID validation fixes...
Testing malformed subscription IDs...
Empty ID test: Connection failed (expected)
Long ID test: Connection failed (expected)
Invalid chars test: Connection failed (expected)
NULL ID test: Connection failed (expected)
Valid ID test: Failed
Testing CLOSE message validation...
CLOSE empty ID test: Connection failed (expected)
CLOSE valid ID test: Failed
Subscription validation tests completed.
2026-04-01 09:57:39 - \033[0;32m✓ Subscription Validation Tests PASSED\033[0m (Duration: 0s)
2026-04-01 09:57:39 - ==========================================
2026-04-01 09:57:39 - Running Test Suite: Memory Corruption Tests
2026-04-01 09:57:39 - Description: Buffer overflow and memory safety testing
2026-04-01 09:57:39 - ==========================================
==========================================
C-Relay Memory Corruption Test Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
Note: These tests may cause the relay to crash if vulnerabilities exist
=== Basic Connectivity Test ===
Testing Basic connectivity... PASSED - No memory corruption detected
=== Subscription ID Memory Corruption Tests ===
Testing Empty subscription ID... UNCERTAIN - Expected error but got normal response
Testing Very long subscription ID (1KB)... UNCERTAIN - Expected error but got normal response
Testing Very long subscription ID (10KB)... UNCERTAIN - Expected error but got normal response
Testing Subscription ID with null bytes... UNCERTAIN - Expected error but got normal response
Testing Subscription ID with special chars... UNCERTAIN - Expected error but got normal response
Testing Unicode subscription ID... UNCERTAIN - Expected error but got normal response
Testing Subscription ID with path traversal... UNCERTAIN - Expected error but got normal response
=== Filter Array Memory Corruption Tests ===
Testing Too many filters (50)... UNCERTAIN - Expected error but got normal response
=== Concurrent Access Memory Tests ===
Testing Concurrent subscription creation... ["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
["EVENT","concurrent_1775051860259483357",{"pubkey":"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa","created_at":1775051826,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"791ebcdcbe58fd5153b9a1f8f39907b0fcc4d86fc189e35fc9b6365031339b53","sig":"4164e20b9169a57c8086a0430b81af9bcabf69082c1d0d9d869673f2f5b0a350fb0ce882f2a19f08b613a0872edb69a0f8d1ccba0675bacff16c19b7282080e6"}]
PASSED - Concurrent access handled safely
Testing Concurrent CLOSE operations...
PASSED - Concurrent access handled safely
=== Malformed JSON Memory Tests ===
Testing Unclosed JSON object... UNCERTAIN - Expected error but got normal response
Testing Mismatched brackets... UNCERTAIN - Expected error but got normal response
Testing Extra closing brackets... UNCERTAIN - Expected error but got normal response
Testing Null bytes in JSON... UNCERTAIN - Expected error but got normal response
=== Large Message Memory Tests ===
Testing Very large filter array... UNCERTAIN - Expected error but got normal response
Testing Very long search term... UNCERTAIN - Expected error but got normal response
=== Test Results ===
Total tests: 17
Passed: 17
Failed: 0
✓ All memory corruption tests passed!
The relay appears to handle memory safely.
2026-04-01 09:57:40 - \033[0;32m✓ Memory Corruption Tests PASSED\033[0m (Duration: 1s)
2026-04-01 09:57:40 - ==========================================
2026-04-01 09:57:40 - Running Test Suite: Input Validation Tests
2026-04-01 09:57:40 - Description: Comprehensive input boundary testing
2026-04-01 09:57:40 - ==========================================
==========================================
C-Relay Input Validation Test Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
=== Basic Connectivity Test ===
Testing Basic connectivity... PASSED - Input accepted correctly
=== Message Type Validation ===
Testing Invalid message type - string... PASSED - Invalid input properly rejected
Testing Invalid message type - number... PASSED - Invalid input properly rejected
Testing Invalid message type - null... PASSED - Invalid input properly rejected
Testing Invalid message type - object... PASSED - Invalid input properly rejected
Testing Empty message type... PASSED - Invalid input properly rejected
Testing Very long message type... PASSED - Invalid input properly rejected
=== Message Structure Validation ===
Testing Too few arguments... PASSED - Invalid input properly rejected
Testing Too many arguments... PASSED - Invalid input properly rejected
Testing Non-array message... PASSED - Invalid input properly rejected
Testing Empty array... PASSED - Invalid input properly rejected
Testing Nested arrays incorrectly... PASSED - Invalid input properly rejected
=== Subscription ID Boundary Tests ===
Testing Valid subscription ID... PASSED - Input accepted correctly
Testing Empty subscription ID... PASSED - Invalid input properly rejected
Testing Subscription ID with spaces... PASSED - Invalid input properly rejected
Testing Subscription ID with newlines... PASSED - Invalid input properly rejected
Testing Subscription ID with tabs... PASSED - Invalid input properly rejected
Testing Subscription ID with control chars... PASSED - Invalid input properly rejected
Testing Unicode subscription ID... PASSED - Invalid input properly rejected
Testing Very long subscription ID... PASSED - Invalid input properly rejected
=== Filter Object Validation ===
Testing Valid empty filter... PASSED - Input accepted correctly
Testing Non-object filter... PASSED - Invalid input properly rejected
Testing Null filter... PASSED - Invalid input properly rejected
Testing Array filter... PASSED - Invalid input properly rejected
Testing Filter with invalid keys... PASSED - Input accepted correctly
=== Authors Field Validation ===
Testing Valid authors array... PASSED - Input accepted correctly
Testing Empty authors array... PASSED - Input accepted correctly
Testing Non-array authors... PASSED - Invalid input properly rejected
Testing Invalid hex in authors... PASSED - Invalid input properly rejected
Testing Short pubkey in authors... PASSED - Invalid input properly rejected
=== IDs Field Validation ===
Testing Valid ids array... PASSED - Input accepted correctly
Testing Empty ids array... PASSED - Input accepted correctly
Testing Non-array ids... PASSED - Invalid input properly rejected
=== Kinds Field Validation ===
Testing Valid kinds array... PASSED - Input accepted correctly
Testing Empty kinds array... PASSED - Input accepted correctly
Testing Non-array kinds... PASSED - Invalid input properly rejected
Testing String in kinds... PASSED - Invalid input properly rejected
=== Timestamp Field Validation ===
Testing Valid since timestamp... PASSED - Input accepted correctly
Testing Valid until timestamp... PASSED - Input accepted correctly
Testing String since timestamp... PASSED - Invalid input properly rejected
Testing Negative timestamp... PASSED - Invalid input properly rejected
=== Limit Field Validation ===
Testing Valid limit... PASSED - Input accepted correctly
Testing Zero limit... PASSED - Input accepted correctly
Testing String limit... PASSED - Invalid input properly rejected
Testing Negative limit... PASSED - Invalid input properly rejected
=== Multiple Filters ===
Testing Two valid filters... PASSED - Input accepted correctly
Testing Many filters... PASSED - Input accepted correctly
=== Test Results ===
Total tests: 47
Passed: 47
Failed: 0
✓ All input validation tests passed!
The relay properly validates input.
2026-04-01 09:57:41 - \033[0;32m✓ Input Validation Tests PASSED\033[0m (Duration: 1s)
2026-04-01 09:57:41 -
2026-04-01 09:57:41 - \033[0;34m=== PERFORMANCE TEST SUITES ===\033[0m
2026-04-01 09:57:41 - ==========================================
2026-04-01 09:57:41 - Running Test Suite: Subscription Limit Tests
2026-04-01 09:57:41 - Description: Subscription limit enforcement testing
2026-04-01 09:57:41 - ==========================================
=== Subscription Limit Test ===
[INFO] Testing relay at: ws://127.0.0.1:8888
[INFO] Note: This test assumes default subscription limits (max 25 per client)
=== Test 1: Basic Connectivity ===
[INFO] Testing basic WebSocket connection...
[PASS] Basic connectivity works
=== Test 2: Subscription Limit Enforcement ===
[INFO] Testing subscription limits by creating multiple subscriptions...
[INFO] Creating multiple subscriptions within a single connection...
[INFO] Hit subscription limit at subscription 2
[PASS] Subscription limit enforcement working (limit hit after 1 subscriptions)
=== Test Complete ===
2026-04-01 09:57:42 - \033[0;32m✓ Subscription Limit Tests PASSED\033[0m (Duration: 1s)
2026-04-01 09:57:42 - ==========================================
2026-04-01 09:57:42 - Running Test Suite: Load Testing
2026-04-01 09:57:42 - Description: High concurrent connection testing
2026-04-01 09:57:42 - ==========================================
==========================================
C-Relay Load Testing Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
=== Basic Connectivity Test ===
✓ Relay is accessible
==========================================
Load Test: Light Load Test
Description: Basic load test with moderate concurrent connections
Concurrent clients: 10
Messages per client: 5
==========================================
Launching 10 clients...
All clients completed. Processing results...
=== Load Test Results ===
Test duration: 1s
Total connections attempted: 10
Successful connections: 10
Failed connections: 0
Connection success rate: 100%
Messages expected: 50
Messages sent: 50
Messages received: 100
✓ EXCELLENT: High connection success rate
Checking relay responsiveness... ✓ Relay is still responsive
==========================================
Load Test: Medium Load Test
Description: Moderate load test with higher concurrency
Concurrent clients: 25
Messages per client: 10
==========================================
Launching 25 clients...
All clients completed. Processing results...
=== Load Test Results ===
Test duration: 4s
Total connections attempted: 35
Successful connections: 25
Failed connections: 0
Connection success rate: 71%
Messages expected: 250
Messages sent: 250
Messages received: 500
✗ POOR: Low connection success rate
Checking relay responsiveness... ✓ Relay is still responsive
==========================================
Load Test: Heavy Load Test
Description: Heavy load test with high concurrency
Concurrent clients: 50
Messages per client: 20
==========================================
Launching 50 clients...
All clients completed. Processing results...
=== Load Test Results ===
Test duration: 15s
Total connections attempted: 85
Successful connections: 50
Failed connections: 0
Connection success rate: 58%
Messages expected: 1000
Messages sent: 1000
Messages received: 2000
✗ POOR: Low connection success rate
Checking relay responsiveness... ✓ Relay is still responsive
==========================================
Load Test: Stress Test
Description: Maximum load test to find breaking point
Concurrent clients: 100
Messages per client: 50
==========================================
Launching 100 clients...
All clients completed. Processing results...
=== Load Test Results ===
Test duration: 64s
Total connections attempted: 185
Successful connections: 100
Failed connections: 0
Connection success rate: 54%
Messages expected: 5000
Messages sent: 5000
Messages received: 7500
✗ POOR: Low connection success rate
Checking relay responsiveness... ✓ Relay is still responsive
==========================================
Load Testing Complete
==========================================
All load tests completed. Check individual test results above.
If any tests failed, the relay may need optimization or have resource limits.
2026-04-01 09:59:07 - \033[0;32m✓ Load Testing PASSED\033[0m (Duration: 85s)
2026-04-01 09:59:07 - ==========================================
2026-04-01 09:59:07 - Running Test Suite: Stress Testing
2026-04-01 09:59:07 - Description: Resource usage and stability testing
2026-04-01 09:59:07 - ==========================================
2026-04-01 09:59:07 - \033[0;31mERROR: Test script stress_tests.sh not found\033[0m
+733
View File
@@ -0,0 +1,733 @@
2026-04-01 11:02:00 - ==========================================
2026-04-01 11:02:00 - C-Relay Comprehensive Test Suite Runner
2026-04-01 11:02:00 - ==========================================
2026-04-01 11:02:00 - Relay URL: ws://127.0.0.1:8888
2026-04-01 11:02:00 - Log file: test_results_20260401_110200.log
2026-04-01 11:02:00 - Report file: test_report_20260401_110200.html
2026-04-01 11:02:00 -
2026-04-01 11:02:00 - Checking relay status at ws://127.0.0.1:8888...
2026-04-01 11:02:00 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
2026-04-01 11:02:00 -
2026-04-01 11:02:00 - Starting comprehensive test execution...
2026-04-01 11:02:00 -
2026-04-01 11:02:00 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
2026-04-01 11:02:00 - ==========================================
2026-04-01 11:02:00 - Running Test Suite: SQL Injection Tests
2026-04-01 11:02:00 - Description: Comprehensive SQL injection vulnerability testing
2026-04-01 11:02:00 - ==========================================
==========================================
C-Relay SQL Injection Test Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
=== Basic Connectivity Test ===
Testing Basic connectivity... PASSED - Valid query works
=== Authors Filter SQL Injection Tests ===
Testing Authors filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: */... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: #... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== IDs Filter SQL Injection Tests ===
Testing IDs filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: */... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: #... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== Kinds Filter SQL Injection Tests ===
Testing Kinds filter with string injection... PASSED - SQL injection blocked (rejected with error)
Testing Kinds filter with negative value... PASSED - SQL injection blocked (rejected with error)
Testing Kinds filter with very large value... PASSED - SQL injection blocked (rejected with error)
=== Search Filter SQL Injection Tests ===
Testing Search filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: */... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== Tag Filter SQL Injection Tests ===
Testing #e tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
=== Timestamp Filter SQL Injection Tests ===
Testing Since parameter injection... PASSED - SQL injection blocked (rejected with error)
Testing Until parameter injection... PASSED - SQL injection blocked (rejected with error)
=== Limit Parameter SQL Injection Tests ===
Testing Limit parameter injection... PASSED - SQL injection blocked (rejected with error)
Testing Limit with UNION... PASSED - SQL injection blocked (rejected with error)
=== Complex Multi-Filter SQL Injection Tests ===
Testing Multi-filter with authors injection... PASSED - SQL injection blocked (rejected with error)
Testing Multi-filter with search injection... PASSED - SQL injection blocked (rejected with error)
Testing Multi-filter with tag injection... PASSED - SQL injection blocked (query sanitized)
=== COUNT Message SQL Injection Tests ===
Testing COUNT with authors payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: */... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: */... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: #... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: #... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== Edge Case SQL Injection Tests ===
Testing Empty string injection... PASSED - SQL injection blocked (rejected with error)
Testing Null byte injection... PASSED - SQL injection blocked (silently rejected)
Testing Unicode injection... PASSED - SQL injection blocked (rejected with error)
Testing Very long injection payload... PASSED - SQL injection blocked (rejected with error)
=== Subscription ID SQL Injection Tests ===
Testing Subscription ID injection... PASSED - SQL injection blocked (rejected with error)
Testing Subscription ID with quotes... PASSED - SQL injection blocked (silently rejected)
=== CLOSE Message SQL Injection Tests ===
Testing CLOSE with injection... PASSED - SQL injection blocked (rejected with error)
=== Test Results ===
Total tests: 318
Passed: 318
Failed: 0
✓ All SQL injection tests passed!
The relay appears to be protected against SQL injection attacks.
2026-04-01 11:02:07 - \033[0;32m✓ SQL Injection Tests PASSED\033[0m (Duration: 7s)
2026-04-01 11:02:07 - ==========================================
2026-04-01 11:02:07 - Running Test Suite: Filter Validation Tests
2026-04-01 11:02:07 - Description: Input validation for REQ and COUNT messages
2026-04-01 11:02:07 - ==========================================
=== C-Relay Filter Validation Tests ===
Testing against relay at ws://127.0.0.1:8888
Testing Valid REQ message... PASSED
Testing Valid COUNT message... PASSED
=== Testing Filter Array Validation ===
Testing Non-object filter... PASSED
Testing Too many filters... PASSED
=== Testing Authors Validation ===
Testing Invalid author type... PASSED
Testing Invalid author hex... PASSED
Testing Too many authors... PASSED
=== Testing IDs Validation ===
Testing Invalid ID type... PASSED
Testing Invalid ID hex... PASSED
Testing Too many IDs... PASSED
=== Testing Kinds Validation ===
Testing Invalid kind type... PASSED
Testing Negative kind... PASSED
Testing Too large kind... PASSED
Testing Too many kinds... PASSED
=== Testing Timestamp Validation ===
Testing Invalid since type... PASSED
Testing Negative since... PASSED
Testing Invalid until type... PASSED
Testing Negative until... PASSED
=== Testing Limit Validation ===
Testing Invalid limit type... PASSED
Testing Negative limit... PASSED
Testing Too large limit... PASSED
=== Testing Search Validation ===
Testing Invalid search type... PASSED
Testing Search too long... PASSED
Testing Search SQL injection... PASSED
=== Testing Tag Filter Validation ===
Testing Invalid tag filter type... PASSED
Testing Too many tag values... PASSED
Testing Tag value too long... PASSED
=== Testing Rate Limiting ===
Testing rate limiting with malformed requests... UNCERTAIN - Rate limiting may not have triggered (this could be normal)
=== Test Results ===
Total tests: 28
Passed: 28
Failed: 0
All tests passed!
2026-04-01 11:02:10 - \033[0;32m✓ Filter Validation Tests PASSED\033[0m (Duration: 3s)
2026-04-01 11:02:10 - ==========================================
2026-04-01 11:02:10 - Running Test Suite: Subscription Validation Tests
2026-04-01 11:02:10 - Description: Subscription ID and message validation
2026-04-01 11:02:10 - ==========================================
Testing subscription ID validation fixes...
Testing malformed subscription IDs...
Empty ID test: Connection failed (expected)
Long ID test: Connection failed (expected)
Invalid chars test: Connection failed (expected)
NULL ID test: Connection failed (expected)
Valid ID test: Failed
Testing CLOSE message validation...
CLOSE empty ID test: Connection failed (expected)
CLOSE valid ID test: Failed
Subscription validation tests completed.
2026-04-01 11:02:10 - \033[0;32m✓ Subscription Validation Tests PASSED\033[0m (Duration: 0s)
2026-04-01 11:02:10 - ==========================================
2026-04-01 11:02:10 - Running Test Suite: Memory Corruption Tests
2026-04-01 11:02:10 - Description: Buffer overflow and memory safety testing
2026-04-01 11:02:10 - ==========================================
==========================================
C-Relay Memory Corruption Test Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
Note: These tests may cause the relay to crash if vulnerabilities exist
=== Basic Connectivity Test ===
Testing Basic connectivity... PASSED - No memory corruption detected
=== Subscription ID Memory Corruption Tests ===
Testing Empty subscription ID... UNCERTAIN - Expected error but got normal response
Testing Very long subscription ID (1KB)... UNCERTAIN - Expected error but got normal response
Testing Very long subscription ID (10KB)... UNCERTAIN - Expected error but got normal response
Testing Subscription ID with null bytes... UNCERTAIN - Expected error but got normal response
Testing Subscription ID with special chars... UNCERTAIN - Expected error but got normal response
Testing Unicode subscription ID... UNCERTAIN - Expected error but got normal response
Testing Subscription ID with path traversal... UNCERTAIN - Expected error but got normal response
=== Filter Array Memory Corruption Tests ===
Testing Too many filters (50)... UNCERTAIN - Expected error but got normal response
=== Concurrent Access Memory Tests ===
Testing Concurrent subscription creation... ["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
["EVENT","concurrent_1775055730660028379",{"pubkey":"26a8961265cc6db56d18b95fb54ad16a54259ab4342a2bd83e102846cb120e94","created_at":1775055645,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"f5ac528fb84b75bdef8050a56f0c2b4733389b2d97f91344811a7c3bf7e0bc4d","sig":"8c7480c64a4db80b643f60d4d6d38b5585bd6b10da3dbee0eab7b60796ee18826400162242c7056782a410de0b119860ba3210bffba78f0f6be466634e3b6940"}]
PASSED - Concurrent access handled safely
Testing Concurrent CLOSE operations...
PASSED - Concurrent access handled safely
=== Malformed JSON Memory Tests ===
Testing Unclosed JSON object... UNCERTAIN - Expected error but got normal response
Testing Mismatched brackets... UNCERTAIN - Expected error but got normal response
Testing Extra closing brackets... UNCERTAIN - Expected error but got normal response
Testing Null bytes in JSON... UNCERTAIN - Expected error but got normal response
=== Large Message Memory Tests ===
Testing Very large filter array... UNCERTAIN - Expected error but got normal response
Testing Very long search term... UNCERTAIN - Expected error but got normal response
=== Test Results ===
Total tests: 17
Passed: 17
Failed: 0
✓ All memory corruption tests passed!
The relay appears to handle memory safely.
2026-04-01 11:02:11 - \033[0;32m✓ Memory Corruption Tests PASSED\033[0m (Duration: 1s)
2026-04-01 11:02:11 - ==========================================
2026-04-01 11:02:11 - Running Test Suite: Input Validation Tests
2026-04-01 11:02:11 - Description: Comprehensive input boundary testing
2026-04-01 11:02:11 - ==========================================
==========================================
C-Relay Input Validation Test Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
=== Basic Connectivity Test ===
Testing Basic connectivity... PASSED - Input accepted correctly
=== Message Type Validation ===
Testing Invalid message type - string... PASSED - Invalid input properly rejected
Testing Invalid message type - number... PASSED - Invalid input properly rejected
Testing Invalid message type - null... PASSED - Invalid input properly rejected
Testing Invalid message type - object... PASSED - Invalid input properly rejected
Testing Empty message type... PASSED - Invalid input properly rejected
Testing Very long message type... PASSED - Invalid input properly rejected
=== Message Structure Validation ===
Testing Too few arguments... PASSED - Invalid input properly rejected
Testing Too many arguments... PASSED - Invalid input properly rejected
Testing Non-array message... PASSED - Invalid input properly rejected
Testing Empty array... PASSED - Invalid input properly rejected
Testing Nested arrays incorrectly... PASSED - Invalid input properly rejected
=== Subscription ID Boundary Tests ===
Testing Valid subscription ID... PASSED - Input accepted correctly
Testing Empty subscription ID... PASSED - Invalid input properly rejected
Testing Subscription ID with spaces... PASSED - Invalid input properly rejected
Testing Subscription ID with newlines... PASSED - Invalid input properly rejected
Testing Subscription ID with tabs... PASSED - Invalid input properly rejected
Testing Subscription ID with control chars... PASSED - Invalid input properly rejected
Testing Unicode subscription ID... PASSED - Invalid input properly rejected
Testing Very long subscription ID... PASSED - Invalid input properly rejected
=== Filter Object Validation ===
Testing Valid empty filter... PASSED - Input accepted correctly
Testing Non-object filter... PASSED - Invalid input properly rejected
Testing Null filter... PASSED - Invalid input properly rejected
Testing Array filter... PASSED - Invalid input properly rejected
Testing Filter with invalid keys... PASSED - Input accepted correctly
=== Authors Field Validation ===
Testing Valid authors array... PASSED - Input accepted correctly
Testing Empty authors array... PASSED - Input accepted correctly
Testing Non-array authors... PASSED - Invalid input properly rejected
Testing Invalid hex in authors... PASSED - Invalid input properly rejected
Testing Short pubkey in authors... PASSED - Invalid input properly rejected
=== IDs Field Validation ===
Testing Valid ids array... PASSED - Input accepted correctly
Testing Empty ids array... PASSED - Input accepted correctly
Testing Non-array ids... PASSED - Invalid input properly rejected
=== Kinds Field Validation ===
Testing Valid kinds array... PASSED - Input accepted correctly
Testing Empty kinds array... PASSED - Input accepted correctly
Testing Non-array kinds... PASSED - Invalid input properly rejected
Testing String in kinds... PASSED - Invalid input properly rejected
=== Timestamp Field Validation ===
Testing Valid since timestamp... PASSED - Input accepted correctly
Testing Valid until timestamp... PASSED - Input accepted correctly
Testing String since timestamp... PASSED - Invalid input properly rejected
Testing Negative timestamp... PASSED - Invalid input properly rejected
=== Limit Field Validation ===
Testing Valid limit... PASSED - Input accepted correctly
Testing Zero limit... PASSED - Input accepted correctly
Testing String limit... PASSED - Invalid input properly rejected
Testing Negative limit... PASSED - Invalid input properly rejected
=== Multiple Filters ===
Testing Two valid filters... PASSED - Input accepted correctly
Testing Many filters... PASSED - Input accepted correctly
=== Test Results ===
Total tests: 47
Passed: 47
Failed: 0
✓ All input validation tests passed!
The relay properly validates input.
2026-04-01 11:02:12 - \033[0;32m✓ Input Validation Tests PASSED\033[0m (Duration: 1s)
2026-04-01 11:02:12 -
2026-04-01 11:02:12 - \033[0;34m=== PERFORMANCE TEST SUITES ===\033[0m
2026-04-01 11:02:12 - ==========================================
2026-04-01 11:02:12 - Running Test Suite: Subscription Limit Tests
2026-04-01 11:02:12 - Description: Subscription limit enforcement testing
2026-04-01 11:02:12 - ==========================================
=== Subscription Limit Test ===
[INFO] Testing relay at: ws://127.0.0.1:8888
[INFO] Note: This test assumes default subscription limits (max 25 per client)
=== Test 1: Basic Connectivity ===
[INFO] Testing basic WebSocket connection...
[PASS] Basic connectivity works
=== Test 2: Subscription Limit Enforcement ===
[INFO] Testing subscription limits by creating multiple subscriptions...
[INFO] Creating multiple subscriptions within a single connection...
[INFO] Hit subscription limit at subscription 2
[PASS] Subscription limit enforcement working (limit hit after 1 subscriptions)
=== Test Complete ===
2026-04-01 11:02:12 - \033[0;32m✓ Subscription Limit Tests PASSED\033[0m (Duration: 0s)
2026-04-01 11:02:12 - ==========================================
2026-04-01 11:02:12 - Running Test Suite: Load Testing
2026-04-01 11:02:12 - Description: High concurrent connection testing
2026-04-01 11:02:12 - ==========================================
==========================================
C-Relay Load Testing Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
=== Basic Connectivity Test ===
✓ Relay is accessible
==========================================
Load Test: Light Load Test
Description: Basic load test with moderate concurrent connections
Concurrent clients: 10
Messages per client: 5
==========================================
Launching 10 clients...
All clients completed. Processing results...
=== Load Test Results ===
Test duration: 1s
Total connections attempted: 10
Successful connections: 10
Failed connections: 0
Connection success rate: 100%
Messages expected: 50
Messages sent: 50
Messages received: 100
✓ EXCELLENT: High connection success rate
Checking relay responsiveness... ✓ Relay is still responsive
==========================================
Load Test: Medium Load Test
Description: Moderate load test with higher concurrency
Concurrent clients: 25
Messages per client: 10
==========================================
Launching 25 clients...
All clients completed. Processing results...
=== Load Test Results ===
Test duration: 4s
Total connections attempted: 35
Successful connections: 25
Failed connections: 0
Connection success rate: 71%
Messages expected: 250
Messages sent: 250
Messages received: 500
✗ POOR: Low connection success rate
Checking relay responsiveness... ✓ Relay is still responsive
==========================================
Load Test: Heavy Load Test
Description: Heavy load test with high concurrency
Concurrent clients: 50
Messages per client: 20
==========================================
Launching 50 clients...
All clients completed. Processing results...
=== Load Test Results ===
Test duration: 14s
Total connections attempted: 85
Successful connections: 50
Failed connections: 0
Connection success rate: 58%
Messages expected: 1000
Messages sent: 1000
Messages received: 2000
✗ POOR: Low connection success rate
Checking relay responsiveness... ✓ Relay is still responsive
==========================================
Load Test: Stress Test
Description: Maximum load test to find breaking point
Concurrent clients: 100
Messages per client: 50
==========================================
Launching 100 clients...
All clients completed. Processing results...
=== Load Test Results ===
Test duration: 64s
Total connections attempted: 185
Successful connections: 100
Failed connections: 0
Connection success rate: 54%
Messages expected: 5000
Messages sent: 5000
Messages received: 7500
✗ POOR: Low connection success rate
Checking relay responsiveness... ✓ Relay is still responsive
==========================================
Load Testing Complete
==========================================
All load tests completed. Check individual test results above.
If any tests failed, the relay may need optimization or have resource limits.
2026-04-01 11:03:37 - \033[0;32m✓ Load Testing PASSED\033[0m (Duration: 85s)
2026-04-01 11:03:37 - ==========================================
2026-04-01 11:03:37 - Running Test Suite: Stress Testing
2026-04-01 11:03:37 - Description: Resource usage and stability testing
2026-04-01 11:03:37 - ==========================================
2026-04-01 11:03:37 - \033[0;31mERROR: Test script stress_tests.sh not found\033[0m
+733
View File
@@ -0,0 +1,733 @@
2026-04-01 11:29:24 - ==========================================
2026-04-01 11:29:24 - C-Relay Comprehensive Test Suite Runner
2026-04-01 11:29:24 - ==========================================
2026-04-01 11:29:24 - Relay URL: ws://127.0.0.1:8888
2026-04-01 11:29:24 - Log file: test_results_20260401_112924.log
2026-04-01 11:29:24 - Report file: test_report_20260401_112924.html
2026-04-01 11:29:24 -
2026-04-01 11:29:24 - Checking relay status at ws://127.0.0.1:8888...
2026-04-01 11:29:25 - \033[0;32m✓ Relay HTTP endpoint is accessible\033[0m
2026-04-01 11:29:25 -
2026-04-01 11:29:25 - Starting comprehensive test execution...
2026-04-01 11:29:25 -
2026-04-01 11:29:25 - \033[0;34m=== SECURITY TEST SUITES ===\033[0m
2026-04-01 11:29:25 - ==========================================
2026-04-01 11:29:25 - Running Test Suite: SQL Injection Tests
2026-04-01 11:29:25 - Description: Comprehensive SQL injection vulnerability testing
2026-04-01 11:29:25 - ==========================================
==========================================
C-Relay SQL Injection Test Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
=== Basic Connectivity Test ===
Testing Basic connectivity... PASSED - Valid query works
=== Authors Filter SQL Injection Tests ===
Testing Authors filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: */... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: #... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing Authors filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== IDs Filter SQL Injection Tests ===
Testing IDs filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: */... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: #... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing IDs filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== Kinds Filter SQL Injection Tests ===
Testing Kinds filter with string injection... PASSED - SQL injection blocked (rejected with error)
Testing Kinds filter with negative value... PASSED - SQL injection blocked (rejected with error)
Testing Kinds filter with very large value... PASSED - SQL injection blocked (rejected with error)
=== Search Filter SQL Injection Tests ===
Testing Search filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: */... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing Search filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing Search filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== Tag Filter SQL Injection Tests ===
Testing #e tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #e tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #p tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #t tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #r tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' OR 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: admin'--... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: /*... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: */... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: /**/... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: #... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' AND 1=1 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' AND 1=2 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (query sanitized)
Testing #d tag filter with payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (query sanitized)
=== Timestamp Filter SQL Injection Tests ===
Testing Since parameter injection... PASSED - SQL injection blocked (rejected with error)
Testing Until parameter injection... PASSED - SQL injection blocked (rejected with error)
=== Limit Parameter SQL Injection Tests ===
Testing Limit parameter injection... PASSED - SQL injection blocked (rejected with error)
Testing Limit with UNION... PASSED - SQL injection blocked (rejected with error)
=== Complex Multi-Filter SQL Injection Tests ===
Testing Multi-filter with authors injection... PASSED - SQL injection blocked (rejected with error)
Testing Multi-filter with search injection... PASSED - SQL injection blocked (rejected with error)
Testing Multi-filter with tag injection... PASSED - SQL injection blocked (query sanitized)
=== COUNT Message SQL Injection Tests ===
Testing COUNT with authors payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' OR '1'='1... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' OR '1'='1... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1; SELECT * FROM sqlite_master; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' OR 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: admin'--... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' UNION SELECT password FROM users --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND SLEEP(5) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND 1=1 WAITFOR DELAY '0:0:5' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND 1=CAST((SELECT version()) AS INT) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' UNION SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' UNION SELECT 1,2,3 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' UNION ALL SELECT NULL,NULL,NULL --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; SELECT * FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; DELETE FROM events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; UPDATE events SET content='hacked' WHERE 1=1; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: /*... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: */... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: */... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: /**/... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: #... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: #... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: 0x53514C5F494E4A454354494F4E... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: J1NSTCBJTkpFQ1RJT04gLS0=... PASSED - SQL injection blocked (query sanitized)
Testing COUNT with authors payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '))); DROP TABLE events; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ')) UNION SELECT NULL; --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' AND 1=1 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' AND 1=2 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: ' AND (SELECT COUNT(*) FROM events) > 0 --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; EXEC master..xp_cmdshell 'net user' --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with authors payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
Testing COUNT with search payload: '; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --... PASSED - SQL injection blocked (rejected with error)
=== Edge Case SQL Injection Tests ===
Testing Empty string injection... PASSED - SQL injection blocked (rejected with error)
Testing Null byte injection... PASSED - SQL injection blocked (silently rejected)
Testing Unicode injection... PASSED - SQL injection blocked (rejected with error)
Testing Very long injection payload... PASSED - SQL injection blocked (rejected with error)
=== Subscription ID SQL Injection Tests ===
Testing Subscription ID injection... PASSED - SQL injection blocked (rejected with error)
Testing Subscription ID with quotes... PASSED - SQL injection blocked (silently rejected)
=== CLOSE Message SQL Injection Tests ===
Testing CLOSE with injection... PASSED - SQL injection blocked (rejected with error)
=== Test Results ===
Total tests: 318
Passed: 318
Failed: 0
✓ All SQL injection tests passed!
The relay appears to be protected against SQL injection attacks.
2026-04-01 11:29:32 - \033[0;32m✓ SQL Injection Tests PASSED\033[0m (Duration: 7s)
2026-04-01 11:29:32 - ==========================================
2026-04-01 11:29:32 - Running Test Suite: Filter Validation Tests
2026-04-01 11:29:32 - Description: Input validation for REQ and COUNT messages
2026-04-01 11:29:32 - ==========================================
=== C-Relay Filter Validation Tests ===
Testing against relay at ws://127.0.0.1:8888
Testing Valid REQ message... PASSED
Testing Valid COUNT message... PASSED
=== Testing Filter Array Validation ===
Testing Non-object filter... PASSED
Testing Too many filters... PASSED
=== Testing Authors Validation ===
Testing Invalid author type... PASSED
Testing Invalid author hex... PASSED
Testing Too many authors... PASSED
=== Testing IDs Validation ===
Testing Invalid ID type... PASSED
Testing Invalid ID hex... PASSED
Testing Too many IDs... PASSED
=== Testing Kinds Validation ===
Testing Invalid kind type... PASSED
Testing Negative kind... PASSED
Testing Too large kind... PASSED
Testing Too many kinds... PASSED
=== Testing Timestamp Validation ===
Testing Invalid since type... PASSED
Testing Negative since... PASSED
Testing Invalid until type... PASSED
Testing Negative until... PASSED
=== Testing Limit Validation ===
Testing Invalid limit type... PASSED
Testing Negative limit... PASSED
Testing Too large limit... PASSED
=== Testing Search Validation ===
Testing Invalid search type... PASSED
Testing Search too long... PASSED
Testing Search SQL injection... PASSED
=== Testing Tag Filter Validation ===
Testing Invalid tag filter type... PASSED
Testing Too many tag values... PASSED
Testing Tag value too long... PASSED
=== Testing Rate Limiting ===
Testing rate limiting with malformed requests... UNCERTAIN - Rate limiting may not have triggered (this could be normal)
=== Test Results ===
Total tests: 28
Passed: 28
Failed: 0
All tests passed!
2026-04-01 11:29:35 - \033[0;32m✓ Filter Validation Tests PASSED\033[0m (Duration: 3s)
2026-04-01 11:29:35 - ==========================================
2026-04-01 11:29:35 - Running Test Suite: Subscription Validation Tests
2026-04-01 11:29:35 - Description: Subscription ID and message validation
2026-04-01 11:29:35 - ==========================================
Testing subscription ID validation fixes...
Testing malformed subscription IDs...
Empty ID test: Connection failed (expected)
Long ID test: Connection failed (expected)
Invalid chars test: Connection failed (expected)
NULL ID test: Connection failed (expected)
Valid ID test: Failed
Testing CLOSE message validation...
CLOSE empty ID test: Connection failed (expected)
CLOSE valid ID test: Failed
Subscription validation tests completed.
2026-04-01 11:29:35 - \033[0;32m✓ Subscription Validation Tests PASSED\033[0m (Duration: 0s)
2026-04-01 11:29:35 - ==========================================
2026-04-01 11:29:35 - Running Test Suite: Memory Corruption Tests
2026-04-01 11:29:35 - Description: Buffer overflow and memory safety testing
2026-04-01 11:29:35 - ==========================================
==========================================
C-Relay Memory Corruption Test Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
Note: These tests may cause the relay to crash if vulnerabilities exist
=== Basic Connectivity Test ===
Testing Basic connectivity... PASSED - No memory corruption detected
=== Subscription ID Memory Corruption Tests ===
Testing Empty subscription ID... UNCERTAIN - Expected error but got normal response
Testing Very long subscription ID (1KB)... UNCERTAIN - Expected error but got normal response
Testing Very long subscription ID (10KB)... UNCERTAIN - Expected error but got normal response
Testing Subscription ID with null bytes... UNCERTAIN - Expected error but got normal response
Testing Subscription ID with special chars... UNCERTAIN - Expected error but got normal response
Testing Unicode subscription ID... UNCERTAIN - Expected error but got normal response
Testing Subscription ID with path traversal... UNCERTAIN - Expected error but got normal response
=== Filter Array Memory Corruption Tests ===
Testing Too many filters (50)... UNCERTAIN - Expected error but got normal response
=== Concurrent Access Memory Tests ===
Testing Concurrent subscription creation... ["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
["EVENT","concurrent_1775057376377657551",{"pubkey":"7b013abdf0cbc8f994e66018539fbcb750f3be118a70d8bdcf3b3e86dd2ebb40","created_at":1775057288,"kind":1,"tags":[],"content":"\nRelay Statistics\n━━━━━━━━━━━━━━━━━━━━\nDatabase Size 0.00 MB (4096 bytes)\nTotal Events 0\nActive Subscriptions 0\nOldest Event -\nNewest Event -\n\nEvent_Kind_Distribution:\n\nTime-based Statistics:\n0\tEvents in the last day\n0\tEvents in the last week\n0\tEvents in the last month\n\n\n","id":"210f21884a3581064f4d181bc10e4088b5161216ddc7088f5c39e570240687b3","sig":"a04a697e6b3a53a1df4e3d7d06da2483f07d45c97a939076bd8b28216425a854af5f7e463542adbed9b71af68d36d7e0296be0048c3156a51ed0defa685a5171"}]
PASSED - Concurrent access handled safely
Testing Concurrent CLOSE operations...
PASSED - Concurrent access handled safely
=== Malformed JSON Memory Tests ===
Testing Unclosed JSON object... UNCERTAIN - Expected error but got normal response
Testing Mismatched brackets... UNCERTAIN - Expected error but got normal response
Testing Extra closing brackets... UNCERTAIN - Expected error but got normal response
Testing Null bytes in JSON... UNCERTAIN - Expected error but got normal response
=== Large Message Memory Tests ===
Testing Very large filter array... UNCERTAIN - Expected error but got normal response
Testing Very long search term... UNCERTAIN - Expected error but got normal response
=== Test Results ===
Total tests: 17
Passed: 17
Failed: 0
✓ All memory corruption tests passed!
The relay appears to handle memory safely.
2026-04-01 11:29:36 - \033[0;32m✓ Memory Corruption Tests PASSED\033[0m (Duration: 1s)
2026-04-01 11:29:36 - ==========================================
2026-04-01 11:29:36 - Running Test Suite: Input Validation Tests
2026-04-01 11:29:36 - Description: Comprehensive input boundary testing
2026-04-01 11:29:36 - ==========================================
==========================================
C-Relay Input Validation Test Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
=== Basic Connectivity Test ===
Testing Basic connectivity... PASSED - Input accepted correctly
=== Message Type Validation ===
Testing Invalid message type - string... PASSED - Invalid input properly rejected
Testing Invalid message type - number... PASSED - Invalid input properly rejected
Testing Invalid message type - null... PASSED - Invalid input properly rejected
Testing Invalid message type - object... PASSED - Invalid input properly rejected
Testing Empty message type... PASSED - Invalid input properly rejected
Testing Very long message type... PASSED - Invalid input properly rejected
=== Message Structure Validation ===
Testing Too few arguments... PASSED - Invalid input properly rejected
Testing Too many arguments... PASSED - Invalid input properly rejected
Testing Non-array message... PASSED - Invalid input properly rejected
Testing Empty array... PASSED - Invalid input properly rejected
Testing Nested arrays incorrectly... PASSED - Invalid input properly rejected
=== Subscription ID Boundary Tests ===
Testing Valid subscription ID... PASSED - Input accepted correctly
Testing Empty subscription ID... PASSED - Invalid input properly rejected
Testing Subscription ID with spaces... PASSED - Invalid input properly rejected
Testing Subscription ID with newlines... PASSED - Invalid input properly rejected
Testing Subscription ID with tabs... PASSED - Invalid input properly rejected
Testing Subscription ID with control chars... PASSED - Invalid input properly rejected
Testing Unicode subscription ID... PASSED - Invalid input properly rejected
Testing Very long subscription ID... PASSED - Invalid input properly rejected
=== Filter Object Validation ===
Testing Valid empty filter... PASSED - Input accepted correctly
Testing Non-object filter... PASSED - Invalid input properly rejected
Testing Null filter... PASSED - Invalid input properly rejected
Testing Array filter... PASSED - Invalid input properly rejected
Testing Filter with invalid keys... PASSED - Input accepted correctly
=== Authors Field Validation ===
Testing Valid authors array... PASSED - Input accepted correctly
Testing Empty authors array... PASSED - Input accepted correctly
Testing Non-array authors... PASSED - Invalid input properly rejected
Testing Invalid hex in authors... PASSED - Invalid input properly rejected
Testing Short pubkey in authors... PASSED - Invalid input properly rejected
=== IDs Field Validation ===
Testing Valid ids array... PASSED - Input accepted correctly
Testing Empty ids array... PASSED - Input accepted correctly
Testing Non-array ids... PASSED - Invalid input properly rejected
=== Kinds Field Validation ===
Testing Valid kinds array... PASSED - Input accepted correctly
Testing Empty kinds array... PASSED - Input accepted correctly
Testing Non-array kinds... PASSED - Invalid input properly rejected
Testing String in kinds... PASSED - Invalid input properly rejected
=== Timestamp Field Validation ===
Testing Valid since timestamp... PASSED - Input accepted correctly
Testing Valid until timestamp... PASSED - Input accepted correctly
Testing String since timestamp... PASSED - Invalid input properly rejected
Testing Negative timestamp... PASSED - Invalid input properly rejected
=== Limit Field Validation ===
Testing Valid limit... PASSED - Input accepted correctly
Testing Zero limit... PASSED - Input accepted correctly
Testing String limit... PASSED - Invalid input properly rejected
Testing Negative limit... PASSED - Invalid input properly rejected
=== Multiple Filters ===
Testing Two valid filters... PASSED - Input accepted correctly
Testing Many filters... PASSED - Input accepted correctly
=== Test Results ===
Total tests: 47
Passed: 47
Failed: 0
✓ All input validation tests passed!
The relay properly validates input.
2026-04-01 11:29:38 - \033[0;32m✓ Input Validation Tests PASSED\033[0m (Duration: 2s)
2026-04-01 11:29:38 -
2026-04-01 11:29:38 - \033[0;34m=== PERFORMANCE TEST SUITES ===\033[0m
2026-04-01 11:29:38 - ==========================================
2026-04-01 11:29:38 - Running Test Suite: Subscription Limit Tests
2026-04-01 11:29:38 - Description: Subscription limit enforcement testing
2026-04-01 11:29:38 - ==========================================
=== Subscription Limit Test ===
[INFO] Testing relay at: ws://127.0.0.1:8888
[INFO] Note: This test assumes default subscription limits (max 25 per client)
=== Test 1: Basic Connectivity ===
[INFO] Testing basic WebSocket connection...
[PASS] Basic connectivity works
=== Test 2: Subscription Limit Enforcement ===
[INFO] Testing subscription limits by creating multiple subscriptions...
[INFO] Creating multiple subscriptions within a single connection...
[INFO] Hit subscription limit at subscription 2
[PASS] Subscription limit enforcement working (limit hit after 1 subscriptions)
=== Test Complete ===
2026-04-01 11:29:38 - \033[0;32m✓ Subscription Limit Tests PASSED\033[0m (Duration: 0s)
2026-04-01 11:29:38 - ==========================================
2026-04-01 11:29:38 - Running Test Suite: Load Testing
2026-04-01 11:29:38 - Description: High concurrent connection testing
2026-04-01 11:29:38 - ==========================================
==========================================
C-Relay Load Testing Suite
==========================================
Testing against relay at ws://127.0.0.1:8888
=== Basic Connectivity Test ===
✓ Relay is accessible
==========================================
Load Test: Light Load Test
Description: Basic load test with moderate concurrent connections
Concurrent clients: 10
Messages per client: 5
==========================================
Launching 10 clients...
All clients completed. Processing results...
=== Load Test Results ===
Test duration: 1s
Total connections attempted: 10
Successful connections: 10
Failed connections: 0
Connection success rate: 100%
Messages expected: 50
Messages sent: 50
Messages received: 100
✓ EXCELLENT: High connection success rate
Checking relay responsiveness... ✓ Relay is still responsive
==========================================
Load Test: Medium Load Test
Description: Moderate load test with higher concurrency
Concurrent clients: 25
Messages per client: 10
==========================================
Launching 25 clients...
All clients completed. Processing results...
=== Load Test Results ===
Test duration: 4s
Total connections attempted: 35
Successful connections: 25
Failed connections: 0
Connection success rate: 71%
Messages expected: 250
Messages sent: 250
Messages received: 500
✗ POOR: Low connection success rate
Checking relay responsiveness... ✓ Relay is still responsive
==========================================
Load Test: Heavy Load Test
Description: Heavy load test with high concurrency
Concurrent clients: 50
Messages per client: 20
==========================================
Launching 50 clients...
All clients completed. Processing results...
=== Load Test Results ===
Test duration: 14s
Total connections attempted: 85
Successful connections: 50
Failed connections: 0
Connection success rate: 58%
Messages expected: 1000
Messages sent: 1000
Messages received: 2000
✗ POOR: Low connection success rate
Checking relay responsiveness... ✓ Relay is still responsive
==========================================
Load Test: Stress Test
Description: Maximum load test to find breaking point
Concurrent clients: 100
Messages per client: 50
==========================================
Launching 100 clients...
All clients completed. Processing results...
=== Load Test Results ===
Test duration: 64s
Total connections attempted: 185
Successful connections: 100
Failed connections: 0
Connection success rate: 54%
Messages expected: 5000
Messages sent: 5000
Messages received: 7500
✗ POOR: Low connection success rate
Checking relay responsiveness... ✓ Relay is still responsive
==========================================
Load Testing Complete
==========================================
All load tests completed. Check individual test results above.
If any tests failed, the relay may need optimization or have resource limits.
2026-04-01 11:31:03 - \033[0;32m✓ Load Testing PASSED\033[0m (Duration: 85s)
2026-04-01 11:31:03 - ==========================================
2026-04-01 11:31:03 - Running Test Suite: Stress Testing
2026-04-01 11:31:03 - Description: Resource usage and stability testing
2026-04-01 11:31:03 - ==========================================
2026-04-01 11:31:03 - \033[0;31mERROR: Test script stress_tests.sh not found\033[0m
+183
View File
@@ -0,0 +1,183 @@
#!/bin/bash
# Thread-level CPU profiler for c-relay on remote server
# - Samples per-thread CPU every N seconds using /proc/<pid>/task/*/stat deltas
# - Optionally runs perf record in parallel for callgraph data
# - Pulls artifacts locally and generates a summary report
set -euo pipefail
REMOTE_HOST="${REMOTE_HOST:-ubuntu@laantungir.com}"
DURATION="${DURATION:-600}" # seconds (default 10 minutes)
INTERVAL="${INTERVAL:-10}" # seconds between samples
OUTDIR_BASE="${OUTDIR_BASE:-thread_profile_runs}"
RUN_TS="$(date -u +%Y%m%d_%H%M%S)"
RUN_DIR="${OUTDIR_BASE}/${RUN_TS}"
LOCAL_CSV="${RUN_DIR}/thread_samples.csv"
LOCAL_REPORT="${RUN_DIR}/thread_summary.txt"
REMOTE_DIR="/tmp/c_relay_thread_profile_${RUN_TS}"
ENABLE_PERF="${ENABLE_PERF:-1}" # 1=run perf, 0=skip
mkdir -p "${RUN_DIR}"
echo "=========================================="
echo "C-Relay Thread CPU Profiler"
echo "=========================================="
echo "Remote host : ${REMOTE_HOST}"
echo "Duration : ${DURATION}s"
echo "Interval : ${INTERVAL}s"
echo "Run dir : ${RUN_DIR}"
echo "Perf record : ${ENABLE_PERF}"
echo ""
ssh "${REMOTE_HOST}" "bash -s" <<EOF
set -euo pipefail
REMOTE_DIR="${REMOTE_DIR}"
DURATION="${DURATION}"
INTERVAL="${INTERVAL}"
ENABLE_PERF="${ENABLE_PERF}"
mkdir -p "\${REMOTE_DIR}"
CSV_FILE="\${REMOTE_DIR}/thread_samples.csv"
PERF_FILE="\${REMOTE_DIR}/perf.data"
LOG_FILE="\${REMOTE_DIR}/run.log"
PID=\$(pgrep -f '/usr/local/bin/c_relay/c_relay|c_relay' | head -1 || true)
if [ -z "\${PID}" ]; then
echo "ERROR: c_relay process not found (checked by cmdline pattern)" | tee -a "\${LOG_FILE}"
exit 1
fi
echo "Profiling PID: \${PID}" | tee -a "\${LOG_FILE}"
CLK_TCK=\$(getconf CLK_TCK)
echo "CLK_TCK=\${CLK_TCK}" | tee -a "\${LOG_FILE}"
echo "timestamp,tid,thread_name,cpu_pct,delta_ticks,total_ticks" > "\${CSV_FILE}"
# Start perf in background (if enabled and available)
PERF_PID=""
if [ "\${ENABLE_PERF}" = "1" ]; then
if command -v perf >/dev/null 2>&1; then
if sudo -n true >/dev/null 2>&1; then
echo "Starting perf record in background..." | tee -a "\${LOG_FILE}"
sudo perf record -g -p "\${PID}" -o "\${PERF_FILE}" -- sleep "\${DURATION}" >/dev/null 2>&1 &
PERF_PID=\$!
PERF_OWNER="\$(id -un)"
echo "perf_pid=\${PERF_PID}" | tee -a "\${LOG_FILE}"
else
echo "Skipping perf: sudo -n not available" | tee -a "\${LOG_FILE}"
fi
else
echo "Skipping perf: command not found" | tee -a "\${LOG_FILE}"
fi
fi
# Capture previous totals by tid
declare -A PREV_TOTAL
capture_totals() {
local pid="\$1"
for statf in /proc/\${pid}/task/*/stat; do
[ -f "\${statf}" ] || continue
local tid
tid=\${statf%/stat}
tid=\${tid##*/}
local total
total=\$(awk '{print \$14+\$15}' "\${statf}" 2>/dev/null || echo 0)
PREV_TOTAL[\${tid}]="\${total}"
done
}
capture_totals "\${PID}"
START_TS=\$(date +%s)
END_TS=\$((START_TS + DURATION))
while [ \$(date +%s) -lt \${END_TS} ]; do
NOW=\$(date +%s)
for statf in /proc/\${PID}/task/*/stat; do
[ -f "\${statf}" ] || continue
tid=\${statf%/stat}
tid=\${tid##*/}
total=\$(awk '{print \$14+\$15}' "\${statf}" 2>/dev/null || echo 0)
prev=\${PREV_TOTAL[\${tid}]:-\${total}}
delta=\$((total - prev))
if [ \${delta} -lt 0 ]; then
delta=0
fi
PREV_TOTAL[\${tid}]="\${total}"
name_file="/proc/\${PID}/task/\${tid}/comm"
if [ -f "\${name_file}" ]; then
tname=\$(tr -d '\n' < "\${name_file}")
else
tname="unknown"
fi
cpu_pct=\$(awk -v d="\${delta}" -v hz="\${CLK_TCK}" -v iv="\${INTERVAL}" 'BEGIN { printf "%.2f", (d / hz) * 100.0 / iv }')
echo "\${NOW},\${tid},\${tname},\${cpu_pct},\${delta},\${total}" >> "\${CSV_FILE}"
done
sleep "\${INTERVAL}"
done
if [ -n "\${PERF_PID}" ]; then
wait "\${PERF_PID}" || true
if [ -f "\${PERF_FILE}" ]; then
sudo perf report --stdio -i "\${PERF_FILE}" --sort=comm,symbol --no-children -n > "\${REMOTE_DIR}/perf_report.txt" 2>/dev/null || true
sudo chown "\${PERF_OWNER}:\${PERF_OWNER}" "\${PERF_FILE}" "\${REMOTE_DIR}/perf_report.txt" 2>/dev/null || true
fi
fi
echo "Done. Artifacts in \${REMOTE_DIR}" | tee -a "\${LOG_FILE}"
EOF
echo "Fetching artifacts..."
scp -q "${REMOTE_HOST}:${REMOTE_DIR}/thread_samples.csv" "${LOCAL_CSV}"
scp -q "${REMOTE_HOST}:${REMOTE_DIR}/run.log" "${RUN_DIR}/run.log" || true
scp -q "${REMOTE_HOST}:${REMOTE_DIR}/perf.data" "${RUN_DIR}/perf.data" || true
scp -q "${REMOTE_HOST}:${REMOTE_DIR}/perf_report.txt" "${RUN_DIR}/perf_report.txt" || true
echo "Generating summary..."
{
echo "=========================================="
echo "Thread CPU Summary (avg + max over run)"
echo "=========================================="
echo "Run timestamp (UTC): ${RUN_TS}"
echo "Duration: ${DURATION}s, Interval: ${INTERVAL}s"
echo ""
awk -F, '
NR==1 {next}
{
key=$2"|"$3;
cpu=$4+0;
sum[key]+=cpu;
cnt[key]++;
if (cpu > max[key]) max[key]=cpu;
}
END {
printf("%-10s %-18s %12s %12s\n", "TID", "THREAD", "AVG_CPU%", "MAX_CPU%");
for (k in sum) {
split(k, a, "|");
avg=sum[k]/cnt[k];
printf("%-10s %-18s %12.2f %12.2f\n", a[1], a[2], avg, max[k]);
}
}
' "${LOCAL_CSV}" | sort -k3,3nr
} | tee "${LOCAL_REPORT}"
echo ""
echo "Completed. Files:"
echo " ${LOCAL_CSV}"
echo " ${LOCAL_REPORT}"
if [ -f "${RUN_DIR}/perf_report.txt" ]; then
echo " ${RUN_DIR}/perf_report.txt"
fi
echo ""
echo "Top 20 summary:"
head -n 22 "${LOCAL_REPORT}"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,5 @@
Profiling PID: 2040140
CLK_TCK=100
Starting perf record in background...
perf_pid=2040808
Done. Artifacts in /tmp/c_relay_thread_profile_20260401_154500
@@ -0,0 +1,421 @@
timestamp,tid,thread_name,cpu_pct,delta_ticks,total_ticks
1775058301,2040140",unknown,0.00,0,3950
1775058301,2040150",unknown,0.00,0,9
1775058301,2040151",unknown,0.00,0,8
1775058301,2040152",unknown,0.00,0,11
1775058301,2040153",unknown,0.00,0,11
1775058301,2040154",unknown,0.00,0,0
1775058301,2040155",unknown,0.00,0,0
1775058311,2040140",unknown,62.10,621,4571
1775058311,2040150",unknown,0.20,2,11
1775058311,2040151",unknown,0.10,1,9
1775058311,2040152",unknown,0.10,1,12
1775058311,2040153",unknown,0.00,0,11
1775058311,2040154",unknown,0.00,0,0
1775058311,2040155",unknown,0.00,0,0
1775058321,2040140",unknown,65.20,652,5223
1775058321,2040150",unknown,0.20,2,13
1775058321,2040151",unknown,0.20,2,11
1775058321,2040152",unknown,0.30,3,15
1775058321,2040153",unknown,0.20,2,13
1775058321,2040154",unknown,0.00,0,0
1775058321,2040155",unknown,0.00,0,0
1775058331,2040140",unknown,83.80,838,6061
1775058331,2040150",unknown,0.00,0,13
1775058331,2040151",unknown,0.00,0,11
1775058331,2040152",unknown,0.00,0,15
1775058331,2040153",unknown,0.00,0,13
1775058331,2040154",unknown,0.00,0,0
1775058331,2040155",unknown,0.00,0,0
1775058341,2040140",unknown,100.20,1002,7063
1775058341,2040150",unknown,0.00,0,13
1775058341,2040151",unknown,0.00,0,11
1775058341,2040152",unknown,0.00,0,15
1775058341,2040153",unknown,0.00,0,13
1775058341,2040154",unknown,0.00,0,0
1775058341,2040155",unknown,0.00,0,0
1775058351,2040140",unknown,100.30,1003,8066
1775058351,2040150",unknown,0.00,0,13
1775058351,2040151",unknown,0.00,0,11
1775058351,2040152",unknown,0.10,1,16
1775058351,2040153",unknown,0.00,0,13
1775058351,2040154",unknown,0.00,0,0
1775058351,2040155",unknown,0.00,0,0
1775058361,2040140",unknown,89.50,895,8961
1775058361,2040150",unknown,0.10,1,14
1775058361,2040151",unknown,0.00,0,11
1775058361,2040152",unknown,0.00,0,16
1775058361,2040153",unknown,0.00,0,13
1775058361,2040154",unknown,0.00,0,0
1775058361,2040155",unknown,0.00,0,0
1775058371,2040140",unknown,21.20,212,9173
1775058371,2040150",unknown,0.00,0,14
1775058371,2040151",unknown,0.00,0,11
1775058371,2040152",unknown,0.00,0,16
1775058371,2040153",unknown,0.00,0,13
1775058371,2040154",unknown,0.00,0,0
1775058371,2040155",unknown,0.00,0,0
1775058381,2040140",unknown,36.40,364,9537
1775058381,2040150",unknown,0.10,1,15
1775058381,2040151",unknown,0.10,1,12
1775058381,2040152",unknown,0.10,1,17
1775058381,2040153",unknown,0.00,0,13
1775058381,2040154",unknown,0.00,0,0
1775058381,2040155",unknown,0.00,0,0
1775058391,2040140",unknown,35.30,353,9890
1775058391,2040150",unknown,0.00,0,15
1775058391,2040151",unknown,0.00,0,12
1775058391,2040152",unknown,0.00,0,17
1775058391,2040153",unknown,0.00,0,13
1775058391,2040154",unknown,0.00,0,0
1775058391,2040155",unknown,0.00,0,0
1775058401,2040140",unknown,42.80,428,10318
1775058401,2040150",unknown,0.30,3,18
1775058401,2040151",unknown,0.20,2,14
1775058401,2040152",unknown,0.30,3,20
1775058401,2040153",unknown,0.30,3,16
1775058401,2040154",unknown,0.00,0,0
1775058401,2040155",unknown,0.00,0,0
1775058411,2040140",unknown,73.50,735,11053
1775058411,2040150",unknown,0.40,4,22
1775058411,2040151",unknown,0.20,2,16
1775058411,2040152",unknown,0.40,4,24
1775058411,2040153",unknown,0.20,2,18
1775058411,2040154",unknown,0.00,0,0
1775058411,2040155",unknown,0.00,0,0
1775058421,2040140",unknown,33.60,336,11389
1775058421,2040150",unknown,0.00,0,22
1775058421,2040151",unknown,0.00,0,16
1775058421,2040152",unknown,0.00,0,24
1775058421,2040153",unknown,0.10,1,19
1775058421,2040154",unknown,0.00,0,0
1775058421,2040155",unknown,0.00,0,0
1775058431,2040140",unknown,57.20,572,11961
1775058431,2040150",unknown,0.00,0,22
1775058431,2040151",unknown,0.10,1,17
1775058431,2040152",unknown,0.10,1,25
1775058431,2040153",unknown,0.00,0,19
1775058431,2040154",unknown,0.00,0,0
1775058431,2040155",unknown,0.00,0,0
1775058441,2040140",unknown,96.70,967,12928
1775058441,2040150",unknown,0.00,0,22
1775058441,2040151",unknown,0.00,0,17
1775058441,2040152",unknown,0.00,0,25
1775058441,2040153",unknown,0.00,0,19
1775058441,2040154",unknown,0.00,0,0
1775058441,2040155",unknown,0.00,0,0
1775058451,2040140",unknown,100.30,1003,13931
1775058451,2040150",unknown,0.10,1,23
1775058451,2040151",unknown,0.10,1,18
1775058451,2040152",unknown,0.10,1,26
1775058451,2040153",unknown,0.00,0,19
1775058451,2040154",unknown,0.00,0,0
1775058451,2040155",unknown,0.00,0,0
1775058461,2040140",unknown,98.40,984,14915
1775058461,2040150",unknown,0.10,1,24
1775058461,2040151",unknown,0.30,3,21
1775058461,2040152",unknown,0.10,1,27
1775058461,2040153",unknown,0.20,2,21
1775058461,2040154",unknown,0.00,0,0
1775058461,2040155",unknown,0.00,0,0
1775058471,2040140",unknown,62.90,629,15544
1775058471,2040150",unknown,0.00,0,24
1775058471,2040151",unknown,0.20,2,23
1775058471,2040152",unknown,0.00,0,27
1775058471,2040153",unknown,0.10,1,22
1775058471,2040154",unknown,0.00,0,0
1775058471,2040155",unknown,0.20,2,2
1775058481,2040140",unknown,83.20,832,16376
1775058481,2040150",unknown,0.20,2,26
1775058481,2040151",unknown,0.00,0,23
1775058481,2040152",unknown,0.20,2,29
1775058481,2040153",unknown,0.00,0,22
1775058481,2040154",unknown,0.00,0,0
1775058481,2040155",unknown,0.00,0,2
1775058491,2040140",unknown,100.30,1003,17379
1775058491,2040150",unknown,0.00,0,26
1775058491,2040151",unknown,0.00,0,23
1775058491,2040152",unknown,0.00,0,29
1775058491,2040153",unknown,0.00,0,22
1775058491,2040154",unknown,0.00,0,0
1775058491,2040155",unknown,0.00,0,2
1775058501,2040140",unknown,63.30,633,18012
1775058501,2040150",unknown,0.10,1,27
1775058501,2040151",unknown,0.10,1,24
1775058501,2040152",unknown,0.30,3,32
1775058501,2040153",unknown,0.20,2,24
1775058501,2040154",unknown,0.00,0,0
1775058501,2040155",unknown,0.00,0,2
1775058511,2040140",unknown,57.40,574,18586
1775058511,2040150",unknown,0.00,0,27
1775058511,2040151",unknown,0.10,1,25
1775058511,2040152",unknown,0.00,0,32
1775058511,2040153",unknown,0.00,0,24
1775058511,2040154",unknown,0.00,0,0
1775058511,2040155",unknown,0.00,0,2
1775058521,2040140",unknown,18.70,187,18773
1775058521,2040150",unknown,0.10,1,28
1775058521,2040151",unknown,0.00,0,25
1775058521,2040152",unknown,0.00,0,32
1775058521,2040153",unknown,0.00,0,24
1775058521,2040154",unknown,0.00,0,0
1775058521,2040155",unknown,0.00,0,2
1775058531,2040140",unknown,66.20,662,19435
1775058531,2040150",unknown,0.00,0,28
1775058531,2040151",unknown,0.10,1,26
1775058531,2040152",unknown,0.00,0,32
1775058531,2040153",unknown,0.00,0,24
1775058531,2040154",unknown,0.00,0,0
1775058531,2040155",unknown,0.00,0,2
1775058541,2040140",unknown,89.10,891,20326
1775058541,2040150",unknown,0.00,0,28
1775058541,2040151",unknown,0.00,0,26
1775058541,2040152",unknown,0.10,1,33
1775058541,2040153",unknown,0.00,0,24
1775058541,2040154",unknown,0.00,0,0
1775058541,2040155",unknown,0.00,0,2
1775058551,2040140",unknown,38.10,381,20707
1775058551,2040150",unknown,0.10,1,29
1775058551,2040151",unknown,0.00,0,26
1775058551,2040152",unknown,0.00,0,33
1775058551,2040153",unknown,0.00,0,24
1775058551,2040154",unknown,0.00,0,0
1775058551,2040155",unknown,0.00,0,2
1775058562,2040140",unknown,69.20,692,21399
1775058562,2040150",unknown,0.00,0,29
1775058562,2040151",unknown,0.20,2,28
1775058562,2040152",unknown,0.00,0,33
1775058562,2040153",unknown,0.10,1,25
1775058562,2040154",unknown,0.00,0,0
1775058562,2040155",unknown,0.00,0,2
1775058572,2040140",unknown,92.80,928,22327
1775058572,2040150",unknown,0.00,0,29
1775058572,2040151",unknown,0.00,0,28
1775058572,2040152",unknown,0.00,0,33
1775058572,2040153",unknown,0.00,0,25
1775058572,2040154",unknown,0.00,0,0
1775058572,2040155",unknown,0.00,0,2
1775058582,2040140",unknown,91.30,913,23240
1775058582,2040150",unknown,0.10,1,30
1775058582,2040151",unknown,0.00,0,28
1775058582,2040152",unknown,0.10,1,34
1775058582,2040153",unknown,0.00,0,25
1775058582,2040154",unknown,0.00,0,0
1775058582,2040155",unknown,0.00,0,2
1775058592,2040140",unknown,94.60,946,24186
1775058592,2040150",unknown,0.00,0,30
1775058592,2040151",unknown,0.00,0,28
1775058592,2040152",unknown,0.10,1,35
1775058592,2040153",unknown,0.10,1,26
1775058592,2040154",unknown,0.00,0,0
1775058592,2040155",unknown,0.00,0,2
1775058602,2040140",unknown,45.50,455,24641
1775058602,2040150",unknown,0.00,0,30
1775058602,2040151",unknown,0.00,0,28
1775058602,2040152",unknown,0.00,0,35
1775058602,2040153",unknown,0.00,0,26
1775058602,2040154",unknown,0.00,0,0
1775058602,2040155",unknown,0.00,0,2
1775058612,2040140",unknown,64.50,645,25286
1775058612,2040150",unknown,0.00,0,30
1775058612,2040151",unknown,0.10,1,29
1775058612,2040152",unknown,0.00,0,35
1775058612,2040153",unknown,0.10,1,27
1775058612,2040154",unknown,0.00,0,0
1775058612,2040155",unknown,0.00,0,2
1775058622,2040140",unknown,21.10,211,25497
1775058622,2040150",unknown,0.00,0,30
1775058622,2040151",unknown,0.00,0,29
1775058622,2040152",unknown,0.00,0,35
1775058622,2040153",unknown,0.00,0,27
1775058622,2040154",unknown,0.00,0,0
1775058622,2040155",unknown,0.00,0,2
1775058632,2040140",unknown,26.60,266,25763
1775058632,2040150",unknown,0.20,2,32
1775058632,2040151",unknown,0.10,1,30
1775058632,2040152",unknown,0.00,0,35
1775058632,2040153",unknown,0.00,0,27
1775058632,2040154",unknown,0.10,1,1
1775058632,2040155",unknown,0.00,0,2
1775058642,2040140",unknown,51.80,518,26281
1775058642,2040150",unknown,0.00,0,32
1775058642,2040151",unknown,0.10,1,31
1775058642,2040152",unknown,0.00,0,35
1775058642,2040153",unknown,0.00,0,27
1775058642,2040154",unknown,0.00,0,1
1775058642,2040155",unknown,0.00,0,2
1775058652,2040140",unknown,28.40,284,26565
1775058652,2040150",unknown,0.00,0,32
1775058652,2040151",unknown,0.10,1,32
1775058652,2040152",unknown,0.10,1,36
1775058652,2040153",unknown,0.20,2,29
1775058652,2040154",unknown,0.00,0,1
1775058652,2040155",unknown,0.00,0,2
1775058662,2040140",unknown,14.30,143,26708
1775058662,2040150",unknown,0.20,2,34
1775058662,2040151",unknown,0.10,1,33
1775058662,2040152",unknown,0.40,4,40
1775058662,2040153",unknown,0.30,3,32
1775058662,2040154",unknown,0.00,0,1
1775058662,2040155",unknown,0.00,0,2
1775058672,2040140",unknown,69.20,692,27400
1775058672,2040150",unknown,0.20,2,36
1775058672,2040151",unknown,0.00,0,33
1775058672,2040152",unknown,0.20,2,42
1775058672,2040153",unknown,0.10,1,33
1775058672,2040154",unknown,0.00,0,1
1775058672,2040155",unknown,0.00,0,2
1775058682,2040140",unknown,71.10,711,28111
1775058682,2040150",unknown,0.30,3,39
1775058682,2040151",unknown,0.20,2,35
1775058682,2040152",unknown,0.00,0,42
1775058682,2040153",unknown,0.10,1,34
1775058682,2040154",unknown,0.00,0,1
1775058682,2040155",unknown,0.00,0,2
1775058692,2040140",unknown,99.40,994,29105
1775058692,2040150",unknown,0.00,0,39
1775058692,2040151",unknown,0.00,0,35
1775058692,2040152",unknown,0.00,0,42
1775058692,2040153",unknown,0.00,0,34
1775058692,2040154",unknown,0.00,0,1
1775058692,2040155",unknown,0.00,0,2
1775058702,2040140",unknown,30.60,306,29411
1775058702,2040150",unknown,0.10,1,40
1775058702,2040151",unknown,0.10,1,36
1775058702,2040152",unknown,0.20,2,44
1775058702,2040153",unknown,0.20,2,36
1775058702,2040154",unknown,0.00,0,1
1775058702,2040155",unknown,0.00,0,2
1775058712,2040140",unknown,14.70,147,29558
1775058712,2040150",unknown,0.00,0,40
1775058712,2040151",unknown,0.00,0,36
1775058712,2040152",unknown,0.10,1,45
1775058712,2040153",unknown,0.00,0,36
1775058712,2040154",unknown,0.10,1,2
1775058712,2040155",unknown,0.00,0,2
1775058722,2040140",unknown,14.20,142,29700
1775058722,2040150",unknown,0.00,0,40
1775058722,2040151",unknown,0.10,1,37
1775058722,2040152",unknown,0.10,1,46
1775058722,2040153",unknown,0.00,0,36
1775058722,2040154",unknown,0.00,0,2
1775058722,2040155",unknown,0.00,0,2
1775058732,2040140",unknown,29.20,292,29992
1775058732,2040150",unknown,0.10,1,41
1775058732,2040151",unknown,0.00,0,37
1775058732,2040152",unknown,0.00,0,46
1775058732,2040153",unknown,0.00,0,36
1775058732,2040154",unknown,0.00,0,2
1775058732,2040155",unknown,0.00,0,2
1775058742,2040140",unknown,29.80,298,30290
1775058742,2040150",unknown,0.00,0,41
1775058742,2040151",unknown,0.00,0,37
1775058742,2040152",unknown,0.10,1,47
1775058742,2040153",unknown,0.00,0,36
1775058742,2040154",unknown,0.00,0,2
1775058742,2040155",unknown,0.00,0,2
1775058752,2040140",unknown,48.20,482,30772
1775058752,2040150",unknown,0.00,0,41
1775058752,2040151",unknown,0.00,0,37
1775058752,2040152",unknown,0.00,0,47
1775058752,2040153",unknown,0.10,1,37
1775058752,2040154",unknown,0.00,0,2
1775058752,2040155",unknown,0.00,0,2
1775058762,2040140",unknown,28.10,281,31053
1775058762,2040150",unknown,0.00,0,41
1775058762,2040151",unknown,0.00,0,37
1775058762,2040152",unknown,0.00,0,47
1775058762,2040153",unknown,0.00,0,37
1775058762,2040154",unknown,0.00,0,2
1775058762,2040155",unknown,0.00,0,2
1775058772,2040140",unknown,40.80,408,31461
1775058772,2040150",unknown,0.10,1,42
1775058772,2040151",unknown,0.10,1,38
1775058772,2040152",unknown,0.10,1,48
1775058772,2040153",unknown,0.10,1,38
1775058772,2040154",unknown,0.00,0,2
1775058772,2040155",unknown,0.00,0,2
1775058782,2040140",unknown,98.90,989,32450
1775058782,2040150",unknown,0.30,3,45
1775058782,2040151",unknown,0.00,0,38
1775058782,2040152",unknown,0.30,3,51
1775058782,2040153",unknown,0.10,1,39
1775058782,2040154",unknown,0.00,0,2
1775058782,2040155",unknown,0.10,1,3
1775058792,2040140",unknown,59.50,595,33045
1775058792,2040150",unknown,0.20,2,47
1775058792,2040151",unknown,0.10,1,39
1775058792,2040152",unknown,0.10,1,52
1775058792,2040153",unknown,0.10,1,40
1775058792,2040154",unknown,0.00,0,2
1775058792,2040155",unknown,0.00,0,3
1775058802,2040140",unknown,47.10,471,33516
1775058802,2040150",unknown,0.00,0,47
1775058802,2040151",unknown,0.00,0,39
1775058802,2040152",unknown,0.00,0,52
1775058802,2040153",unknown,0.10,1,41
1775058802,2040154",unknown,0.00,0,2
1775058802,2040155",unknown,0.10,1,4
1775058812,2040140",unknown,50.60,506,34022
1775058812,2040150",unknown,0.00,0,47
1775058812,2040151",unknown,0.00,0,39
1775058812,2040152",unknown,0.10,1,53
1775058812,2040153",unknown,0.00,0,41
1775058812,2040154",unknown,0.00,0,2
1775058812,2040155",unknown,0.10,1,5
1775058822,2040140",unknown,28.10,281,34303
1775058822,2040150",unknown,0.00,0,47
1775058822,2040151",unknown,0.00,0,39
1775058822,2040152",unknown,0.00,0,53
1775058822,2040153",unknown,0.00,0,41
1775058822,2040154",unknown,0.00,0,2
1775058822,2040155",unknown,0.00,0,5
1775058832,2040140",unknown,75.30,753,35056
1775058832,2040150",unknown,0.10,1,48
1775058832,2040151",unknown,0.10,1,40
1775058832,2040152",unknown,0.10,1,54
1775058832,2040153",unknown,0.10,1,42
1775058832,2040154",unknown,0.00,0,2
1775058832,2040155",unknown,0.00,0,5
1775058842,2040140",unknown,37.20,372,35428
1775058842,2040150",unknown,0.00,0,48
1775058842,2040151",unknown,0.00,0,40
1775058842,2040152",unknown,0.00,0,54
1775058842,2040153",unknown,0.00,0,42
1775058842,2040154",unknown,0.00,0,2
1775058842,2040155",unknown,0.00,0,5
1775058852,2040140",unknown,27.00,270,35698
1775058852,2040150",unknown,0.20,2,50
1775058852,2040151",unknown,0.20,2,42
1775058852,2040152",unknown,0.30,3,57
1775058852,2040153",unknown,0.20,2,44
1775058852,2040154",unknown,0.00,0,2
1775058852,2040155",unknown,0.00,0,5
1775058862,2040140",unknown,46.30,463,36161
1775058862,2040150",unknown,0.00,0,50
1775058862,2040151",unknown,0.10,1,43
1775058862,2040152",unknown,0.20,2,59
1775058862,2040153",unknown,0.00,0,44
1775058862,2040154",unknown,0.00,0,2
1775058862,2040155",unknown,0.00,0,5
1775058872,2040140",unknown,14.00,140,36301
1775058872,2040150",unknown,0.00,0,50
1775058872,2040151",unknown,0.00,0,43
1775058872,2040152",unknown,0.00,0,59
1775058872,2040153",unknown,0.00,0,44
1775058872,2040154",unknown,0.00,0,2
1775058872,2040155",unknown,0.00,0,5
1775058882,2040140",unknown,56.20,562,36863
1775058882,2040150",unknown,0.00,0,50
1775058882,2040151",unknown,0.10,1,44
1775058882,2040152",unknown,0.10,1,60
1775058882,2040153",unknown,0.00,0,44
1775058882,2040154",unknown,0.00,0,2
1775058882,2040155",unknown,0.00,0,5
1775058893,2040140",unknown,35.60,356,37219
1775058893,2040150",unknown,0.20,2,52
1775058893,2040151",unknown,0.10,1,45
1775058893,2040152",unknown,0.10,1,61
1775058893,2040153",unknown,0.10,1,45
1775058893,2040154",unknown,0.00,0,2
1775058893,2040155",unknown,0.00,0,5
Can't render this file because it contains an unexpected character in line 2 and column 19.
@@ -0,0 +1,14 @@
==========================================
Thread CPU Summary (avg + max over run)
==========================================
Run timestamp (UTC): 20260401_154500
Duration: 600s, Interval: 10s
2040140" unknown 55.45 100.30
2040152" unknown 0.08 0.40
2040150" unknown 0.07 0.40
2040151" unknown 0.06 0.30
2040153" unknown 0.06 0.30
2040155" unknown 0.01 0.20
2040154" unknown 0.00 0.10
TID THREAD AVG_CPU% MAX_CPU%
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,5 @@
Profiling PID: 2040140
CLK_TCK=100
Starting perf record in background...
perf_pid=2046339
Done. Artifacts in /tmp/c_relay_thread_profile_20260401_155603
@@ -0,0 +1,372 @@
timestamp,tid,thread_name,cpu_pct,delta_ticks,total_ticks
1775058964,2040140,lws-main,0.00,0,42849
1775058964,2040150,db-read-1,0.00,0,58
1775058964,2040151,db-read-2,0.00,0,54
1775058964,2040152,db-read-3,0.10,1,68
1775058964,2040153,db-read-4,0.00,0,53
1775058964,2040154,db-write,0.00,0,2
1775058964,2040155,event-worker,0.00,0,5
1775058974,2040140,lws-main,59.30,593,43442
1775058974,2040150,db-read-1,0.00,0,58
1775058974,2040151,db-read-2,0.00,0,54
1775058974,2040152,db-read-3,0.10,1,69
1775058974,2040153,db-read-4,0.00,0,53
1775058974,2040154,db-write,0.00,0,2
1775058974,2040155,event-worker,0.00,0,5
1775058984,2040140,lws-main,67.40,674,44116
1775058984,2040150,db-read-1,0.00,0,58
1775058984,2040151,db-read-2,0.00,0,54
1775058984,2040152,db-read-3,0.00,0,69
1775058984,2040153,db-read-4,0.10,1,54
1775058984,2040154,db-write,0.00,0,2
1775058984,2040155,event-worker,0.00,0,5
1775058994,2040140,lws-main,98.70,987,45103
1775058994,2040150,db-read-1,0.10,1,59
1775058994,2040151,db-read-2,0.00,0,54
1775058994,2040152,db-read-3,0.00,0,69
1775058994,2040153,db-read-4,0.00,0,54
1775058994,2040154,db-write,0.00,0,2
1775058994,2040155,event-worker,0.00,0,5
1775059004,2040140,lws-main,71.00,710,45813
1775059004,2040150,db-read-1,0.00,0,59
1775059004,2040151,db-read-2,0.00,0,54
1775059004,2040152,db-read-3,0.10,1,70
1775059004,2040153,db-read-4,0.10,1,55
1775059004,2040154,db-write,0.00,0,2
1775059004,2040155,event-worker,0.00,0,5
1775059014,2040140,lws-main,14.70,147,45960
1775059014,2040150,db-read-1,0.10,1,60
1775059014,2040151,db-read-2,0.30,3,57
1775059014,2040152,db-read-3,0.10,1,71
1775059014,2040153,db-read-4,0.10,1,56
1775059014,2040154,db-write,0.00,0,2
1775059014,2040155,event-worker,0.00,0,5
1775059024,2040140,lws-main,47.70,477,46437
1775059024,2040150,db-read-1,0.00,0,60
1775059024,2040151,db-read-2,0.00,0,57
1775059024,2040152,db-read-3,0.00,0,71
1775059024,2040153,db-read-4,0.00,0,56
1775059024,2040154,db-write,0.00,0,2
1775059024,2040155,event-worker,0.00,0,5
1775059034,2040140,lws-main,99.50,995,47432
1775059034,2040150,db-read-1,0.10,1,61
1775059034,2040151,db-read-2,0.10,1,58
1775059034,2040152,db-read-3,0.10,1,72
1775059034,2040153,db-read-4,0.00,0,56
1775059034,2040154,db-write,0.10,1,3
1775059034,2040155,event-worker,0.00,0,5
1775059044,2040140,lws-main,88.50,885,48317
1775059044,2040150,db-read-1,0.20,2,63
1775059044,2040151,db-read-2,0.10,1,59
1775059044,2040152,db-read-3,0.10,1,73
1775059044,2040153,db-read-4,0.20,2,58
1775059044,2040154,db-write,0.00,0,3
1775059044,2040155,event-worker,0.00,0,5
1775059054,2040140,lws-main,67.70,677,48994
1775059054,2040150,db-read-1,0.00,0,63
1775059054,2040151,db-read-2,0.00,0,59
1775059054,2040152,db-read-3,0.00,0,73
1775059054,2040153,db-read-4,0.00,0,58
1775059054,2040154,db-write,0.00,0,3
1775059054,2040155,event-worker,0.00,0,5
1775059064,2040140,lws-main,53.10,531,49525
1775059064,2040150,db-read-1,0.40,4,67
1775059064,2040151,db-read-2,0.40,4,63
1775059064,2040152,db-read-3,0.20,2,75
1775059064,2040153,db-read-4,0.00,0,58
1775059064,2040154,db-write,0.00,0,3
1775059064,2040155,event-worker,0.00,0,5
1775059074,2040140,lws-main,61.60,616,50141
1775059074,2040150,db-read-1,0.30,3,70
1775059074,2040151,db-read-2,0.20,2,65
1775059074,2040152,db-read-3,0.10,1,76
1775059074,2040153,db-read-4,0.20,2,60
1775059074,2040154,db-write,0.00,0,3
1775059074,2040155,event-worker,0.00,0,5
1775059084,2040140,lws-main,96.20,962,51103
1775059084,2040150,db-read-1,0.00,0,70
1775059084,2040151,db-read-2,0.20,2,67
1775059084,2040152,db-read-3,0.40,4,80
1775059084,2040153,db-read-4,0.10,1,61
1775059084,2040154,db-write,0.10,1,4
1775059084,2040155,event-worker,0.00,0,5
1775059094,2040140,lws-main,53.60,536,51639
1775059094,2040150,db-read-1,0.10,1,71
1775059094,2040151,db-read-2,0.20,2,69
1775059094,2040152,db-read-3,0.00,0,80
1775059094,2040153,db-read-4,0.00,0,61
1775059094,2040154,db-write,0.00,0,4
1775059094,2040155,event-worker,0.00,0,5
1775059105,2040140,lws-main,51.40,514,52153
1775059105,2040150,db-read-1,0.10,1,72
1775059105,2040151,db-read-2,0.00,0,69
1775059105,2040152,db-read-3,0.00,0,80
1775059105,2040153,db-read-4,0.10,1,62
1775059105,2040154,db-write,0.00,0,4
1775059105,2040155,event-worker,0.00,0,5
1775059115,2040140,lws-main,78.50,785,52938
1775059115,2040150,db-read-1,0.00,0,72
1775059115,2040151,db-read-2,0.00,0,69
1775059115,2040152,db-read-3,0.00,0,80
1775059115,2040153,db-read-4,0.00,0,62
1775059115,2040154,db-write,0.00,0,4
1775059115,2040155,event-worker,0.00,0,5
1775059125,2040140,lws-main,40.70,407,53345
1775059125,2040150,db-read-1,0.00,0,72
1775059125,2040151,db-read-2,0.00,0,69
1775059125,2040152,db-read-3,0.10,1,81
1775059125,2040153,db-read-4,0.10,1,63
1775059125,2040154,db-write,0.00,0,4
1775059125,2040155,event-worker,0.00,0,5
1775059135,2040140,lws-main,41.00,410,53755
1775059135,2040150,db-read-1,0.00,0,72
1775059135,2040151,db-read-2,0.00,0,69
1775059135,2040152,db-read-3,0.00,0,81
1775059135,2040153,db-read-4,0.00,0,63
1775059135,2040154,db-write,0.00,0,4
1775059135,2040155,event-worker,0.00,0,5
1775059145,2040140,lws-main,58.70,587,54342
1775059145,2040150,db-read-1,0.00,0,72
1775059145,2040151,db-read-2,0.00,0,69
1775059145,2040152,db-read-3,0.00,0,81
1775059145,2040153,db-read-4,0.00,0,63
1775059145,2040154,db-write,0.00,0,4
1775059145,2040155,event-worker,0.00,0,5
1775059155,2040140,lws-main,28.40,284,54626
1775059155,2040150,db-read-1,0.20,2,74
1775059155,2040151,db-read-2,0.20,2,71
1775059155,2040152,db-read-3,0.10,1,82
1775059155,2040153,db-read-4,0.10,1,64
1775059155,2040154,db-write,0.00,0,4
1775059155,2040155,event-worker,0.00,0,5
1775059165,2040140,lws-main,89.80,898,55524
1775059165,2040150,db-read-1,0.30,3,77
1775059165,2040151,db-read-2,0.20,2,73
1775059165,2040152,db-read-3,0.30,3,85
1775059165,2040153,db-read-4,0.10,1,65
1775059165,2040154,db-write,0.00,0,4
1775059165,2040155,event-worker,0.00,0,5
1775059175,2040140,lws-main,100.20,1002,56526
1775059175,2040150,db-read-1,0.00,0,77
1775059175,2040151,db-read-2,0.00,0,73
1775059175,2040152,db-read-3,0.10,1,86
1775059175,2040153,db-read-4,0.20,2,67
1775059175,2040154,db-write,0.00,0,4
1775059175,2040155,event-worker,0.00,0,5
1775059185,2040140,lws-main,100.20,1002,57528
1775059185,2040150,db-read-1,0.20,2,79
1775059185,2040151,db-read-2,0.20,2,75
1775059185,2040152,db-read-3,0.10,1,87
1775059185,2040153,db-read-4,0.20,2,69
1775059185,2040154,db-write,0.00,0,4
1775059185,2040155,event-worker,0.00,0,5
1775059195,2040140,lws-main,75.70,757,58285
1775059195,2040150,db-read-1,0.20,2,81
1775059195,2040151,db-read-2,0.20,2,77
1775059195,2040152,db-read-3,0.20,2,89
1775059195,2040153,db-read-4,0.20,2,71
1775059195,2040154,db-write,0.00,0,4
1775059195,2040155,event-worker,0.00,0,5
1775059205,2040140,lws-main,77.70,777,59062
1775059205,2040150,db-read-1,0.20,2,83
1775059205,2040151,db-read-2,0.30,3,80
1775059205,2040152,db-read-3,0.50,5,94
1775059205,2040153,db-read-4,0.30,3,74
1775059205,2040154,db-write,0.00,0,4
1775059205,2040155,event-worker,0.10,1,6
1775059215,2040140,lws-main,99.60,996,60058
1775059215,2040150,db-read-1,0.40,4,87
1775059215,2040151,db-read-2,0.40,4,84
1775059215,2040152,db-read-3,0.10,1,95
1775059215,2040153,db-read-4,0.20,2,76
1775059215,2040154,db-write,0.00,0,4
1775059215,2040155,event-worker,0.00,0,6
1775059225,2040140,lws-main,100.30,1003,61061
1775059225,2040150,db-read-1,0.00,0,87
1775059225,2040151,db-read-2,0.10,1,85
1775059225,2040152,db-read-3,0.10,1,96
1775059225,2040153,db-read-4,0.20,2,78
1775059225,2040154,db-write,0.00,0,4
1775059225,2040155,event-worker,0.00,0,6
1775059235,2040140,lws-main,100.10,1001,62062
1775059235,2040150,db-read-1,0.10,1,88
1775059235,2040151,db-read-2,0.00,0,85
1775059235,2040152,db-read-3,0.00,0,96
1775059235,2040153,db-read-4,0.00,0,78
1775059235,2040154,db-write,0.00,0,4
1775059235,2040155,event-worker,0.00,0,6
1775059245,2040140,lws-main,98.10,981,63043
1775059245,2040150,db-read-1,0.10,1,89
1775059245,2040151,db-read-2,0.00,0,85
1775059245,2040152,db-read-3,0.20,2,98
1775059245,2040153,db-read-4,0.10,1,79
1775059245,2040154,db-write,0.00,0,4
1775059245,2040155,event-worker,0.00,0,6
1775059255,2040140,lws-main,54.10,541,63584
1775059255,2040150,db-read-1,0.20,2,91
1775059255,2040151,db-read-2,0.50,5,90
1775059255,2040152,db-read-3,0.50,5,103
1775059255,2040153,db-read-4,0.50,5,84
1775059255,2040154,db-write,0.00,0,4
1775059255,2040155,event-worker,0.00,0,6
1775059265,2040140,lws-main,82.80,828,64412
1775059265,2040150,db-read-1,0.00,0,91
1775059265,2040151,db-read-2,0.00,0,90
1775059265,2040152,db-read-3,0.00,0,103
1775059265,2040153,db-read-4,0.00,0,84
1775059265,2040154,db-write,0.00,0,4
1775059265,2040155,event-worker,0.00,0,6
1775059275,2040140,lws-main,99.90,999,65411
1775059275,2040150,db-read-1,0.10,1,92
1775059275,2040151,db-read-2,0.00,0,90
1775059275,2040152,db-read-3,0.00,0,103
1775059275,2040153,db-read-4,0.00,0,84
1775059275,2040154,db-write,0.00,0,4
1775059275,2040155,event-worker,0.00,0,6
1775059285,2040140,lws-main,58.30,583,65994
1775059285,2040150,db-read-1,0.00,0,92
1775059285,2040151,db-read-2,0.10,1,91
1775059285,2040152,db-read-3,0.10,1,104
1775059285,2040153,db-read-4,0.00,0,84
1775059285,2040154,db-write,0.00,0,4
1775059285,2040155,event-worker,0.00,0,6
1775059295,2040140,lws-main,42.60,426,66420
1775059295,2040150,db-read-1,0.30,3,95
1775059295,2040151,db-read-2,0.10,1,92
1775059295,2040152,db-read-3,0.10,1,105
1775059295,2040153,db-read-4,0.20,2,86
1775059295,2040154,db-write,0.00,0,4
1775059295,2040155,event-worker,0.00,0,6
1775059305,2040140,lws-main,16.20,162,66582
1775059305,2040150,db-read-1,0.20,2,97
1775059305,2040151,db-read-2,0.00,0,92
1775059305,2040152,db-read-3,0.10,1,106
1775059305,2040153,db-read-4,0.10,1,87
1775059305,2040154,db-write,0.00,0,4
1775059305,2040155,event-worker,0.00,0,6
1775059315,2040140,lws-main,100.30,1003,67585
1775059315,2040150,db-read-1,0.00,0,97
1775059315,2040151,db-read-2,0.00,0,92
1775059315,2040152,db-read-3,0.00,0,106
1775059315,2040153,db-read-4,0.00,0,87
1775059315,2040154,db-write,0.00,0,4
1775059315,2040155,event-worker,0.00,0,6
1775059325,2040140,lws-main,66.00,660,68245
1775059325,2040150,db-read-1,0.20,2,99
1775059325,2040151,db-read-2,0.50,5,97
1775059325,2040152,db-read-3,0.20,2,108
1775059325,2040153,db-read-4,0.20,2,89
1775059325,2040154,db-write,0.00,0,4
1775059325,2040155,event-worker,0.00,0,6
1775059335,2040140,lws-main,21.70,217,68462
1775059335,2040150,db-read-1,0.00,0,99
1775059335,2040151,db-read-2,0.00,0,97
1775059335,2040152,db-read-3,0.00,0,108
1775059335,2040153,db-read-4,0.00,0,89
1775059335,2040154,db-write,0.00,0,4
1775059335,2040155,event-worker,0.00,0,6
1775059345,2040140,lws-main,47.00,470,68932
1775059345,2040150,db-read-1,0.30,3,102
1775059345,2040151,db-read-2,0.30,3,100
1775059345,2040152,db-read-3,0.90,9,117
1775059345,2040153,db-read-4,0.20,2,91
1775059345,2040154,db-write,0.00,0,4
1775059345,2040155,event-worker,0.00,0,6
1775059355,2040140,lws-main,54.40,544,69476
1775059355,2040150,db-read-1,0.00,0,102
1775059355,2040151,db-read-2,0.00,0,100
1775059355,2040152,db-read-3,0.00,0,117
1775059355,2040153,db-read-4,0.00,0,91
1775059355,2040154,db-write,0.00,0,4
1775059355,2040155,event-worker,0.00,0,6
1775059365,2040140,lws-main,99.70,997,70473
1775059365,2040150,db-read-1,0.10,1,103
1775059365,2040151,db-read-2,0.10,1,101
1775059365,2040152,db-read-3,0.00,0,117
1775059365,2040153,db-read-4,0.00,0,91
1775059365,2040154,db-write,0.00,0,4
1775059365,2040155,event-worker,0.00,0,6
1775059375,2040140,lws-main,46.20,462,70935
1775059375,2040150,db-read-1,0.20,2,105
1775059375,2040151,db-read-2,0.10,1,102
1775059375,2040152,db-read-3,0.40,4,121
1775059375,2040153,db-read-4,0.20,2,93
1775059375,2040154,db-write,0.00,0,4
1775059375,2040155,event-worker,0.00,0,6
1775059385,2040140,lws-main,69.00,690,71625
1775059385,2040150,db-read-1,0.10,1,106
1775059385,2040151,db-read-2,0.00,0,102
1775059385,2040152,db-read-3,0.10,1,122
1775059385,2040153,db-read-4,0.10,1,94
1775059385,2040154,db-write,0.00,0,4
1775059385,2040155,event-worker,0.00,0,6
1775059395,2040140,lws-main,71.50,715,72340
1775059395,2040150,db-read-1,0.00,0,106
1775059395,2040151,db-read-2,0.20,2,104
1775059395,2040152,db-read-3,0.10,1,123
1775059395,2040153,db-read-4,0.30,3,97
1775059395,2040154,db-write,0.00,0,4
1775059395,2040155,event-worker,0.00,0,6
1775059405,2040140,lws-main,100.10,1001,73341
1775059405,2040150,db-read-1,0.20,2,108
1775059405,2040151,db-read-2,0.00,0,104
1775059405,2040152,db-read-3,0.00,0,123
1775059405,2040153,db-read-4,0.20,2,99
1775059405,2040154,db-write,0.00,0,4
1775059405,2040155,event-worker,0.00,0,6
1775059415,2040140,lws-main,100.20,1002,74343
1775059415,2040150,db-read-1,0.30,3,111
1775059415,2040151,db-read-2,0.20,2,106
1775059415,2040152,db-read-3,0.20,2,125
1775059415,2040153,db-read-4,0.30,3,102
1775059415,2040154,db-write,0.10,1,5
1775059415,2040155,event-worker,0.00,0,6
1775059425,2040140,lws-main,97.40,974,75317
1775059425,2040150,db-read-1,0.10,1,112
1775059425,2040151,db-read-2,0.10,1,107
1775059425,2040152,db-read-3,0.30,3,128
1775059425,2040153,db-read-4,0.10,1,103
1775059425,2040154,db-write,0.00,0,5
1775059425,2040155,event-worker,0.00,0,6
1775059435,2040140,lws-main,80.60,806,76123
1775059435,2040150,db-read-1,0.10,1,113
1775059435,2040151,db-read-2,0.20,2,109
1775059435,2040152,db-read-3,0.10,1,129
1775059435,2040153,db-read-4,0.10,1,104
1775059435,2040154,db-write,0.00,0,5
1775059435,2040155,event-worker,0.00,0,6
1775059445,2040140,lws-main,40.70,407,76530
1775059445,2040150,db-read-1,0.10,1,114
1775059445,2040151,db-read-2,0.00,0,109
1775059445,2040152,db-read-3,0.20,2,131
1775059445,2040153,db-read-4,0.10,1,105
1775059445,2040154,db-write,0.00,0,5
1775059445,2040155,event-worker,0.00,0,6
1775059456,2040140,lws-main,56.00,560,77090
1775059456,2040150,db-read-1,0.00,0,114
1775059456,2040151,db-read-2,0.20,2,111
1775059456,2040152,db-read-3,0.00,0,131
1775059456,2040153,db-read-4,0.10,1,106
1775059456,2040154,db-write,0.00,0,5
1775059456,2040155,event-worker,0.00,0,6
1775059466,2040140,lws-main,53.20,532,77622
1775059466,2040150,db-read-1,0.00,0,114
1775059466,2040151,db-read-2,0.00,0,111
1775059466,2040152,db-read-3,0.00,0,131
1775059466,2040153,db-read-4,0.00,0,106
1775059466,2040154,db-write,0.00,0,5
1775059466,2040155,event-worker,0.00,0,6
1775059476,2040140,lws-main,96.70,967,78589
1775059476,2040150,db-read-1,0.00,0,114
1775059476,2040151,db-read-2,0.00,0,111
1775059476,2040152,db-read-3,0.00,0,131
1775059476,2040153,db-read-4,0.00,0,106
1775059476,2040154,db-write,0.00,0,5
1775059476,2040155,event-worker,0.00,0,6
1775059486,2040140,lws-main,67.60,676,79265
1775059486,2040150,db-read-1,0.20,2,116
1775059486,2040151,db-read-2,0.00,0,111
1775059486,2040152,db-read-3,0.00,0,131
1775059486,2040153,db-read-4,0.00,0,106
1775059486,2040154,db-write,0.00,0,5
1775059486,2040155,event-worker,0.00,0,6
1 timestamp tid thread_name cpu_pct delta_ticks total_ticks
2 1775058964 2040140 lws-main 0.00 0 42849
3 1775058964 2040150 db-read-1 0.00 0 58
4 1775058964 2040151 db-read-2 0.00 0 54
5 1775058964 2040152 db-read-3 0.10 1 68
6 1775058964 2040153 db-read-4 0.00 0 53
7 1775058964 2040154 db-write 0.00 0 2
8 1775058964 2040155 event-worker 0.00 0 5
9 1775058974 2040140 lws-main 59.30 593 43442
10 1775058974 2040150 db-read-1 0.00 0 58
11 1775058974 2040151 db-read-2 0.00 0 54
12 1775058974 2040152 db-read-3 0.10 1 69
13 1775058974 2040153 db-read-4 0.00 0 53
14 1775058974 2040154 db-write 0.00 0 2
15 1775058974 2040155 event-worker 0.00 0 5
16 1775058984 2040140 lws-main 67.40 674 44116
17 1775058984 2040150 db-read-1 0.00 0 58
18 1775058984 2040151 db-read-2 0.00 0 54
19 1775058984 2040152 db-read-3 0.00 0 69
20 1775058984 2040153 db-read-4 0.10 1 54
21 1775058984 2040154 db-write 0.00 0 2
22 1775058984 2040155 event-worker 0.00 0 5
23 1775058994 2040140 lws-main 98.70 987 45103
24 1775058994 2040150 db-read-1 0.10 1 59
25 1775058994 2040151 db-read-2 0.00 0 54
26 1775058994 2040152 db-read-3 0.00 0 69
27 1775058994 2040153 db-read-4 0.00 0 54
28 1775058994 2040154 db-write 0.00 0 2
29 1775058994 2040155 event-worker 0.00 0 5
30 1775059004 2040140 lws-main 71.00 710 45813
31 1775059004 2040150 db-read-1 0.00 0 59
32 1775059004 2040151 db-read-2 0.00 0 54
33 1775059004 2040152 db-read-3 0.10 1 70
34 1775059004 2040153 db-read-4 0.10 1 55
35 1775059004 2040154 db-write 0.00 0 2
36 1775059004 2040155 event-worker 0.00 0 5
37 1775059014 2040140 lws-main 14.70 147 45960
38 1775059014 2040150 db-read-1 0.10 1 60
39 1775059014 2040151 db-read-2 0.30 3 57
40 1775059014 2040152 db-read-3 0.10 1 71
41 1775059014 2040153 db-read-4 0.10 1 56
42 1775059014 2040154 db-write 0.00 0 2
43 1775059014 2040155 event-worker 0.00 0 5
44 1775059024 2040140 lws-main 47.70 477 46437
45 1775059024 2040150 db-read-1 0.00 0 60
46 1775059024 2040151 db-read-2 0.00 0 57
47 1775059024 2040152 db-read-3 0.00 0 71
48 1775059024 2040153 db-read-4 0.00 0 56
49 1775059024 2040154 db-write 0.00 0 2
50 1775059024 2040155 event-worker 0.00 0 5
51 1775059034 2040140 lws-main 99.50 995 47432
52 1775059034 2040150 db-read-1 0.10 1 61
53 1775059034 2040151 db-read-2 0.10 1 58
54 1775059034 2040152 db-read-3 0.10 1 72
55 1775059034 2040153 db-read-4 0.00 0 56
56 1775059034 2040154 db-write 0.10 1 3
57 1775059034 2040155 event-worker 0.00 0 5
58 1775059044 2040140 lws-main 88.50 885 48317
59 1775059044 2040150 db-read-1 0.20 2 63
60 1775059044 2040151 db-read-2 0.10 1 59
61 1775059044 2040152 db-read-3 0.10 1 73
62 1775059044 2040153 db-read-4 0.20 2 58
63 1775059044 2040154 db-write 0.00 0 3
64 1775059044 2040155 event-worker 0.00 0 5
65 1775059054 2040140 lws-main 67.70 677 48994
66 1775059054 2040150 db-read-1 0.00 0 63
67 1775059054 2040151 db-read-2 0.00 0 59
68 1775059054 2040152 db-read-3 0.00 0 73
69 1775059054 2040153 db-read-4 0.00 0 58
70 1775059054 2040154 db-write 0.00 0 3
71 1775059054 2040155 event-worker 0.00 0 5
72 1775059064 2040140 lws-main 53.10 531 49525
73 1775059064 2040150 db-read-1 0.40 4 67
74 1775059064 2040151 db-read-2 0.40 4 63
75 1775059064 2040152 db-read-3 0.20 2 75
76 1775059064 2040153 db-read-4 0.00 0 58
77 1775059064 2040154 db-write 0.00 0 3
78 1775059064 2040155 event-worker 0.00 0 5
79 1775059074 2040140 lws-main 61.60 616 50141
80 1775059074 2040150 db-read-1 0.30 3 70
81 1775059074 2040151 db-read-2 0.20 2 65
82 1775059074 2040152 db-read-3 0.10 1 76
83 1775059074 2040153 db-read-4 0.20 2 60
84 1775059074 2040154 db-write 0.00 0 3
85 1775059074 2040155 event-worker 0.00 0 5
86 1775059084 2040140 lws-main 96.20 962 51103
87 1775059084 2040150 db-read-1 0.00 0 70
88 1775059084 2040151 db-read-2 0.20 2 67
89 1775059084 2040152 db-read-3 0.40 4 80
90 1775059084 2040153 db-read-4 0.10 1 61
91 1775059084 2040154 db-write 0.10 1 4
92 1775059084 2040155 event-worker 0.00 0 5
93 1775059094 2040140 lws-main 53.60 536 51639
94 1775059094 2040150 db-read-1 0.10 1 71
95 1775059094 2040151 db-read-2 0.20 2 69
96 1775059094 2040152 db-read-3 0.00 0 80
97 1775059094 2040153 db-read-4 0.00 0 61
98 1775059094 2040154 db-write 0.00 0 4
99 1775059094 2040155 event-worker 0.00 0 5
100 1775059105 2040140 lws-main 51.40 514 52153
101 1775059105 2040150 db-read-1 0.10 1 72
102 1775059105 2040151 db-read-2 0.00 0 69
103 1775059105 2040152 db-read-3 0.00 0 80
104 1775059105 2040153 db-read-4 0.10 1 62
105 1775059105 2040154 db-write 0.00 0 4
106 1775059105 2040155 event-worker 0.00 0 5
107 1775059115 2040140 lws-main 78.50 785 52938
108 1775059115 2040150 db-read-1 0.00 0 72
109 1775059115 2040151 db-read-2 0.00 0 69
110 1775059115 2040152 db-read-3 0.00 0 80
111 1775059115 2040153 db-read-4 0.00 0 62
112 1775059115 2040154 db-write 0.00 0 4
113 1775059115 2040155 event-worker 0.00 0 5
114 1775059125 2040140 lws-main 40.70 407 53345
115 1775059125 2040150 db-read-1 0.00 0 72
116 1775059125 2040151 db-read-2 0.00 0 69
117 1775059125 2040152 db-read-3 0.10 1 81
118 1775059125 2040153 db-read-4 0.10 1 63
119 1775059125 2040154 db-write 0.00 0 4
120 1775059125 2040155 event-worker 0.00 0 5
121 1775059135 2040140 lws-main 41.00 410 53755
122 1775059135 2040150 db-read-1 0.00 0 72
123 1775059135 2040151 db-read-2 0.00 0 69
124 1775059135 2040152 db-read-3 0.00 0 81
125 1775059135 2040153 db-read-4 0.00 0 63
126 1775059135 2040154 db-write 0.00 0 4
127 1775059135 2040155 event-worker 0.00 0 5
128 1775059145 2040140 lws-main 58.70 587 54342
129 1775059145 2040150 db-read-1 0.00 0 72
130 1775059145 2040151 db-read-2 0.00 0 69
131 1775059145 2040152 db-read-3 0.00 0 81
132 1775059145 2040153 db-read-4 0.00 0 63
133 1775059145 2040154 db-write 0.00 0 4
134 1775059145 2040155 event-worker 0.00 0 5
135 1775059155 2040140 lws-main 28.40 284 54626
136 1775059155 2040150 db-read-1 0.20 2 74
137 1775059155 2040151 db-read-2 0.20 2 71
138 1775059155 2040152 db-read-3 0.10 1 82
139 1775059155 2040153 db-read-4 0.10 1 64
140 1775059155 2040154 db-write 0.00 0 4
141 1775059155 2040155 event-worker 0.00 0 5
142 1775059165 2040140 lws-main 89.80 898 55524
143 1775059165 2040150 db-read-1 0.30 3 77
144 1775059165 2040151 db-read-2 0.20 2 73
145 1775059165 2040152 db-read-3 0.30 3 85
146 1775059165 2040153 db-read-4 0.10 1 65
147 1775059165 2040154 db-write 0.00 0 4
148 1775059165 2040155 event-worker 0.00 0 5
149 1775059175 2040140 lws-main 100.20 1002 56526
150 1775059175 2040150 db-read-1 0.00 0 77
151 1775059175 2040151 db-read-2 0.00 0 73
152 1775059175 2040152 db-read-3 0.10 1 86
153 1775059175 2040153 db-read-4 0.20 2 67
154 1775059175 2040154 db-write 0.00 0 4
155 1775059175 2040155 event-worker 0.00 0 5
156 1775059185 2040140 lws-main 100.20 1002 57528
157 1775059185 2040150 db-read-1 0.20 2 79
158 1775059185 2040151 db-read-2 0.20 2 75
159 1775059185 2040152 db-read-3 0.10 1 87
160 1775059185 2040153 db-read-4 0.20 2 69
161 1775059185 2040154 db-write 0.00 0 4
162 1775059185 2040155 event-worker 0.00 0 5
163 1775059195 2040140 lws-main 75.70 757 58285
164 1775059195 2040150 db-read-1 0.20 2 81
165 1775059195 2040151 db-read-2 0.20 2 77
166 1775059195 2040152 db-read-3 0.20 2 89
167 1775059195 2040153 db-read-4 0.20 2 71
168 1775059195 2040154 db-write 0.00 0 4
169 1775059195 2040155 event-worker 0.00 0 5
170 1775059205 2040140 lws-main 77.70 777 59062
171 1775059205 2040150 db-read-1 0.20 2 83
172 1775059205 2040151 db-read-2 0.30 3 80
173 1775059205 2040152 db-read-3 0.50 5 94
174 1775059205 2040153 db-read-4 0.30 3 74
175 1775059205 2040154 db-write 0.00 0 4
176 1775059205 2040155 event-worker 0.10 1 6
177 1775059215 2040140 lws-main 99.60 996 60058
178 1775059215 2040150 db-read-1 0.40 4 87
179 1775059215 2040151 db-read-2 0.40 4 84
180 1775059215 2040152 db-read-3 0.10 1 95
181 1775059215 2040153 db-read-4 0.20 2 76
182 1775059215 2040154 db-write 0.00 0 4
183 1775059215 2040155 event-worker 0.00 0 6
184 1775059225 2040140 lws-main 100.30 1003 61061
185 1775059225 2040150 db-read-1 0.00 0 87
186 1775059225 2040151 db-read-2 0.10 1 85
187 1775059225 2040152 db-read-3 0.10 1 96
188 1775059225 2040153 db-read-4 0.20 2 78
189 1775059225 2040154 db-write 0.00 0 4
190 1775059225 2040155 event-worker 0.00 0 6
191 1775059235 2040140 lws-main 100.10 1001 62062
192 1775059235 2040150 db-read-1 0.10 1 88
193 1775059235 2040151 db-read-2 0.00 0 85
194 1775059235 2040152 db-read-3 0.00 0 96
195 1775059235 2040153 db-read-4 0.00 0 78
196 1775059235 2040154 db-write 0.00 0 4
197 1775059235 2040155 event-worker 0.00 0 6
198 1775059245 2040140 lws-main 98.10 981 63043
199 1775059245 2040150 db-read-1 0.10 1 89
200 1775059245 2040151 db-read-2 0.00 0 85
201 1775059245 2040152 db-read-3 0.20 2 98
202 1775059245 2040153 db-read-4 0.10 1 79
203 1775059245 2040154 db-write 0.00 0 4
204 1775059245 2040155 event-worker 0.00 0 6
205 1775059255 2040140 lws-main 54.10 541 63584
206 1775059255 2040150 db-read-1 0.20 2 91
207 1775059255 2040151 db-read-2 0.50 5 90
208 1775059255 2040152 db-read-3 0.50 5 103
209 1775059255 2040153 db-read-4 0.50 5 84
210 1775059255 2040154 db-write 0.00 0 4
211 1775059255 2040155 event-worker 0.00 0 6
212 1775059265 2040140 lws-main 82.80 828 64412
213 1775059265 2040150 db-read-1 0.00 0 91
214 1775059265 2040151 db-read-2 0.00 0 90
215 1775059265 2040152 db-read-3 0.00 0 103
216 1775059265 2040153 db-read-4 0.00 0 84
217 1775059265 2040154 db-write 0.00 0 4
218 1775059265 2040155 event-worker 0.00 0 6
219 1775059275 2040140 lws-main 99.90 999 65411
220 1775059275 2040150 db-read-1 0.10 1 92
221 1775059275 2040151 db-read-2 0.00 0 90
222 1775059275 2040152 db-read-3 0.00 0 103
223 1775059275 2040153 db-read-4 0.00 0 84
224 1775059275 2040154 db-write 0.00 0 4
225 1775059275 2040155 event-worker 0.00 0 6
226 1775059285 2040140 lws-main 58.30 583 65994
227 1775059285 2040150 db-read-1 0.00 0 92
228 1775059285 2040151 db-read-2 0.10 1 91
229 1775059285 2040152 db-read-3 0.10 1 104
230 1775059285 2040153 db-read-4 0.00 0 84
231 1775059285 2040154 db-write 0.00 0 4
232 1775059285 2040155 event-worker 0.00 0 6
233 1775059295 2040140 lws-main 42.60 426 66420
234 1775059295 2040150 db-read-1 0.30 3 95
235 1775059295 2040151 db-read-2 0.10 1 92
236 1775059295 2040152 db-read-3 0.10 1 105
237 1775059295 2040153 db-read-4 0.20 2 86
238 1775059295 2040154 db-write 0.00 0 4
239 1775059295 2040155 event-worker 0.00 0 6
240 1775059305 2040140 lws-main 16.20 162 66582
241 1775059305 2040150 db-read-1 0.20 2 97
242 1775059305 2040151 db-read-2 0.00 0 92
243 1775059305 2040152 db-read-3 0.10 1 106
244 1775059305 2040153 db-read-4 0.10 1 87
245 1775059305 2040154 db-write 0.00 0 4
246 1775059305 2040155 event-worker 0.00 0 6
247 1775059315 2040140 lws-main 100.30 1003 67585
248 1775059315 2040150 db-read-1 0.00 0 97
249 1775059315 2040151 db-read-2 0.00 0 92
250 1775059315 2040152 db-read-3 0.00 0 106
251 1775059315 2040153 db-read-4 0.00 0 87
252 1775059315 2040154 db-write 0.00 0 4
253 1775059315 2040155 event-worker 0.00 0 6
254 1775059325 2040140 lws-main 66.00 660 68245
255 1775059325 2040150 db-read-1 0.20 2 99
256 1775059325 2040151 db-read-2 0.50 5 97
257 1775059325 2040152 db-read-3 0.20 2 108
258 1775059325 2040153 db-read-4 0.20 2 89
259 1775059325 2040154 db-write 0.00 0 4
260 1775059325 2040155 event-worker 0.00 0 6
261 1775059335 2040140 lws-main 21.70 217 68462
262 1775059335 2040150 db-read-1 0.00 0 99
263 1775059335 2040151 db-read-2 0.00 0 97
264 1775059335 2040152 db-read-3 0.00 0 108
265 1775059335 2040153 db-read-4 0.00 0 89
266 1775059335 2040154 db-write 0.00 0 4
267 1775059335 2040155 event-worker 0.00 0 6
268 1775059345 2040140 lws-main 47.00 470 68932
269 1775059345 2040150 db-read-1 0.30 3 102
270 1775059345 2040151 db-read-2 0.30 3 100
271 1775059345 2040152 db-read-3 0.90 9 117
272 1775059345 2040153 db-read-4 0.20 2 91
273 1775059345 2040154 db-write 0.00 0 4
274 1775059345 2040155 event-worker 0.00 0 6
275 1775059355 2040140 lws-main 54.40 544 69476
276 1775059355 2040150 db-read-1 0.00 0 102
277 1775059355 2040151 db-read-2 0.00 0 100
278 1775059355 2040152 db-read-3 0.00 0 117
279 1775059355 2040153 db-read-4 0.00 0 91
280 1775059355 2040154 db-write 0.00 0 4
281 1775059355 2040155 event-worker 0.00 0 6
282 1775059365 2040140 lws-main 99.70 997 70473
283 1775059365 2040150 db-read-1 0.10 1 103
284 1775059365 2040151 db-read-2 0.10 1 101
285 1775059365 2040152 db-read-3 0.00 0 117
286 1775059365 2040153 db-read-4 0.00 0 91
287 1775059365 2040154 db-write 0.00 0 4
288 1775059365 2040155 event-worker 0.00 0 6
289 1775059375 2040140 lws-main 46.20 462 70935
290 1775059375 2040150 db-read-1 0.20 2 105
291 1775059375 2040151 db-read-2 0.10 1 102
292 1775059375 2040152 db-read-3 0.40 4 121
293 1775059375 2040153 db-read-4 0.20 2 93
294 1775059375 2040154 db-write 0.00 0 4
295 1775059375 2040155 event-worker 0.00 0 6
296 1775059385 2040140 lws-main 69.00 690 71625
297 1775059385 2040150 db-read-1 0.10 1 106
298 1775059385 2040151 db-read-2 0.00 0 102
299 1775059385 2040152 db-read-3 0.10 1 122
300 1775059385 2040153 db-read-4 0.10 1 94
301 1775059385 2040154 db-write 0.00 0 4
302 1775059385 2040155 event-worker 0.00 0 6
303 1775059395 2040140 lws-main 71.50 715 72340
304 1775059395 2040150 db-read-1 0.00 0 106
305 1775059395 2040151 db-read-2 0.20 2 104
306 1775059395 2040152 db-read-3 0.10 1 123
307 1775059395 2040153 db-read-4 0.30 3 97
308 1775059395 2040154 db-write 0.00 0 4
309 1775059395 2040155 event-worker 0.00 0 6
310 1775059405 2040140 lws-main 100.10 1001 73341
311 1775059405 2040150 db-read-1 0.20 2 108
312 1775059405 2040151 db-read-2 0.00 0 104
313 1775059405 2040152 db-read-3 0.00 0 123
314 1775059405 2040153 db-read-4 0.20 2 99
315 1775059405 2040154 db-write 0.00 0 4
316 1775059405 2040155 event-worker 0.00 0 6
317 1775059415 2040140 lws-main 100.20 1002 74343
318 1775059415 2040150 db-read-1 0.30 3 111
319 1775059415 2040151 db-read-2 0.20 2 106
320 1775059415 2040152 db-read-3 0.20 2 125
321 1775059415 2040153 db-read-4 0.30 3 102
322 1775059415 2040154 db-write 0.10 1 5
323 1775059415 2040155 event-worker 0.00 0 6
324 1775059425 2040140 lws-main 97.40 974 75317
325 1775059425 2040150 db-read-1 0.10 1 112
326 1775059425 2040151 db-read-2 0.10 1 107
327 1775059425 2040152 db-read-3 0.30 3 128
328 1775059425 2040153 db-read-4 0.10 1 103
329 1775059425 2040154 db-write 0.00 0 5
330 1775059425 2040155 event-worker 0.00 0 6
331 1775059435 2040140 lws-main 80.60 806 76123
332 1775059435 2040150 db-read-1 0.10 1 113
333 1775059435 2040151 db-read-2 0.20 2 109
334 1775059435 2040152 db-read-3 0.10 1 129
335 1775059435 2040153 db-read-4 0.10 1 104
336 1775059435 2040154 db-write 0.00 0 5
337 1775059435 2040155 event-worker 0.00 0 6
338 1775059445 2040140 lws-main 40.70 407 76530
339 1775059445 2040150 db-read-1 0.10 1 114
340 1775059445 2040151 db-read-2 0.00 0 109
341 1775059445 2040152 db-read-3 0.20 2 131
342 1775059445 2040153 db-read-4 0.10 1 105
343 1775059445 2040154 db-write 0.00 0 5
344 1775059445 2040155 event-worker 0.00 0 6
345 1775059456 2040140 lws-main 56.00 560 77090
346 1775059456 2040150 db-read-1 0.00 0 114
347 1775059456 2040151 db-read-2 0.20 2 111
348 1775059456 2040152 db-read-3 0.00 0 131
349 1775059456 2040153 db-read-4 0.10 1 106
350 1775059456 2040154 db-write 0.00 0 5
351 1775059456 2040155 event-worker 0.00 0 6
352 1775059466 2040140 lws-main 53.20 532 77622
353 1775059466 2040150 db-read-1 0.00 0 114
354 1775059466 2040151 db-read-2 0.00 0 111
355 1775059466 2040152 db-read-3 0.00 0 131
356 1775059466 2040153 db-read-4 0.00 0 106
357 1775059466 2040154 db-write 0.00 0 5
358 1775059466 2040155 event-worker 0.00 0 6
359 1775059476 2040140 lws-main 96.70 967 78589
360 1775059476 2040150 db-read-1 0.00 0 114
361 1775059476 2040151 db-read-2 0.00 0 111
362 1775059476 2040152 db-read-3 0.00 0 131
363 1775059476 2040153 db-read-4 0.00 0 106
364 1775059476 2040154 db-write 0.00 0 5
365 1775059476 2040155 event-worker 0.00 0 6
366 1775059486 2040140 lws-main 67.60 676 79265
367 1775059486 2040150 db-read-1 0.20 2 116
368 1775059486 2040151 db-read-2 0.00 0 111
369 1775059486 2040152 db-read-3 0.00 0 131
370 1775059486 2040153 db-read-4 0.00 0 106
371 1775059486 2040154 db-write 0.00 0 5
372 1775059486 2040155 event-worker 0.00 0 6
@@ -0,0 +1,14 @@
==========================================
Thread CPU Summary (avg + max over run)
==========================================
Run timestamp (UTC): 20260401_155603
Duration: 600s, Interval: 10s
2040140 lws-main 68.71 100.30
2040152 db-read-3 0.12 0.90
2040150 db-read-1 0.11 0.40
2040151 db-read-2 0.11 0.50
2040153 db-read-4 0.10 0.50
2040154 db-write 0.01 0.10
2040155 event-worker 0.00 0.10
TID THREAD AVG_CPU% MAX_CPU%
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,5 @@
Profiling PID: 2083904
CLK_TCK=100
Starting perf record in background...
perf_pid=2086924
Done. Artifacts in /tmp/c_relay_thread_profile_20260401_181751
@@ -0,0 +1,211 @@
timestamp,tid,thread_name,cpu_pct,delta_ticks,total_ticks
1775067472,2083904,lws-main,0.00,0,17710
1775067472,2083906,db-read-1,0.00,0,33
1775067472,2083907,db-read-2,0.00,0,29
1775067472,2083908,db-read-3,0.00,0,31
1775067472,2083909,db-read-4,0.00,0,41
1775067472,2083910,db-write,0.00,0,0
1775067472,2083911,event-worker,0.00,0,0
1775067482,2083904,lws-main,53.40,534,18244
1775067482,2083906,db-read-1,0.20,2,35
1775067482,2083907,db-read-2,0.00,0,29
1775067482,2083908,db-read-3,0.10,1,32
1775067482,2083909,db-read-4,0.00,0,41
1775067482,2083910,db-write,0.00,0,0
1775067482,2083911,event-worker,0.00,0,0
1775067492,2083904,lws-main,100.30,1003,19247
1775067492,2083906,db-read-1,0.00,0,35
1775067492,2083907,db-read-2,0.00,0,29
1775067492,2083908,db-read-3,0.00,0,32
1775067492,2083909,db-read-4,0.00,0,41
1775067492,2083910,db-write,0.00,0,0
1775067492,2083911,event-worker,0.00,0,0
1775067502,2083904,lws-main,100.30,1003,20250
1775067502,2083906,db-read-1,0.00,0,35
1775067502,2083907,db-read-2,0.00,0,29
1775067502,2083908,db-read-3,0.00,0,32
1775067502,2083909,db-read-4,0.00,0,41
1775067502,2083910,db-write,0.00,0,0
1775067502,2083911,event-worker,0.00,0,0
1775067512,2083904,lws-main,95.70,957,21207
1775067512,2083906,db-read-1,0.00,0,35
1775067512,2083907,db-read-2,0.10,1,30
1775067512,2083908,db-read-3,0.00,0,32
1775067512,2083909,db-read-4,0.00,0,41
1775067512,2083910,db-write,0.00,0,0
1775067512,2083911,event-worker,0.00,0,0
1775067522,2083904,lws-main,73.70,737,21944
1775067522,2083906,db-read-1,0.00,0,35
1775067522,2083907,db-read-2,0.00,0,30
1775067522,2083908,db-read-3,0.00,0,32
1775067522,2083909,db-read-4,0.00,0,41
1775067522,2083910,db-write,0.00,0,0
1775067522,2083911,event-worker,0.00,0,0
1775067532,2083904,lws-main,9.80,98,22042
1775067532,2083906,db-read-1,0.00,0,35
1775067532,2083907,db-read-2,0.00,0,30
1775067532,2083908,db-read-3,0.10,1,33
1775067532,2083909,db-read-4,0.10,1,42
1775067532,2083910,db-write,0.00,0,0
1775067532,2083911,event-worker,0.00,0,0
1775067542,2083904,lws-main,28.80,288,22330
1775067542,2083906,db-read-1,0.20,2,37
1775067542,2083907,db-read-2,0.40,4,34
1775067542,2083908,db-read-3,0.10,1,34
1775067542,2083909,db-read-4,0.20,2,44
1775067542,2083910,db-write,0.00,0,0
1775067542,2083911,event-worker,0.00,0,0
1775067552,2083904,lws-main,37.80,378,22708
1775067552,2083906,db-read-1,0.20,2,39
1775067552,2083907,db-read-2,0.20,2,36
1775067552,2083908,db-read-3,0.40,4,38
1775067552,2083909,db-read-4,0.10,1,45
1775067552,2083910,db-write,0.00,0,0
1775067552,2083911,event-worker,0.00,0,0
1775067563,2083904,lws-main,89.40,894,23602
1775067563,2083906,db-read-1,0.00,0,39
1775067563,2083907,db-read-2,0.10,1,37
1775067563,2083908,db-read-3,0.00,0,38
1775067563,2083909,db-read-4,0.10,1,46
1775067563,2083910,db-write,0.00,0,0
1775067563,2083911,event-worker,0.00,0,0
1775067573,2083904,lws-main,99.50,995,24597
1775067573,2083906,db-read-1,0.00,0,39
1775067573,2083907,db-read-2,0.00,0,37
1775067573,2083908,db-read-3,0.00,0,38
1775067573,2083909,db-read-4,0.00,0,46
1775067573,2083910,db-write,0.00,0,0
1775067573,2083911,event-worker,0.00,0,0
1775067583,2083904,lws-main,100.20,1002,25599
1775067583,2083906,db-read-1,0.00,0,39
1775067583,2083907,db-read-2,0.00,0,37
1775067583,2083908,db-read-3,0.00,0,38
1775067583,2083909,db-read-4,0.00,0,46
1775067583,2083910,db-write,0.00,0,0
1775067583,2083911,event-worker,0.00,0,0
1775067593,2083904,lws-main,100.20,1002,26601
1775067593,2083906,db-read-1,0.00,0,39
1775067593,2083907,db-read-2,0.00,0,37
1775067593,2083908,db-read-3,0.00,0,38
1775067593,2083909,db-read-4,0.00,0,46
1775067593,2083910,db-write,0.00,0,0
1775067593,2083911,event-worker,0.00,0,0
1775067603,2083904,lws-main,89.20,892,27493
1775067603,2083906,db-read-1,0.60,6,45
1775067603,2083907,db-read-2,0.20,2,39
1775067603,2083908,db-read-3,0.40,4,42
1775067603,2083909,db-read-4,0.60,6,52
1775067603,2083910,db-write,0.00,0,0
1775067603,2083911,event-worker,0.00,0,0
1775067613,2083904,lws-main,76.10,761,28254
1775067613,2083906,db-read-1,0.00,0,45
1775067613,2083907,db-read-2,0.00,0,39
1775067613,2083908,db-read-3,0.00,0,42
1775067613,2083909,db-read-4,0.00,0,52
1775067613,2083910,db-write,0.00,0,0
1775067613,2083911,event-worker,0.00,0,0
1775067623,2083904,lws-main,97.90,979,29233
1775067623,2083906,db-read-1,0.10,1,46
1775067623,2083907,db-read-2,0.20,2,41
1775067623,2083908,db-read-3,0.00,0,42
1775067623,2083909,db-read-4,0.00,0,52
1775067623,2083910,db-write,0.00,0,0
1775067623,2083911,event-worker,0.00,0,0
1775067633,2083904,lws-main,45.40,454,29687
1775067633,2083906,db-read-1,0.00,0,46
1775067633,2083907,db-read-2,0.00,0,41
1775067633,2083908,db-read-3,0.10,1,43
1775067633,2083909,db-read-4,0.00,0,52
1775067633,2083910,db-write,0.00,0,0
1775067633,2083911,event-worker,0.00,0,0
1775067643,2083904,lws-main,100.40,1004,30691
1775067643,2083906,db-read-1,0.00,0,46
1775067643,2083907,db-read-2,0.00,0,41
1775067643,2083908,db-read-3,0.00,0,43
1775067643,2083909,db-read-4,0.00,0,52
1775067643,2083910,db-write,0.00,0,0
1775067643,2083911,event-worker,0.00,0,0
1775067653,2083904,lws-main,100.20,1002,31693
1775067653,2083906,db-read-1,0.10,1,47
1775067653,2083907,db-read-2,0.00,0,41
1775067653,2083908,db-read-3,0.10,1,44
1775067653,2083909,db-read-4,0.10,1,53
1775067653,2083910,db-write,0.00,0,0
1775067653,2083911,event-worker,0.00,0,0
1775067663,2083904,lws-main,68.40,684,32377
1775067663,2083906,db-read-1,0.10,1,48
1775067663,2083907,db-read-2,0.20,2,43
1775067663,2083908,db-read-3,0.10,1,45
1775067663,2083909,db-read-4,0.10,1,54
1775067663,2083910,db-write,0.00,0,0
1775067663,2083911,event-worker,0.00,0,0
1775067673,2083904,lws-main,29.60,296,32673
1775067673,2083906,db-read-1,0.10,1,49
1775067673,2083907,db-read-2,0.10,1,44
1775067673,2083908,db-read-3,0.20,2,47
1775067673,2083909,db-read-4,0.20,2,56
1775067673,2083910,db-write,0.00,0,0
1775067673,2083911,event-worker,0.00,0,0
1775067683,2083904,lws-main,58.30,583,33256
1775067683,2083906,db-read-1,0.40,4,53
1775067683,2083907,db-read-2,0.40,4,48
1775067683,2083908,db-read-3,0.40,4,51
1775067683,2083909,db-read-4,0.60,6,62
1775067683,2083910,db-write,0.00,0,0
1775067683,2083911,event-worker,0.10,1,1
1775067693,2083904,lws-main,52.90,529,33785
1775067693,2083906,db-read-1,0.10,1,54
1775067693,2083907,db-read-2,0.20,2,50
1775067693,2083908,db-read-3,0.10,1,52
1775067693,2083909,db-read-4,0.10,1,63
1775067693,2083910,db-write,0.00,0,0
1775067693,2083911,event-worker,0.00,0,1
1775067703,2083904,lws-main,43.10,431,34216
1775067703,2083906,db-read-1,0.10,1,55
1775067703,2083907,db-read-2,0.00,0,50
1775067703,2083908,db-read-3,0.00,0,52
1775067703,2083909,db-read-4,0.10,1,64
1775067703,2083910,db-write,0.00,0,0
1775067703,2083911,event-worker,0.00,0,1
1775067713,2083904,lws-main,60.20,602,34818
1775067713,2083906,db-read-1,0.00,0,55
1775067713,2083907,db-read-2,0.00,0,50
1775067713,2083908,db-read-3,0.00,0,52
1775067713,2083909,db-read-4,0.00,0,64
1775067713,2083910,db-write,0.00,0,0
1775067713,2083911,event-worker,0.00,0,1
1775067723,2083904,lws-main,100.40,1004,35822
1775067723,2083906,db-read-1,0.00,0,55
1775067723,2083907,db-read-2,0.00,0,50
1775067723,2083908,db-read-3,0.00,0,52
1775067723,2083909,db-read-4,0.00,0,64
1775067723,2083910,db-write,0.00,0,0
1775067723,2083911,event-worker,0.00,0,1
1775067733,2083904,lws-main,100.20,1002,36824
1775067733,2083906,db-read-1,0.00,0,55
1775067733,2083907,db-read-2,0.00,0,50
1775067733,2083908,db-read-3,0.00,0,52
1775067733,2083909,db-read-4,0.00,0,64
1775067733,2083910,db-write,0.00,0,0
1775067733,2083911,event-worker,0.00,0,1
1775067743,2083904,lws-main,100.20,1002,37826
1775067743,2083906,db-read-1,0.00,0,55
1775067743,2083907,db-read-2,0.00,0,50
1775067743,2083908,db-read-3,0.20,2,54
1775067743,2083909,db-read-4,0.00,0,64
1775067743,2083910,db-write,0.00,0,0
1775067743,2083911,event-worker,0.00,0,1
1775067753,2083904,lws-main,93.70,937,38763
1775067753,2083906,db-read-1,0.20,2,57
1775067753,2083907,db-read-2,0.20,2,52
1775067753,2083908,db-read-3,0.10,1,55
1775067753,2083909,db-read-4,0.20,2,66
1775067753,2083910,db-write,0.10,1,1
1775067753,2083911,event-worker,0.00,0,1
1775067763,2083904,lws-main,100.10,1001,39764
1775067763,2083906,db-read-1,0.00,0,57
1775067763,2083907,db-read-2,0.00,0,52
1775067763,2083908,db-read-3,0.00,0,55
1775067763,2083909,db-read-4,0.00,0,66
1775067763,2083910,db-write,0.00,0,1
1775067763,2083911,event-worker,0.00,0,1
1 timestamp tid thread_name cpu_pct delta_ticks total_ticks
2 1775067472 2083904 lws-main 0.00 0 17710
3 1775067472 2083906 db-read-1 0.00 0 33
4 1775067472 2083907 db-read-2 0.00 0 29
5 1775067472 2083908 db-read-3 0.00 0 31
6 1775067472 2083909 db-read-4 0.00 0 41
7 1775067472 2083910 db-write 0.00 0 0
8 1775067472 2083911 event-worker 0.00 0 0
9 1775067482 2083904 lws-main 53.40 534 18244
10 1775067482 2083906 db-read-1 0.20 2 35
11 1775067482 2083907 db-read-2 0.00 0 29
12 1775067482 2083908 db-read-3 0.10 1 32
13 1775067482 2083909 db-read-4 0.00 0 41
14 1775067482 2083910 db-write 0.00 0 0
15 1775067482 2083911 event-worker 0.00 0 0
16 1775067492 2083904 lws-main 100.30 1003 19247
17 1775067492 2083906 db-read-1 0.00 0 35
18 1775067492 2083907 db-read-2 0.00 0 29
19 1775067492 2083908 db-read-3 0.00 0 32
20 1775067492 2083909 db-read-4 0.00 0 41
21 1775067492 2083910 db-write 0.00 0 0
22 1775067492 2083911 event-worker 0.00 0 0
23 1775067502 2083904 lws-main 100.30 1003 20250
24 1775067502 2083906 db-read-1 0.00 0 35
25 1775067502 2083907 db-read-2 0.00 0 29
26 1775067502 2083908 db-read-3 0.00 0 32
27 1775067502 2083909 db-read-4 0.00 0 41
28 1775067502 2083910 db-write 0.00 0 0
29 1775067502 2083911 event-worker 0.00 0 0
30 1775067512 2083904 lws-main 95.70 957 21207
31 1775067512 2083906 db-read-1 0.00 0 35
32 1775067512 2083907 db-read-2 0.10 1 30
33 1775067512 2083908 db-read-3 0.00 0 32
34 1775067512 2083909 db-read-4 0.00 0 41
35 1775067512 2083910 db-write 0.00 0 0
36 1775067512 2083911 event-worker 0.00 0 0
37 1775067522 2083904 lws-main 73.70 737 21944
38 1775067522 2083906 db-read-1 0.00 0 35
39 1775067522 2083907 db-read-2 0.00 0 30
40 1775067522 2083908 db-read-3 0.00 0 32
41 1775067522 2083909 db-read-4 0.00 0 41
42 1775067522 2083910 db-write 0.00 0 0
43 1775067522 2083911 event-worker 0.00 0 0
44 1775067532 2083904 lws-main 9.80 98 22042
45 1775067532 2083906 db-read-1 0.00 0 35
46 1775067532 2083907 db-read-2 0.00 0 30
47 1775067532 2083908 db-read-3 0.10 1 33
48 1775067532 2083909 db-read-4 0.10 1 42
49 1775067532 2083910 db-write 0.00 0 0
50 1775067532 2083911 event-worker 0.00 0 0
51 1775067542 2083904 lws-main 28.80 288 22330
52 1775067542 2083906 db-read-1 0.20 2 37
53 1775067542 2083907 db-read-2 0.40 4 34
54 1775067542 2083908 db-read-3 0.10 1 34
55 1775067542 2083909 db-read-4 0.20 2 44
56 1775067542 2083910 db-write 0.00 0 0
57 1775067542 2083911 event-worker 0.00 0 0
58 1775067552 2083904 lws-main 37.80 378 22708
59 1775067552 2083906 db-read-1 0.20 2 39
60 1775067552 2083907 db-read-2 0.20 2 36
61 1775067552 2083908 db-read-3 0.40 4 38
62 1775067552 2083909 db-read-4 0.10 1 45
63 1775067552 2083910 db-write 0.00 0 0
64 1775067552 2083911 event-worker 0.00 0 0
65 1775067563 2083904 lws-main 89.40 894 23602
66 1775067563 2083906 db-read-1 0.00 0 39
67 1775067563 2083907 db-read-2 0.10 1 37
68 1775067563 2083908 db-read-3 0.00 0 38
69 1775067563 2083909 db-read-4 0.10 1 46
70 1775067563 2083910 db-write 0.00 0 0
71 1775067563 2083911 event-worker 0.00 0 0
72 1775067573 2083904 lws-main 99.50 995 24597
73 1775067573 2083906 db-read-1 0.00 0 39
74 1775067573 2083907 db-read-2 0.00 0 37
75 1775067573 2083908 db-read-3 0.00 0 38
76 1775067573 2083909 db-read-4 0.00 0 46
77 1775067573 2083910 db-write 0.00 0 0
78 1775067573 2083911 event-worker 0.00 0 0
79 1775067583 2083904 lws-main 100.20 1002 25599
80 1775067583 2083906 db-read-1 0.00 0 39
81 1775067583 2083907 db-read-2 0.00 0 37
82 1775067583 2083908 db-read-3 0.00 0 38
83 1775067583 2083909 db-read-4 0.00 0 46
84 1775067583 2083910 db-write 0.00 0 0
85 1775067583 2083911 event-worker 0.00 0 0
86 1775067593 2083904 lws-main 100.20 1002 26601
87 1775067593 2083906 db-read-1 0.00 0 39
88 1775067593 2083907 db-read-2 0.00 0 37
89 1775067593 2083908 db-read-3 0.00 0 38
90 1775067593 2083909 db-read-4 0.00 0 46
91 1775067593 2083910 db-write 0.00 0 0
92 1775067593 2083911 event-worker 0.00 0 0
93 1775067603 2083904 lws-main 89.20 892 27493
94 1775067603 2083906 db-read-1 0.60 6 45
95 1775067603 2083907 db-read-2 0.20 2 39
96 1775067603 2083908 db-read-3 0.40 4 42
97 1775067603 2083909 db-read-4 0.60 6 52
98 1775067603 2083910 db-write 0.00 0 0
99 1775067603 2083911 event-worker 0.00 0 0
100 1775067613 2083904 lws-main 76.10 761 28254
101 1775067613 2083906 db-read-1 0.00 0 45
102 1775067613 2083907 db-read-2 0.00 0 39
103 1775067613 2083908 db-read-3 0.00 0 42
104 1775067613 2083909 db-read-4 0.00 0 52
105 1775067613 2083910 db-write 0.00 0 0
106 1775067613 2083911 event-worker 0.00 0 0
107 1775067623 2083904 lws-main 97.90 979 29233
108 1775067623 2083906 db-read-1 0.10 1 46
109 1775067623 2083907 db-read-2 0.20 2 41
110 1775067623 2083908 db-read-3 0.00 0 42
111 1775067623 2083909 db-read-4 0.00 0 52
112 1775067623 2083910 db-write 0.00 0 0
113 1775067623 2083911 event-worker 0.00 0 0
114 1775067633 2083904 lws-main 45.40 454 29687
115 1775067633 2083906 db-read-1 0.00 0 46
116 1775067633 2083907 db-read-2 0.00 0 41
117 1775067633 2083908 db-read-3 0.10 1 43
118 1775067633 2083909 db-read-4 0.00 0 52
119 1775067633 2083910 db-write 0.00 0 0
120 1775067633 2083911 event-worker 0.00 0 0
121 1775067643 2083904 lws-main 100.40 1004 30691
122 1775067643 2083906 db-read-1 0.00 0 46
123 1775067643 2083907 db-read-2 0.00 0 41
124 1775067643 2083908 db-read-3 0.00 0 43
125 1775067643 2083909 db-read-4 0.00 0 52
126 1775067643 2083910 db-write 0.00 0 0
127 1775067643 2083911 event-worker 0.00 0 0
128 1775067653 2083904 lws-main 100.20 1002 31693
129 1775067653 2083906 db-read-1 0.10 1 47
130 1775067653 2083907 db-read-2 0.00 0 41
131 1775067653 2083908 db-read-3 0.10 1 44
132 1775067653 2083909 db-read-4 0.10 1 53
133 1775067653 2083910 db-write 0.00 0 0
134 1775067653 2083911 event-worker 0.00 0 0
135 1775067663 2083904 lws-main 68.40 684 32377
136 1775067663 2083906 db-read-1 0.10 1 48
137 1775067663 2083907 db-read-2 0.20 2 43
138 1775067663 2083908 db-read-3 0.10 1 45
139 1775067663 2083909 db-read-4 0.10 1 54
140 1775067663 2083910 db-write 0.00 0 0
141 1775067663 2083911 event-worker 0.00 0 0
142 1775067673 2083904 lws-main 29.60 296 32673
143 1775067673 2083906 db-read-1 0.10 1 49
144 1775067673 2083907 db-read-2 0.10 1 44
145 1775067673 2083908 db-read-3 0.20 2 47
146 1775067673 2083909 db-read-4 0.20 2 56
147 1775067673 2083910 db-write 0.00 0 0
148 1775067673 2083911 event-worker 0.00 0 0
149 1775067683 2083904 lws-main 58.30 583 33256
150 1775067683 2083906 db-read-1 0.40 4 53
151 1775067683 2083907 db-read-2 0.40 4 48
152 1775067683 2083908 db-read-3 0.40 4 51
153 1775067683 2083909 db-read-4 0.60 6 62
154 1775067683 2083910 db-write 0.00 0 0
155 1775067683 2083911 event-worker 0.10 1 1
156 1775067693 2083904 lws-main 52.90 529 33785
157 1775067693 2083906 db-read-1 0.10 1 54
158 1775067693 2083907 db-read-2 0.20 2 50
159 1775067693 2083908 db-read-3 0.10 1 52
160 1775067693 2083909 db-read-4 0.10 1 63
161 1775067693 2083910 db-write 0.00 0 0
162 1775067693 2083911 event-worker 0.00 0 1
163 1775067703 2083904 lws-main 43.10 431 34216
164 1775067703 2083906 db-read-1 0.10 1 55
165 1775067703 2083907 db-read-2 0.00 0 50
166 1775067703 2083908 db-read-3 0.00 0 52
167 1775067703 2083909 db-read-4 0.10 1 64
168 1775067703 2083910 db-write 0.00 0 0
169 1775067703 2083911 event-worker 0.00 0 1
170 1775067713 2083904 lws-main 60.20 602 34818
171 1775067713 2083906 db-read-1 0.00 0 55
172 1775067713 2083907 db-read-2 0.00 0 50
173 1775067713 2083908 db-read-3 0.00 0 52
174 1775067713 2083909 db-read-4 0.00 0 64
175 1775067713 2083910 db-write 0.00 0 0
176 1775067713 2083911 event-worker 0.00 0 1
177 1775067723 2083904 lws-main 100.40 1004 35822
178 1775067723 2083906 db-read-1 0.00 0 55
179 1775067723 2083907 db-read-2 0.00 0 50
180 1775067723 2083908 db-read-3 0.00 0 52
181 1775067723 2083909 db-read-4 0.00 0 64
182 1775067723 2083910 db-write 0.00 0 0
183 1775067723 2083911 event-worker 0.00 0 1
184 1775067733 2083904 lws-main 100.20 1002 36824
185 1775067733 2083906 db-read-1 0.00 0 55
186 1775067733 2083907 db-read-2 0.00 0 50
187 1775067733 2083908 db-read-3 0.00 0 52
188 1775067733 2083909 db-read-4 0.00 0 64
189 1775067733 2083910 db-write 0.00 0 0
190 1775067733 2083911 event-worker 0.00 0 1
191 1775067743 2083904 lws-main 100.20 1002 37826
192 1775067743 2083906 db-read-1 0.00 0 55
193 1775067743 2083907 db-read-2 0.00 0 50
194 1775067743 2083908 db-read-3 0.20 2 54
195 1775067743 2083909 db-read-4 0.00 0 64
196 1775067743 2083910 db-write 0.00 0 0
197 1775067743 2083911 event-worker 0.00 0 1
198 1775067753 2083904 lws-main 93.70 937 38763
199 1775067753 2083906 db-read-1 0.20 2 57
200 1775067753 2083907 db-read-2 0.20 2 52
201 1775067753 2083908 db-read-3 0.10 1 55
202 1775067753 2083909 db-read-4 0.20 2 66
203 1775067753 2083910 db-write 0.10 1 1
204 1775067753 2083911 event-worker 0.00 0 1
205 1775067763 2083904 lws-main 100.10 1001 39764
206 1775067763 2083906 db-read-1 0.00 0 57
207 1775067763 2083907 db-read-2 0.00 0 52
208 1775067763 2083908 db-read-3 0.00 0 55
209 1775067763 2083909 db-read-4 0.00 0 66
210 1775067763 2083910 db-write 0.00 0 1
211 1775067763 2083911 event-worker 0.00 0 1
@@ -0,0 +1,14 @@
==========================================
Thread CPU Summary (avg + max over run)
==========================================
Run timestamp (UTC): 20260401_181751
Duration: 300s, Interval: 10s
2083904 lws-main 73.51 100.40
2083906 db-read-1 0.08 0.60
2083907 db-read-2 0.08 0.40
2083908 db-read-3 0.08 0.40
2083909 db-read-4 0.08 0.60
2083910 db-write 0.00 0.10
2083911 event-worker 0.00 0.10
TID THREAD AVG_CPU% MAX_CPU%
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,5 @@
Profiling PID: 2083904
CLK_TCK=100
Starting perf record in background...
perf_pid=2094527
Done. Artifacts in /tmp/c_relay_thread_profile_20260401_183846
@@ -0,0 +1,211 @@
timestamp,tid,thread_name,cpu_pct,delta_ticks,total_ticks
1775068727,2083904,lws-main,0.20,2,102303
1775068727,2083906,db-read-1,0.00,0,153
1775068727,2083907,db-read-2,0.00,0,145
1775068727,2083908,db-read-3,0.00,0,145
1775068727,2083909,db-read-4,0.00,0,160
1775068727,2083910,db-write,0.00,0,4
1775068727,2083911,event-worker,0.00,0,11
1775068737,2083904,lws-main,100.20,1002,103305
1775068737,2083906,db-read-1,0.00,0,153
1775068737,2083907,db-read-2,0.10,1,146
1775068737,2083908,db-read-3,0.00,0,145
1775068737,2083909,db-read-4,0.00,0,160
1775068737,2083910,db-write,0.00,0,4
1775068737,2083911,event-worker,0.00,0,11
1775068747,2083904,lws-main,100.30,1003,104308
1775068747,2083906,db-read-1,0.00,0,153
1775068747,2083907,db-read-2,0.00,0,146
1775068747,2083908,db-read-3,0.10,1,146
1775068747,2083909,db-read-4,0.10,1,161
1775068747,2083910,db-write,0.00,0,4
1775068747,2083911,event-worker,0.00,0,11
1775068757,2083904,lws-main,100.20,1002,105310
1775068757,2083906,db-read-1,0.00,0,153
1775068757,2083907,db-read-2,0.00,0,146
1775068757,2083908,db-read-3,0.00,0,146
1775068757,2083909,db-read-4,0.00,0,161
1775068757,2083910,db-write,0.00,0,4
1775068757,2083911,event-worker,0.00,0,11
1775068767,2083904,lws-main,100.20,1002,106312
1775068767,2083906,db-read-1,0.00,0,153
1775068767,2083907,db-read-2,0.00,0,146
1775068767,2083908,db-read-3,0.00,0,146
1775068767,2083909,db-read-4,0.00,0,161
1775068767,2083910,db-write,0.00,0,4
1775068767,2083911,event-worker,0.00,0,11
1775068777,2083904,lws-main,81.10,811,107123
1775068777,2083906,db-read-1,0.00,0,153
1775068777,2083907,db-read-2,0.00,0,146
1775068777,2083908,db-read-3,0.10,1,147
1775068777,2083909,db-read-4,0.00,0,161
1775068777,2083910,db-write,0.00,0,4
1775068777,2083911,event-worker,0.00,0,11
1775068787,2083904,lws-main,7.40,74,107197
1775068787,2083906,db-read-1,0.00,0,153
1775068787,2083907,db-read-2,0.00,0,146
1775068787,2083908,db-read-3,0.00,0,147
1775068787,2083909,db-read-4,0.00,0,161
1775068787,2083910,db-write,0.00,0,4
1775068787,2083911,event-worker,0.00,0,11
1775068797,2083904,lws-main,50.30,503,107700
1775068797,2083906,db-read-1,0.30,3,156
1775068797,2083907,db-read-2,0.30,3,149
1775068797,2083908,db-read-3,0.30,3,150
1775068797,2083909,db-read-4,0.20,2,163
1775068797,2083910,db-write,0.00,0,4
1775068797,2083911,event-worker,0.00,0,11
1775068807,2083904,lws-main,58.00,580,108280
1775068807,2083906,db-read-1,0.00,0,156
1775068807,2083907,db-read-2,0.00,0,149
1775068807,2083908,db-read-3,0.00,0,150
1775068807,2083909,db-read-4,0.00,0,163
1775068807,2083910,db-write,0.00,0,4
1775068807,2083911,event-worker,0.00,0,11
1775068818,2083904,lws-main,58.40,584,108864
1775068818,2083906,db-read-1,0.10,1,157
1775068818,2083907,db-read-2,0.10,1,150
1775068818,2083908,db-read-3,0.10,1,151
1775068818,2083909,db-read-4,0.30,3,166
1775068818,2083910,db-write,0.00,0,4
1775068818,2083911,event-worker,0.00,0,11
1775068828,2083904,lws-main,43.30,433,109297
1775068828,2083906,db-read-1,0.00,0,157
1775068828,2083907,db-read-2,0.00,0,150
1775068828,2083908,db-read-3,0.00,0,151
1775068828,2083909,db-read-4,0.20,2,168
1775068828,2083910,db-write,0.00,0,4
1775068828,2083911,event-worker,0.20,2,13
1775068838,2083904,lws-main,29.00,290,109587
1775068838,2083906,db-read-1,0.20,2,159
1775068838,2083907,db-read-2,0.00,0,150
1775068838,2083908,db-read-3,0.10,1,152
1775068838,2083909,db-read-4,0.00,0,168
1775068838,2083910,db-write,0.00,0,4
1775068838,2083911,event-worker,0.00,0,13
1775068848,2083904,lws-main,37.00,370,109957
1775068848,2083906,db-read-1,0.00,0,159
1775068848,2083907,db-read-2,0.10,1,151
1775068848,2083908,db-read-3,0.00,0,152
1775068848,2083909,db-read-4,0.00,0,168
1775068848,2083910,db-write,0.00,0,4
1775068848,2083911,event-worker,0.00,0,13
1775068858,2083904,lws-main,48.40,484,110441
1775068858,2083906,db-read-1,0.10,1,160
1775068858,2083907,db-read-2,0.10,1,152
1775068858,2083908,db-read-3,0.10,1,153
1775068858,2083909,db-read-4,0.20,2,170
1775068858,2083910,db-write,0.00,0,4
1775068858,2083911,event-worker,0.00,0,13
1775068868,2083904,lws-main,45.00,450,110891
1775068868,2083906,db-read-1,0.00,0,160
1775068868,2083907,db-read-2,0.10,1,153
1775068868,2083908,db-read-3,0.00,0,153
1775068868,2083909,db-read-4,0.00,0,170
1775068868,2083910,db-write,0.00,0,4
1775068868,2083911,event-worker,0.00,0,13
1775068878,2083904,lws-main,21.00,210,111101
1775068878,2083906,db-read-1,0.00,0,160
1775068878,2083907,db-read-2,0.00,0,153
1775068878,2083908,db-read-3,0.00,0,153
1775068878,2083909,db-read-4,0.00,0,170
1775068878,2083910,db-write,0.00,0,4
1775068878,2083911,event-worker,0.00,0,13
1775068888,2083904,lws-main,36.40,364,111465
1775068888,2083906,db-read-1,0.00,0,160
1775068888,2083907,db-read-2,0.30,3,156
1775068888,2083908,db-read-3,0.10,1,154
1775068888,2083909,db-read-4,0.00,0,170
1775068888,2083910,db-write,0.00,0,4
1775068888,2083911,event-worker,0.00,0,13
1775068898,2083904,lws-main,44.10,441,111906
1775068898,2083906,db-read-1,0.00,0,160
1775068898,2083907,db-read-2,0.00,0,156
1775068898,2083908,db-read-3,0.00,0,154
1775068898,2083909,db-read-4,0.00,0,170
1775068898,2083910,db-write,0.00,0,4
1775068898,2083911,event-worker,0.00,0,13
1775068908,2083904,lws-main,19.70,197,112103
1775068908,2083906,db-read-1,0.20,2,162
1775068908,2083907,db-read-2,0.10,1,157
1775068908,2083908,db-read-3,0.00,0,154
1775068908,2083909,db-read-4,0.10,1,171
1775068908,2083910,db-write,0.00,0,4
1775068908,2083911,event-worker,0.00,0,13
1775068918,2083904,lws-main,83.20,832,112935
1775068918,2083906,db-read-1,0.30,3,165
1775068918,2083907,db-read-2,0.30,3,160
1775068918,2083908,db-read-3,0.20,2,156
1775068918,2083909,db-read-4,0.20,2,173
1775068918,2083910,db-write,0.00,0,4
1775068918,2083911,event-worker,0.00,0,13
1775068928,2083904,lws-main,37.40,374,113309
1775068928,2083906,db-read-1,0.00,0,165
1775068928,2083907,db-read-2,0.00,0,160
1775068928,2083908,db-read-3,0.10,1,157
1775068928,2083909,db-read-4,0.00,0,173
1775068928,2083910,db-write,0.00,0,4
1775068928,2083911,event-worker,0.00,0,13
1775068938,2083904,lws-main,59.80,598,113907
1775068938,2083906,db-read-1,0.00,0,165
1775068938,2083907,db-read-2,0.00,0,160
1775068938,2083908,db-read-3,0.10,1,158
1775068938,2083909,db-read-4,0.00,0,173
1775068938,2083910,db-write,0.00,0,4
1775068938,2083911,event-worker,0.00,0,13
1775068948,2083904,lws-main,61.20,612,114519
1775068948,2083906,db-read-1,0.30,3,168
1775068948,2083907,db-read-2,0.20,2,162
1775068948,2083908,db-read-3,0.10,1,159
1775068948,2083909,db-read-4,0.40,4,177
1775068948,2083910,db-write,0.00,0,4
1775068948,2083911,event-worker,0.10,1,14
1775068958,2083904,lws-main,65.30,653,115172
1775068958,2083906,db-read-1,0.00,0,168
1775068958,2083907,db-read-2,0.10,1,163
1775068958,2083908,db-read-3,0.10,1,160
1775068958,2083909,db-read-4,0.20,2,179
1775068958,2083910,db-write,0.00,0,4
1775068958,2083911,event-worker,0.00,0,14
1775068968,2083904,lws-main,23.00,230,115402
1775068968,2083906,db-read-1,0.00,0,168
1775068968,2083907,db-read-2,0.00,0,163
1775068968,2083908,db-read-3,0.00,0,160
1775068968,2083909,db-read-4,0.00,0,179
1775068968,2083910,db-write,0.00,0,4
1775068968,2083911,event-worker,0.00,0,14
1775068978,2083904,lws-main,79.80,798,116200
1775068978,2083906,db-read-1,0.40,4,172
1775068978,2083907,db-read-2,0.10,1,164
1775068978,2083908,db-read-3,0.10,1,161
1775068978,2083909,db-read-4,0.20,2,181
1775068978,2083910,db-write,0.00,0,4
1775068978,2083911,event-worker,0.00,0,14
1775068988,2083904,lws-main,70.70,707,116907
1775068988,2083906,db-read-1,0.00,0,172
1775068988,2083907,db-read-2,0.00,0,164
1775068988,2083908,db-read-3,0.10,1,162
1775068988,2083909,db-read-4,0.00,0,181
1775068988,2083910,db-write,0.00,0,4
1775068988,2083911,event-worker,0.00,0,14
1775068998,2083904,lws-main,23.00,230,117137
1775068998,2083906,db-read-1,0.00,0,172
1775068998,2083907,db-read-2,0.00,0,164
1775068998,2083908,db-read-3,0.00,0,162
1775068998,2083909,db-read-4,0.00,0,181
1775068998,2083910,db-write,0.00,0,4
1775068998,2083911,event-worker,0.00,0,14
1775069008,2083904,lws-main,66.00,660,117797
1775069008,2083906,db-read-1,0.30,3,175
1775069008,2083907,db-read-2,0.40,4,168
1775069008,2083908,db-read-3,0.40,4,166
1775069008,2083909,db-read-4,0.30,3,184
1775069008,2083910,db-write,0.00,0,4
1775069008,2083911,event-worker,0.00,0,14
1775069018,2083904,lws-main,80.00,800,118597
1775069018,2083906,db-read-1,0.00,0,175
1775069018,2083907,db-read-2,0.00,0,168
1775069018,2083908,db-read-3,0.00,0,166
1775069018,2083909,db-read-4,0.00,0,184
1775069018,2083910,db-write,0.00,0,4
1775069018,2083911,event-worker,0.00,0,14
1 timestamp tid thread_name cpu_pct delta_ticks total_ticks
2 1775068727 2083904 lws-main 0.20 2 102303
3 1775068727 2083906 db-read-1 0.00 0 153
4 1775068727 2083907 db-read-2 0.00 0 145
5 1775068727 2083908 db-read-3 0.00 0 145
6 1775068727 2083909 db-read-4 0.00 0 160
7 1775068727 2083910 db-write 0.00 0 4
8 1775068727 2083911 event-worker 0.00 0 11
9 1775068737 2083904 lws-main 100.20 1002 103305
10 1775068737 2083906 db-read-1 0.00 0 153
11 1775068737 2083907 db-read-2 0.10 1 146
12 1775068737 2083908 db-read-3 0.00 0 145
13 1775068737 2083909 db-read-4 0.00 0 160
14 1775068737 2083910 db-write 0.00 0 4
15 1775068737 2083911 event-worker 0.00 0 11
16 1775068747 2083904 lws-main 100.30 1003 104308
17 1775068747 2083906 db-read-1 0.00 0 153
18 1775068747 2083907 db-read-2 0.00 0 146
19 1775068747 2083908 db-read-3 0.10 1 146
20 1775068747 2083909 db-read-4 0.10 1 161
21 1775068747 2083910 db-write 0.00 0 4
22 1775068747 2083911 event-worker 0.00 0 11
23 1775068757 2083904 lws-main 100.20 1002 105310
24 1775068757 2083906 db-read-1 0.00 0 153
25 1775068757 2083907 db-read-2 0.00 0 146
26 1775068757 2083908 db-read-3 0.00 0 146
27 1775068757 2083909 db-read-4 0.00 0 161
28 1775068757 2083910 db-write 0.00 0 4
29 1775068757 2083911 event-worker 0.00 0 11
30 1775068767 2083904 lws-main 100.20 1002 106312
31 1775068767 2083906 db-read-1 0.00 0 153
32 1775068767 2083907 db-read-2 0.00 0 146
33 1775068767 2083908 db-read-3 0.00 0 146
34 1775068767 2083909 db-read-4 0.00 0 161
35 1775068767 2083910 db-write 0.00 0 4
36 1775068767 2083911 event-worker 0.00 0 11
37 1775068777 2083904 lws-main 81.10 811 107123
38 1775068777 2083906 db-read-1 0.00 0 153
39 1775068777 2083907 db-read-2 0.00 0 146
40 1775068777 2083908 db-read-3 0.10 1 147
41 1775068777 2083909 db-read-4 0.00 0 161
42 1775068777 2083910 db-write 0.00 0 4
43 1775068777 2083911 event-worker 0.00 0 11
44 1775068787 2083904 lws-main 7.40 74 107197
45 1775068787 2083906 db-read-1 0.00 0 153
46 1775068787 2083907 db-read-2 0.00 0 146
47 1775068787 2083908 db-read-3 0.00 0 147
48 1775068787 2083909 db-read-4 0.00 0 161
49 1775068787 2083910 db-write 0.00 0 4
50 1775068787 2083911 event-worker 0.00 0 11
51 1775068797 2083904 lws-main 50.30 503 107700
52 1775068797 2083906 db-read-1 0.30 3 156
53 1775068797 2083907 db-read-2 0.30 3 149
54 1775068797 2083908 db-read-3 0.30 3 150
55 1775068797 2083909 db-read-4 0.20 2 163
56 1775068797 2083910 db-write 0.00 0 4
57 1775068797 2083911 event-worker 0.00 0 11
58 1775068807 2083904 lws-main 58.00 580 108280
59 1775068807 2083906 db-read-1 0.00 0 156
60 1775068807 2083907 db-read-2 0.00 0 149
61 1775068807 2083908 db-read-3 0.00 0 150
62 1775068807 2083909 db-read-4 0.00 0 163
63 1775068807 2083910 db-write 0.00 0 4
64 1775068807 2083911 event-worker 0.00 0 11
65 1775068818 2083904 lws-main 58.40 584 108864
66 1775068818 2083906 db-read-1 0.10 1 157
67 1775068818 2083907 db-read-2 0.10 1 150
68 1775068818 2083908 db-read-3 0.10 1 151
69 1775068818 2083909 db-read-4 0.30 3 166
70 1775068818 2083910 db-write 0.00 0 4
71 1775068818 2083911 event-worker 0.00 0 11
72 1775068828 2083904 lws-main 43.30 433 109297
73 1775068828 2083906 db-read-1 0.00 0 157
74 1775068828 2083907 db-read-2 0.00 0 150
75 1775068828 2083908 db-read-3 0.00 0 151
76 1775068828 2083909 db-read-4 0.20 2 168
77 1775068828 2083910 db-write 0.00 0 4
78 1775068828 2083911 event-worker 0.20 2 13
79 1775068838 2083904 lws-main 29.00 290 109587
80 1775068838 2083906 db-read-1 0.20 2 159
81 1775068838 2083907 db-read-2 0.00 0 150
82 1775068838 2083908 db-read-3 0.10 1 152
83 1775068838 2083909 db-read-4 0.00 0 168
84 1775068838 2083910 db-write 0.00 0 4
85 1775068838 2083911 event-worker 0.00 0 13
86 1775068848 2083904 lws-main 37.00 370 109957
87 1775068848 2083906 db-read-1 0.00 0 159
88 1775068848 2083907 db-read-2 0.10 1 151
89 1775068848 2083908 db-read-3 0.00 0 152
90 1775068848 2083909 db-read-4 0.00 0 168
91 1775068848 2083910 db-write 0.00 0 4
92 1775068848 2083911 event-worker 0.00 0 13
93 1775068858 2083904 lws-main 48.40 484 110441
94 1775068858 2083906 db-read-1 0.10 1 160
95 1775068858 2083907 db-read-2 0.10 1 152
96 1775068858 2083908 db-read-3 0.10 1 153
97 1775068858 2083909 db-read-4 0.20 2 170
98 1775068858 2083910 db-write 0.00 0 4
99 1775068858 2083911 event-worker 0.00 0 13
100 1775068868 2083904 lws-main 45.00 450 110891
101 1775068868 2083906 db-read-1 0.00 0 160
102 1775068868 2083907 db-read-2 0.10 1 153
103 1775068868 2083908 db-read-3 0.00 0 153
104 1775068868 2083909 db-read-4 0.00 0 170
105 1775068868 2083910 db-write 0.00 0 4
106 1775068868 2083911 event-worker 0.00 0 13
107 1775068878 2083904 lws-main 21.00 210 111101
108 1775068878 2083906 db-read-1 0.00 0 160
109 1775068878 2083907 db-read-2 0.00 0 153
110 1775068878 2083908 db-read-3 0.00 0 153
111 1775068878 2083909 db-read-4 0.00 0 170
112 1775068878 2083910 db-write 0.00 0 4
113 1775068878 2083911 event-worker 0.00 0 13
114 1775068888 2083904 lws-main 36.40 364 111465
115 1775068888 2083906 db-read-1 0.00 0 160
116 1775068888 2083907 db-read-2 0.30 3 156
117 1775068888 2083908 db-read-3 0.10 1 154
118 1775068888 2083909 db-read-4 0.00 0 170
119 1775068888 2083910 db-write 0.00 0 4
120 1775068888 2083911 event-worker 0.00 0 13
121 1775068898 2083904 lws-main 44.10 441 111906
122 1775068898 2083906 db-read-1 0.00 0 160
123 1775068898 2083907 db-read-2 0.00 0 156
124 1775068898 2083908 db-read-3 0.00 0 154
125 1775068898 2083909 db-read-4 0.00 0 170
126 1775068898 2083910 db-write 0.00 0 4
127 1775068898 2083911 event-worker 0.00 0 13
128 1775068908 2083904 lws-main 19.70 197 112103
129 1775068908 2083906 db-read-1 0.20 2 162
130 1775068908 2083907 db-read-2 0.10 1 157
131 1775068908 2083908 db-read-3 0.00 0 154
132 1775068908 2083909 db-read-4 0.10 1 171
133 1775068908 2083910 db-write 0.00 0 4
134 1775068908 2083911 event-worker 0.00 0 13
135 1775068918 2083904 lws-main 83.20 832 112935
136 1775068918 2083906 db-read-1 0.30 3 165
137 1775068918 2083907 db-read-2 0.30 3 160
138 1775068918 2083908 db-read-3 0.20 2 156
139 1775068918 2083909 db-read-4 0.20 2 173
140 1775068918 2083910 db-write 0.00 0 4
141 1775068918 2083911 event-worker 0.00 0 13
142 1775068928 2083904 lws-main 37.40 374 113309
143 1775068928 2083906 db-read-1 0.00 0 165
144 1775068928 2083907 db-read-2 0.00 0 160
145 1775068928 2083908 db-read-3 0.10 1 157
146 1775068928 2083909 db-read-4 0.00 0 173
147 1775068928 2083910 db-write 0.00 0 4
148 1775068928 2083911 event-worker 0.00 0 13
149 1775068938 2083904 lws-main 59.80 598 113907
150 1775068938 2083906 db-read-1 0.00 0 165
151 1775068938 2083907 db-read-2 0.00 0 160
152 1775068938 2083908 db-read-3 0.10 1 158
153 1775068938 2083909 db-read-4 0.00 0 173
154 1775068938 2083910 db-write 0.00 0 4
155 1775068938 2083911 event-worker 0.00 0 13
156 1775068948 2083904 lws-main 61.20 612 114519
157 1775068948 2083906 db-read-1 0.30 3 168
158 1775068948 2083907 db-read-2 0.20 2 162
159 1775068948 2083908 db-read-3 0.10 1 159
160 1775068948 2083909 db-read-4 0.40 4 177
161 1775068948 2083910 db-write 0.00 0 4
162 1775068948 2083911 event-worker 0.10 1 14
163 1775068958 2083904 lws-main 65.30 653 115172
164 1775068958 2083906 db-read-1 0.00 0 168
165 1775068958 2083907 db-read-2 0.10 1 163
166 1775068958 2083908 db-read-3 0.10 1 160
167 1775068958 2083909 db-read-4 0.20 2 179
168 1775068958 2083910 db-write 0.00 0 4
169 1775068958 2083911 event-worker 0.00 0 14
170 1775068968 2083904 lws-main 23.00 230 115402
171 1775068968 2083906 db-read-1 0.00 0 168
172 1775068968 2083907 db-read-2 0.00 0 163
173 1775068968 2083908 db-read-3 0.00 0 160
174 1775068968 2083909 db-read-4 0.00 0 179
175 1775068968 2083910 db-write 0.00 0 4
176 1775068968 2083911 event-worker 0.00 0 14
177 1775068978 2083904 lws-main 79.80 798 116200
178 1775068978 2083906 db-read-1 0.40 4 172
179 1775068978 2083907 db-read-2 0.10 1 164
180 1775068978 2083908 db-read-3 0.10 1 161
181 1775068978 2083909 db-read-4 0.20 2 181
182 1775068978 2083910 db-write 0.00 0 4
183 1775068978 2083911 event-worker 0.00 0 14
184 1775068988 2083904 lws-main 70.70 707 116907
185 1775068988 2083906 db-read-1 0.00 0 172
186 1775068988 2083907 db-read-2 0.00 0 164
187 1775068988 2083908 db-read-3 0.10 1 162
188 1775068988 2083909 db-read-4 0.00 0 181
189 1775068988 2083910 db-write 0.00 0 4
190 1775068988 2083911 event-worker 0.00 0 14
191 1775068998 2083904 lws-main 23.00 230 117137
192 1775068998 2083906 db-read-1 0.00 0 172
193 1775068998 2083907 db-read-2 0.00 0 164
194 1775068998 2083908 db-read-3 0.00 0 162
195 1775068998 2083909 db-read-4 0.00 0 181
196 1775068998 2083910 db-write 0.00 0 4
197 1775068998 2083911 event-worker 0.00 0 14
198 1775069008 2083904 lws-main 66.00 660 117797
199 1775069008 2083906 db-read-1 0.30 3 175
200 1775069008 2083907 db-read-2 0.40 4 168
201 1775069008 2083908 db-read-3 0.40 4 166
202 1775069008 2083909 db-read-4 0.30 3 184
203 1775069008 2083910 db-write 0.00 0 4
204 1775069008 2083911 event-worker 0.00 0 14
205 1775069018 2083904 lws-main 80.00 800 118597
206 1775069018 2083906 db-read-1 0.00 0 175
207 1775069018 2083907 db-read-2 0.00 0 168
208 1775069018 2083908 db-read-3 0.00 0 166
209 1775069018 2083909 db-read-4 0.00 0 184
210 1775069018 2083910 db-write 0.00 0 4
211 1775069018 2083911 event-worker 0.00 0 14
@@ -0,0 +1,14 @@
==========================================
Thread CPU Summary (avg + max over run)
==========================================
Run timestamp (UTC): 20260401_183846
Duration: 300s, Interval: 10s
2083904 lws-main 54.32 100.30
2083907 db-read-2 0.08 0.40
2083909 db-read-4 0.08 0.40
2083906 db-read-1 0.07 0.40
2083908 db-read-3 0.07 0.40
2083911 event-worker 0.01 0.20
2083910 db-write 0.00 0.00
TID THREAD AVG_CPU% MAX_CPU%
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%