ascii-chat 0.8.38
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 "main.h"
138#include "stats.h"
139#include "client.h"
140#include "render.h"
141#include <ascii-chat/common.h>
142#include <ascii-chat/common/buffer_sizes.h>
143#include <ascii-chat/buffer_pool.h>
144#include <ascii-chat/network/packet_queue.h>
145#include <ascii-chat/debug/lock.h>
146#include <ascii-chat/platform/init.h>
147
163
171mutex_t g_stats_mutex = {0};
172
181static bool g_stats_mutex_initialized = false;
182// Mutex to protect the initialization check (TOCTOU race prevention)
183static static_mutex_t g_stats_init_check_mutex = STATIC_MUTEX_INIT;
184
189int stats_init(void) {
190 int ret = mutex_init(&g_stats_mutex);
191 if (ret == 0) {
192 g_stats_mutex_initialized = true;
193 }
194 return ret;
195}
196
203void stats_cleanup(void) {
204 static_mutex_lock(&g_stats_init_check_mutex);
205 if (g_stats_mutex_initialized) {
206 g_stats_mutex_initialized = false; // Clear flag before destroying mutex
207 static_mutex_unlock(&g_stats_init_check_mutex);
209 } else {
210 static_mutex_unlock(&g_stats_init_check_mutex);
211 }
212}
213
214/* ============================================================================
215 * Statistics Collection and Reporting Thread
216 * ============================================================================
217 */
218
336void *stats_logger_thread(void *arg) {
337 (void)arg;
338
339 while (!atomic_load(&g_server_should_exit)) {
340 // Log buffer pool statistics every 10 seconds with fast exit checking (10ms intervals)
341 for (int i = 0; i < 1000 && !atomic_load(&g_server_should_exit); i++) {
342 platform_sleep_us(10 * US_PER_MS_INT); // 10ms sleep
343 }
344
345 // Check exit condition before proceeding with statistics logging
346 // Check multiple times to avoid accessing freed resources during shutdown.
347 if (atomic_load(&g_server_should_exit)) {
348 break;
349 }
350
351 // Collect all statistics data first
352#ifndef NDEBUG
353 char lock_debug_info[BUFFER_SIZE_MEDIUM] = {0};
354 // Check exit condition again before accessing lock_debug.
355 // lock_debug might be destroyed during shutdown
356 if (!atomic_load(&g_server_should_exit) && lock_debug_is_initialized()) {
357 uint64_t total_acquired = 0, total_released = 0;
358 uint32_t currently_held = 0;
359 lock_debug_get_stats(&total_acquired, &total_released, &currently_held);
360
361 safe_snprintf(lock_debug_info, sizeof(lock_debug_info),
362 "Historical Mutex/RWLock Statistics:\n"
363 " Total locks acquired: %llu\n"
364 " Total locks released: %llu\n"
365 " Currently held: %u\n"
366 " Net locks (acquired - released): %lld",
367 (unsigned long long)total_acquired, (unsigned long long)total_released, currently_held,
368 (long long)total_acquired - (long long)total_released);
369 }
370#endif
371
372 // Collect client statistics
373 // Check exit condition again before accessing rwlock.
374 // rwlock might be destroyed during shutdown.
375 if (atomic_load(&g_server_should_exit)) {
376 break;
377 }
378
379 rwlock_rdlock(&g_client_manager_rwlock);
380 int active_clients = 0;
381 int clients_with_audio = 0;
382 int clients_with_video = 0;
383 char client_details[BUFFER_SIZE_XLARGE] = {0};
384 int client_details_len = 0;
385
386 for (int i = 0; i < MAX_CLIENTS; i++) {
387 if (atomic_load(&g_client_manager.clients[i].active)) {
388 active_clients++;
389 if (g_client_manager.clients[i].audio_queue) {
390 clients_with_audio++;
391 }
392 if (g_client_manager.clients[i].outgoing_video_buffer) {
393 clients_with_video++;
394 }
395 }
396 }
397
398 // Collect per-client details
399 for (int i = 0; i < MAX_CLIENTS; i++) {
400 client_info_t *client = &g_client_manager.clients[i];
401 bool is_active = atomic_load(&client->active);
402 uint32_t client_id_snapshot = atomic_load(&client->client_id);
403
404 if (is_active && client_id_snapshot != 0) {
405 // Check audio queue stats
406 if (client->audio_queue) {
407 uint64_t enqueued, dequeued, dropped;
408 packet_queue_get_stats(client->audio_queue, &enqueued, &dequeued, &dropped);
409 if (enqueued > 0 || dequeued > 0 || dropped > 0) {
410 int len =
411 safe_snprintf(client_details + client_details_len, sizeof(client_details) - client_details_len,
412 " Client %u audio queue: %llu enqueued, %llu dequeued, %llu dropped", client_id_snapshot,
413 (unsigned long long)enqueued, (unsigned long long)dequeued, (unsigned long long)dropped);
414 if (len > 0 && client_details_len + len < (int)sizeof(client_details)) {
415 client_details_len += len;
416 }
417 }
418 }
419 // Check video buffer stats
420 if (client->outgoing_video_buffer) {
421 video_frame_stats_t stats;
422 video_frame_get_stats(client->outgoing_video_buffer, &stats);
423 if (stats.total_frames > 0) {
424 int len = safe_snprintf(client_details + client_details_len, sizeof(client_details) - client_details_len,
425 " Client %u video buffer: %llu frames, %llu dropped (%.1f%% drop rate)",
426 client_id_snapshot, (unsigned long long)stats.total_frames,
427 (unsigned long long)stats.dropped_frames, stats.drop_rate * 100.0f);
428 if (len > 0 && client_details_len + len < (int)sizeof(client_details)) {
429 client_details_len += len;
430 }
431 }
432 }
433 }
434 }
435 rwlock_rdunlock(&g_client_manager_rwlock);
436
437 // Single comprehensive log statement
438 if (client_details_len > 0) {
439 log_info("Stats: Clients: %d, Audio: %d, Video: %d\n%s", active_clients, clients_with_audio, clients_with_video,
440 client_details);
441 }
442 }
443
445
447
448 // Clean up thread-local error context before exit
450
451 return NULL;
452}
453
483 // Aggregate statistics from all active clients
484 uint64_t total_frames_sent = 0;
485 uint64_t total_frames_dropped = 0;
486
487 // Read-lock to safely iterate over all clients
488 rwlock_rdlock(&g_client_manager_rwlock);
489
490 for (int i = 0; i < MAX_CLIENTS; i++) {
491 client_info_t *client = &g_client_manager.clients[i];
492 if (atomic_load(&client->client_id) != 0 && atomic_load(&client->active)) {
493 // Aggregate frames sent to all clients
494 total_frames_sent += client->frames_sent;
495
496 // Get dropped frame statistics from outgoing video buffer
497 if (client->outgoing_video_buffer) {
498 video_frame_stats_t stats;
499 video_frame_get_stats(client->outgoing_video_buffer, &stats);
500 total_frames_dropped += stats.dropped_frames;
501 }
502 }
503 }
504
505 rwlock_rdunlock(&g_client_manager_rwlock);
506
507 // Update global statistics atomically
508 mutex_lock(&g_stats_mutex);
509 g_stats.frames_sent = total_frames_sent;
510 g_stats.frames_dropped = total_frames_dropped;
511
512 // Calculate frames_captured from frames_sent and frames_dropped
513 // frames_captured = frames_sent + frames_dropped (total output frames)
514 if (total_frames_sent > 0 || total_frames_dropped > 0) {
515 g_stats.frames_captured = total_frames_sent + total_frames_dropped;
516 }
517
518 mutex_unlock(&g_stats_mutex);
519}
520
564 // Use static_mutex to protect initialization check (TOCTOU race prevention)
565 // In debug builds, stats_init() may not be called (it's guarded by #ifdef NDEBUG)
566 // Use lock to prevent cleanup from destroying mutex while we're about to lock it
567 static_mutex_lock(&g_stats_init_check_mutex);
568 if (!g_stats_mutex_initialized) {
569 static_mutex_unlock(&g_stats_init_check_mutex);
570 return; // Mutex not initialized, skip logging
571 }
572 static_mutex_unlock(&g_stats_init_check_mutex);
573
574 // Now safe to use the mutex (it was initialized and not being cleaned up)
575 mutex_lock(&g_stats_mutex);
576 log_info("Server Statistics:\n"
577 " frames_captured=%llu\n"
578 " frames_sent=%llu\n"
579 " frames_dropped=%llu\n"
580 " Average FPS: capture=%.2f, send=%.2f",
581 (unsigned long long)g_stats.frames_captured, (unsigned long long)g_stats.frames_sent,
583 mutex_unlock(&g_stats_mutex);
584}
void asciichat_error_stats_print(void)
void asciichat_errno_destroy(void)
Per-client state management and lifecycle orchestration.
void lock_debug_get_stats(uint64_t *total_acquired, uint64_t *total_released, uint32_t *currently_held)
Definition lock.c:1372
bool lock_debug_is_initialized(void)
Definition lock.c:1380
void packet_queue_get_stats(packet_queue_t *queue, uint64_t *enqueued, uint64_t *dequeued, uint64_t *dropped)
void platform_sleep_us(unsigned int us)
Per-client rendering threads with rate limiting.
atomic_bool g_server_should_exit
Global atomic shutdown flag shared across all threads.
ascii-chat Server Mode Entry Point Header
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.
server_stats_t g_stats
Global server statistics structure.
Definition stats.c:162
void log_server_stats(void)
Log comprehensive server statistics summary.
Definition stats.c:563
void * stats_logger_thread(void *arg)
Main statistics collection and reporting thread function.
Definition stats.c:336
int stats_init(void)
Initialize the stats mutex.
Definition stats.c:189
mutex_t g_stats_mutex
Mutex protecting global server statistics.
Definition stats.c:171
void stats_cleanup(void)
Cleanup the stats mutex.
Definition stats.c:203
void update_server_stats(void)
Update global server statistics (placeholder)
Definition stats.c:482
Server performance statistics tracking.
client_info_t clients[MAX_CLIENTS]
Array of client_info_t structures (backing storage)
Definition client.h:65
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
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456
int mutex_init(mutex_t *mutex)
Definition threading.c:16
int mutex_destroy(mutex_t *mutex)
Definition threading.c:21
void video_frame_get_stats(video_frame_buffer_t *vfb, video_frame_stats_t *stats)