#include "kcp_session_stats.h" static int kcp_session_stats_append(char **line, size_t *len, const char *suffix) { size_t suffix_len; char *next; if (line == NULL || len == NULL || suffix == NULL) { errno = EINVAL; return -1; } suffix_len = strlen(suffix); next = (char *) realloc(*line, *len + suffix_len + 1U); if (next == NULL) { return -1; } memcpy(next + *len, suffix, suffix_len + 1U); *line = next; *len += suffix_len; return 0; } static int kcp_session_stats_appendf(char **line, size_t *len, const char *fmt, ...) { va_list args; va_list copy; int needed; char *buffer; if (line == NULL || len == NULL || fmt == NULL) { errno = EINVAL; return -1; } va_start(args, fmt); va_copy(copy, args); needed = vsnprintf(NULL, 0, fmt, copy); va_end(copy); if (needed < 0) { va_end(args); return -1; } buffer = (char *) malloc((size_t) needed + 1U); if (buffer == NULL) { va_end(args); return -1; } vsnprintf(buffer, (size_t) needed + 1U, fmt, args); va_end(args); if (kcp_session_stats_append(line, len, buffer) != 0) { free(buffer); return -1; } free(buffer); return 0; } kcp_session_stats_logger_t *kcp_session_stats_open_jsonl(const char *path) { kcp_session_stats_logger_t *logger; FILE *file; if (path == NULL || path[0] == '\0') { return NULL; } if (omni_ensure_parent_dir(path) != 0) { return NULL; } file = fopen(path, "ab"); if (file == NULL) { return NULL; } logger = (kcp_session_stats_logger_t *) calloc(1, sizeof(*logger)); if (logger == NULL) { fclose(file); return NULL; } omni_file_logger_init_path(&logger->file_logger, file, path, 0); logger->enabled = 1; return logger; } void kcp_session_stats_close(kcp_session_stats_logger_t *logger) { if (logger == NULL) { return; } if (logger->file_logger.file != NULL) { fclose(logger->file_logger.file); } omni_file_logger_destroy(&logger->file_logger); free(logger); } int kcp_session_stats_log(kcp_session_stats_logger_t *logger, const kcp_session_stats_record_t *record) { char *record_type = NULL; char *node_role = NULL; char *node_id = NULL; char *local_addr = NULL; char *remote_addr = NULL; char *sample_reason = NULL; char *line = NULL; size_t line_len = 0; if (logger == NULL || record == NULL || !logger->enabled) { return 0; } record_type = omni_json_escape(record->record_type); node_role = omni_json_escape(record->node_role); node_id = omni_json_escape(record->node_id); local_addr = omni_json_escape(record->local_addr); remote_addr = omni_json_escape(record->remote_addr); sample_reason = omni_json_escape(record->sample_reason); if (record_type == NULL || node_role == NULL || node_id == NULL || local_addr == NULL || remote_addr == NULL || sample_reason == NULL) { free(record_type); free(node_role); free(node_id); free(local_addr); free(remote_addr); free(sample_reason); return -1; } line = omni_strdup(""); if (line == NULL) { free(record_type); free(node_role); free(node_id); free(local_addr); free(remote_addr); free(sample_reason); return -1; } if (kcp_session_stats_appendf(&line, &line_len, "{\"record_type\":\"%s\",\"node_role\":\"%s\",\"node_id\":\"%s\",\"ts_unix_nano\":%" PRId64 ",\"sample_reason\":\"%s\"", record_type, node_role, node_id, record->ts_unix_nano, sample_reason) != 0) { goto cleanup; } if (record->local_addr[0] != '\0' && kcp_session_stats_appendf(&line, &line_len, ",\"local_addr\":\"%s\"", local_addr) != 0) { goto cleanup; } if (record->remote_addr[0] != '\0' && kcp_session_stats_appendf(&line, &line_len, ",\"remote_addr\":\"%s\"", remote_addr) != 0) { goto cleanup; } if (record->has_conv && kcp_session_stats_appendf(&line, &line_len, ",\"conv\":%u", record->conv) != 0) { goto cleanup; } if (record->has_rto_ms && kcp_session_stats_appendf(&line, &line_len, ",\"rto_ms\":%u", record->rto_ms) != 0) { goto cleanup; } if (record->has_srtt_ms && kcp_session_stats_appendf(&line, &line_len, ",\"srtt_ms\":%d", record->srtt_ms) != 0) { goto cleanup; } if (record->has_min_srtt_ms && kcp_session_stats_appendf(&line, &line_len, ",\"min_srtt_ms\":%d", record->min_srtt_ms) != 0) { goto cleanup; } if (record->has_srttvar_ms && kcp_session_stats_appendf(&line, &line_len, ",\"srttvar_ms\":%d", record->srttvar_ms) != 0) { goto cleanup; } if (record->has_last_feedback_age_ms && kcp_session_stats_appendf(&line, &line_len, ",\"last_feedback_age_ms\":%u", record->last_feedback_age_ms) != 0) { goto cleanup; } if (record->has_snd_wnd && kcp_session_stats_appendf(&line, &line_len, ",\"snd_wnd\":%u", record->snd_wnd) != 0) { goto cleanup; } if (record->has_rmt_wnd && kcp_session_stats_appendf(&line, &line_len, ",\"rmt_wnd\":%u", record->rmt_wnd) != 0) { goto cleanup; } if (record->has_inflight && kcp_session_stats_appendf(&line, &line_len, ",\"inflight\":%u", record->inflight) != 0) { goto cleanup; } if (record->has_window_limit && kcp_session_stats_appendf(&line, &line_len, ",\"window_limit\":%u", record->window_limit) != 0) { goto cleanup; } if (record->has_window_pressure_pct && kcp_session_stats_appendf(&line, &line_len, ",\"window_pressure_pct\":%.3f", record->window_pressure_pct) != 0) { goto cleanup; } if (record->has_bytes_sent && kcp_session_stats_appendf(&line, &line_len, ",\"bytes_sent\":%" PRIu64, record->bytes_sent) != 0) { goto cleanup; } if (record->has_bytes_received && kcp_session_stats_appendf(&line, &line_len, ",\"bytes_received\":%" PRIu64, record->bytes_received) != 0) { goto cleanup; } if (record->has_in_pkts && kcp_session_stats_appendf(&line, &line_len, ",\"in_pkts\":%" PRIu64, record->in_pkts) != 0) { goto cleanup; } if (record->has_out_pkts && kcp_session_stats_appendf(&line, &line_len, ",\"out_pkts\":%" PRIu64, record->out_pkts) != 0) { goto cleanup; } if (record->has_in_segs && kcp_session_stats_appendf(&line, &line_len, ",\"in_segs\":%" PRIu64, record->in_segs) != 0) { goto cleanup; } if (record->has_out_segs && kcp_session_stats_appendf(&line, &line_len, ",\"out_segs\":%" PRIu64, record->out_segs) != 0) { goto cleanup; } if (record->has_retrans_segs && kcp_session_stats_appendf(&line, &line_len, ",\"retrans_segs\":%" PRIu64, record->retrans_segs) != 0) { goto cleanup; } if (record->has_fast_retrans_segs && kcp_session_stats_appendf(&line, &line_len, ",\"fast_retrans_segs\":%" PRIu64, record->fast_retrans_segs) != 0) { goto cleanup; } if (record->has_early_retrans_segs && kcp_session_stats_appendf(&line, &line_len, ",\"early_retrans_segs\":%" PRIu64, record->early_retrans_segs) != 0) { goto cleanup; } if (record->has_lost_segs && kcp_session_stats_appendf(&line, &line_len, ",\"lost_segs\":%" PRIu64, record->lost_segs) != 0) { goto cleanup; } if (record->has_repeat_segs && kcp_session_stats_appendf(&line, &line_len, ",\"repeat_segs\":%" PRIu64, record->repeat_segs) != 0) { goto cleanup; } if (record->has_in_errs && kcp_session_stats_appendf(&line, &line_len, ",\"in_errs\":%" PRIu64, record->in_errs) != 0) { goto cleanup; } if (record->has_kcp_in_errs && kcp_session_stats_appendf(&line, &line_len, ",\"kcp_in_errs\":%" PRIu64, record->kcp_in_errs) != 0) { goto cleanup; } if (record->has_ring_buffer_snd_queue && kcp_session_stats_appendf(&line, &line_len, ",\"ring_buffer_snd_queue\":%" PRIu64, record->ring_buffer_snd_queue) != 0) { goto cleanup; } if (record->has_ring_buffer_rcv_queue && kcp_session_stats_appendf(&line, &line_len, ",\"ring_buffer_rcv_queue\":%" PRIu64, record->ring_buffer_rcv_queue) != 0) { goto cleanup; } if (record->has_ring_buffer_snd_buffer && kcp_session_stats_appendf(&line, &line_len, ",\"ring_buffer_snd_buffer\":%" PRIu64, record->ring_buffer_snd_buffer) != 0) { goto cleanup; } if (record->has_curr_estab && kcp_session_stats_appendf(&line, &line_len, ",\"curr_estab\":%" PRIu64, record->curr_estab) != 0) { goto cleanup; } if (kcp_session_stats_append(&line, &line_len, "}") != 0) { goto cleanup; } free(record_type); free(node_role); free(node_id); free(local_addr); free(remote_addr); free(sample_reason); if (omni_file_logger_write_line(&logger->file_logger, line) != 0) { free(line); return -1; } free(line); return 0; cleanup: free(record_type); free(node_role); free(node_id); free(local_addr); free(remote_addr); free(sample_reason); free(line); return -1; }