ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
stats.c
Go to the documentation of this file.
1
133#include <stdatomic.h>
134#include <stdio.h>
135#include <string.h>
136#include <time.h>
137#include "stats.h"
138#include "client.h"
139#include "render.h"
140#include "common.h"
141#include "buffer_pool.h"
142#include "network/packet_queue.h"
143#include "debug/lock.h"
144
160
169
178static bool g_stats_mutex_initialized = false;
179
184int stats_init(void) {
185 int ret = mutex_init(&g_stats_mutex);
186 if (ret == 0) {
187 g_stats_mutex_initialized = true;
188 }
189 return ret;
190}
191
195void stats_cleanup(void) {
196 if (g_stats_mutex_initialized) {
198 g_stats_mutex_initialized = false;
199 }
200}
201
208extern atomic_bool g_server_should_exit;
209
210/* ============================================================================
211 * Statistics Collection and Reporting Thread
212 * ============================================================================
213 */
214
332void *stats_logger_thread(void *arg) {
333 (void)arg;
334
335 while (!atomic_load(&g_server_should_exit)) {
336 // Log buffer pool statistics every 10 seconds with fast exit checking (10ms intervals)
337 for (int i = 0; i < 1000 && !atomic_load(&g_server_should_exit); i++) {
338 platform_sleep_usec(10000); // 10ms sleep
339 }
340
341 // Check exit condition before proceeding with statistics logging
342 // CRITICAL: Check multiple times to avoid accessing freed resources during shutdown
343 if (atomic_load(&g_server_should_exit)) {
344 break;
345 }
346
347 // Collect all statistics data first
348#ifndef NDEBUG
349 char lock_debug_info[512] = {0};
350 // CRITICAL: Check exit condition again before accessing lock_debug
351 // lock_debug might be destroyed during shutdown
352 if (!atomic_load(&g_server_should_exit) && lock_debug_is_initialized()) {
353 uint64_t total_acquired = 0, total_released = 0;
354 uint32_t currently_held = 0;
355 lock_debug_get_stats(&total_acquired, &total_released, &currently_held);
356
357 safe_snprintf(lock_debug_info, sizeof(lock_debug_info),
358 "Historical Mutex/RWLock Statistics:\n"
359 " Total locks acquired: %llu\n"
360 " Total locks released: %llu\n"
361 " Currently held: %u\n"
362 " Net locks (acquired - released): %lld",
363 (unsigned long long)total_acquired, (unsigned long long)total_released, currently_held,
364 (long long)total_acquired - (long long)total_released);
365 }
366#endif
367
368 // Collect client statistics
369 // CRITICAL: Check exit condition again before accessing rwlock
370 // rwlock might be destroyed during shutdown
371 if (atomic_load(&g_server_should_exit)) {
372 break;
373 }
374
376 int active_clients = 0;
377 int clients_with_audio = 0;
378 int clients_with_video = 0;
379 char client_details[2048] = {0};
380 int client_details_len = 0;
381
382 for (int i = 0; i < MAX_CLIENTS; i++) {
383 if (atomic_load(&g_client_manager.clients[i].active)) {
384 active_clients++;
386 clients_with_audio++;
387 }
389 clients_with_video++;
390 }
391 }
392 }
393
394 // Collect per-client details
395 for (int i = 0; i < MAX_CLIENTS; i++) {
397 bool is_active = atomic_load(&client->active);
398 uint32_t client_id_snapshot = atomic_load(&client->client_id);
399
400 if (is_active && client_id_snapshot != 0) {
401 // Check audio queue stats
402 if (client->audio_queue) {
403 uint64_t enqueued, dequeued, dropped;
404 packet_queue_get_stats(client->audio_queue, &enqueued, &dequeued, &dropped);
405 if (enqueued > 0 || dequeued > 0 || dropped > 0) {
406 int len =
407 snprintf(client_details + client_details_len, sizeof(client_details) - client_details_len,
408 " Client %u audio queue: %llu enqueued, %llu dequeued, %llu dropped", client_id_snapshot,
409 (unsigned long long)enqueued, (unsigned long long)dequeued, (unsigned long long)dropped);
410 if (len > 0 && client_details_len + len < (int)sizeof(client_details)) {
411 client_details_len += len;
412 }
413 }
414 }
415 // Check video buffer stats
416 if (client->outgoing_video_buffer) {
419 if (stats.total_frames > 0) {
420 int len = snprintf(client_details + client_details_len, sizeof(client_details) - client_details_len,
421 " Client %u video buffer: %llu frames, %llu dropped (%.1f%% drop rate)",
422 client_id_snapshot, (unsigned long long)stats.total_frames,
423 (unsigned long long)stats.dropped_frames, stats.drop_rate * 100.0f);
424 if (len > 0 && client_details_len + len < (int)sizeof(client_details)) {
425 client_details_len += len;
426 }
427 }
428 }
429 }
430 }
432
433 // Single comprehensive log statement
434 if (client_details_len > 0) {
435 log_info("Stats: Clients: %d, Audio: %d, Video: %d\n%s", active_clients, clients_with_audio, clients_with_video,
436 client_details);
437 }
438 }
439
441
443
444 // Clean up thread-local error context before exit
446
447 return NULL;
448}
449
479 // Aggregate statistics from all active clients
480 uint64_t total_frames_sent = 0;
481 uint64_t total_frames_dropped = 0;
482
483 // Read-lock to safely iterate over all clients
485
486 for (int i = 0; i < MAX_CLIENTS; i++) {
488 if (atomic_load(&client->client_id) != 0 && atomic_load(&client->active)) {
489 // Aggregate frames sent to all clients
490 total_frames_sent += client->frames_sent;
491
492 // Get dropped frame statistics from outgoing video buffer
493 if (client->outgoing_video_buffer) {
496 total_frames_dropped += stats.dropped_frames;
497 }
498 }
499 }
500
502
503 // Update global statistics atomically
505 g_stats.frames_sent = total_frames_sent;
506 g_stats.frames_dropped = total_frames_dropped;
507
508 // Calculate frames_captured from frames_sent and frames_dropped
509 // frames_captured = frames_sent + frames_dropped (total output frames)
510 if (total_frames_sent > 0 || total_frames_dropped > 0) {
511 g_stats.frames_captured = total_frames_sent + total_frames_dropped;
512 }
513
515}
516
560 // Check if stats mutex is initialized before trying to lock it
561 // In debug builds, stats_init() may not be called (it's guarded by #ifdef NDEBUG)
562 // Skip logging if mutex is not initialized to avoid crashing
563 if (!g_stats_mutex_initialized) {
564 return; // Mutex not initialized, skip logging
565 }
567 log_info("Server Statistics:\n"
568 " frames_captured=%llu\n"
569 " frames_sent=%llu\n"
570 " frames_dropped=%llu\n"
571 " Average FPS: capture=%.2f, send=%.2f",
572 (unsigned long long)g_stats.frames_captured, (unsigned long long)g_stats.frames_sent,
575}
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
unsigned int uint32_t
Definition common.h:58
unsigned long long uint64_t
Definition common.h:59
void asciichat_error_stats_print(void)
Print error statistics to stderr.
void asciichat_errno_cleanup(void)
Cleanup error system resources.
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
Definition limits.h:23
#define log_info(...)
Log an INFO message.
void packet_queue_get_stats(packet_queue_t *queue, uint64_t *enqueued, uint64_t *dequeued, uint64_t *dropped)
Get queue statistics.
int mutex_init(mutex_t *mutex)
Initialize a mutex.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe version of snprintf that ensures null termination.
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
Definition mutex.h:140
pthread_mutex_t mutex_t
Mutex type (POSIX: pthread_mutex_t)
Definition mutex.h:38
#define rwlock_rdlock(lock)
Acquire a read lock (with debug tracking in debug builds)
Definition rwlock.h:194
void platform_sleep_usec(unsigned int usec)
High-precision sleep function with microsecond precision.
bool lock_debug_is_initialized(void)
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
Definition mutex.h:175
#define rwlock_rdunlock(lock)
Release a read lock (with debug tracking in debug builds)
Definition rwlock.h:231
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
void video_frame_get_stats(video_frame_buffer_t *vfb, video_frame_stats_t *stats)
Get frame statistics for quality monitoring.
🔒 Lock debugging and deadlock detection system for ascii-chat
📬 Thread-safe packet queue system for per-client send threads
Per-client rendering threads with rate limiting.
rwlock_t g_client_manager_rwlock
Reader-writer lock protecting the global client manager.
client_manager_t g_client_manager
Global client manager singleton - central coordination point.
Per-client state management and lifecycle orchestration.
server_stats_t g_stats
Global server statistics structure.
Definition stats.c:159
void log_server_stats(void)
Log comprehensive server statistics summary.
Definition stats.c:559
void * stats_logger_thread(void *arg)
Main statistics collection and reporting thread function.
Definition stats.c:332
int stats_init(void)
Initialize the stats mutex.
Definition stats.c:184
atomic_bool g_server_should_exit
Global shutdown flag from main.c - coordinate statistics thread termination.
mutex_t g_stats_mutex
Mutex protecting global server statistics.
Definition stats.c:168
void stats_cleanup(void)
Cleanup the stats mutex.
Definition stats.c:195
void update_server_stats(void)
Update global server statistics (placeholder)
Definition stats.c:478
Server performance statistics tracking.
Per-client state structure for server-side client management.
atomic_uint client_id
video_frame_buffer_t * outgoing_video_buffer
packet_queue_t * audio_queue
atomic_bool active
client_info_t clients[MAX_CLIENTS]
Array of client_info_t structures (backing storage)
Server performance statistics structure.
Definition stats.h:47
uint64_t frames_dropped
Definition stats.h:50
double avg_capture_fps
Definition stats.h:52
uint64_t frames_captured
Definition stats.h:48
double avg_send_fps
Definition stats.h:53
uint64_t frames_sent
Definition stats.h:49
Frame statistics structure.
uint64_t dropped_frames
Total frames dropped (due to buffer full or errors)
uint64_t total_frames
Total frames received since creation.
float drop_rate
Frame drop rate (dropped_frames / total_frames, 0.0-1.0)
⏱️ High-precision timing utilities using sokol_time.h and uthash