#include #include "call.h" #include "statistics.h" #include "graphite.h" #include "main.h" #include "control_ng.h" struct timeval rtpe_started; mutex_t rtpe_codec_stats_lock; GHashTable *rtpe_codec_stats; // op can be CMC_INCREMENT or CMC_DECREMENT // check not to multiple decrement or increment void statistics_update_ip46_inc_dec(struct call* c, int op) { // already incremented if (op == CMC_INCREMENT && c->is_call_media_counted) { return ; // already decremented } else if (op == CMC_DECREMENT && !c->is_call_media_counted) { return ; } // offer is ipv4 only if (c->is_ipv4_media_offer && !c->is_ipv6_media_offer) { // answer is ipv4 only if (c->is_ipv4_media_answer && !c->is_ipv6_media_answer) { RTPE_GAUGE_ADD(ipv4_sessions, op == CMC_INCREMENT ? 1 : -1); // answer is ipv6 only } else if (!c->is_ipv4_media_answer && c->is_ipv6_media_answer) { RTPE_GAUGE_ADD(mixed_sessions, op == CMC_INCREMENT ? 1 : -1); // answer is both ipv4 and ipv6 } else if (c->is_ipv4_media_answer && c->is_ipv6_media_answer) { RTPE_GAUGE_ADD(ipv4_sessions, op == CMC_INCREMENT ? 1 : -1); // answer is neither ipv4 nor ipv6 } else { return ; } // offer is ipv6 only } else if (!c->is_ipv4_media_offer && c->is_ipv6_media_offer) { // answer is ipv4 only if (c->is_ipv4_media_answer && !c->is_ipv6_media_answer) { RTPE_GAUGE_ADD(mixed_sessions, op == CMC_INCREMENT ? 1 : -1); // answer is ipv6 only } else if (!c->is_ipv4_media_answer && c->is_ipv6_media_answer) { RTPE_GAUGE_ADD(ipv6_sessions, op == CMC_INCREMENT ? 1 : -1); // answer is both ipv4 and ipv6 } else if (c->is_ipv4_media_answer && c->is_ipv6_media_answer) { RTPE_GAUGE_ADD(ipv6_sessions, op == CMC_INCREMENT ? 1 : -1); // answer is neither ipv4 nor ipv6 } else { return ; } // offer is both ipv4 and ipv6 } else if (c->is_ipv4_media_offer && c->is_ipv6_media_offer) { // answer is ipv4 only if (c->is_ipv4_media_answer && !c->is_ipv6_media_answer) { RTPE_GAUGE_ADD(ipv4_sessions, op == CMC_INCREMENT ? 1 : -1); // answer is ipv6 only } else if (!c->is_ipv4_media_answer && c->is_ipv6_media_answer) { RTPE_GAUGE_ADD(ipv6_sessions, op == CMC_INCREMENT ? 1 : -1); // answer is both ipv4 and ipv6 } else if (c->is_ipv4_media_answer && c->is_ipv6_media_answer) { RTPE_GAUGE_ADD(mixed_sessions, op == CMC_INCREMENT ? 1 : -1); // answer is neither ipv4 nor ipv6 } else { return ; } // offer is neither ipv4 nor ipv6 } else { return ; } // mark if incremented or decremented c->is_call_media_counted = (op == CMC_INCREMENT) ? 1 : 0; } void statistics_update_foreignown_dec(struct call* c) { if (IS_FOREIGN_CALL(c)) { RTPE_GAUGE_DEC(foreign_sessions); } } void statistics_update_foreignown_inc(struct call* c) { if (IS_FOREIGN_CALL(c)) { /* foreign call*/ RTPE_GAUGE_INC(foreign_sessions); RTPE_STATS_INC(foreign_sess); } } void statistics_update_oneway(struct call* c) { struct packet_stream *ps = NULL, *ps2 = NULL; struct call_monologue *ml; struct call_media *md; GList *k, *o; int found = 0; GList *l; // --- for statistics getting one way stream or no relay at all int total_nopacket_relayed_sess = 0; for (l = c->monologues.head; l; l = l->next) { ml = l->data; // --- go through partner ml and search the RTP for (k = ml->medias.head; k; k = k->next) { md = k->data; for (o = md->streams.head; o; o = o->next) { ps = o->data; if (PS_ISSET(ps, RTP)) { // --- only RTP is interesting found = 1; break; } } if (found) { break; } } if (!found) ps = NULL; if (ps) { struct sink_handler *sh = g_queue_peek_head(&ps->rtp_sinks); ps2 = sh ? sh->sink : NULL; } if (ps && ps2 && atomic64_get(&ps2->stats_in.packets)==0) { if (atomic64_get(&ps->stats_in.packets)!=0 && IS_OWN_CALL(c)){ if (atomic64_get(&ps->stats_in.packets)!=0) RTPE_STATS_INC(oneway_stream_sess); } else { total_nopacket_relayed_sess++; } } } if (IS_OWN_CALL(c)) RTPE_STATS_INC(nopacket_relayed_sess); if (c->monologues.head) { ml = c->monologues.head->data; if (IS_OWN_CALL(c)) { if (ml->term_reason==TIMEOUT) RTPE_STATS_INC(timeout_sess); else if (ml->term_reason==SILENT_TIMEOUT) RTPE_STATS_INC(silent_timeout_sess); else if (ml->term_reason==OFFER_TIMEOUT) RTPE_STATS_INC(offer_timeout_sess); else if (ml->term_reason==REGULAR) RTPE_STATS_INC(regular_term_sess); else if (ml->term_reason==FORCED) RTPE_STATS_INC(forced_term_sess); RTPE_STATS_INC(managed_sess); if (!c->destroyed.tv_sec) c->destroyed = rtpe_now; long long duration = timeval_diff(&c->destroyed, &c->created); RTPE_STATS_ADD(call_duration, duration); duration /= 1000; // millisecond precision for the squared value to avoid overflows RTPE_STATS_ADD(call_duration2, duration * duration); } if (ml->term_reason==FINAL_TIMEOUT) RTPE_STATS_INC(final_timeout_sess); } } #pragma GCC diagnostic ignored "-Wformat-zero-length" #define SM_PUSH(ret, m) \ do { \ struct stats_metric *last = NULL; \ for (GList *l_last = ret->tail; l_last; l_last = l_last->prev) { \ last = l_last->data; \ if (last->label) \ break; \ last = NULL; \ } \ if (!m->is_bracket && last) { \ if (!last->is_bracket || last->is_close_bracket) \ m->is_follow_up = 1; \ } \ else if (m->is_bracket && !m->is_close_bracket && last && last->is_close_bracket) \ m->is_follow_up = 1; \ g_queue_push_tail(ret, m); \ } while (0) #define PROM(name, type) \ do { \ struct stats_metric *last = g_queue_peek_tail(ret); \ last->prom_name = name; \ last->prom_type = type; \ } while (0) #define PROMLAB(fmt, ...) \ do { \ struct stats_metric *last = g_queue_peek_tail(ret); \ last->prom_label = g_strdup_printf(fmt, ## __VA_ARGS__); \ } while (0) #define METRICva(lb, dsc, fmt1, fmt2, ...) \ do { \ struct stats_metric *m = g_slice_alloc0(sizeof(*m)); \ m->label = g_strdup(lb); \ m->descr = g_strdup(dsc); \ if (fmt1) \ m->value_short = g_strdup_printf(fmt1, ## __VA_ARGS__); \ if (fmt2) \ m->value_long = g_strdup_printf(fmt2, ## __VA_ARGS__); \ SM_PUSH(ret, m); \ } while (0) #define METRIC(lb, dsc, fmt1, fmt2, arg) \ do { \ struct stats_metric *m = g_slice_alloc0(sizeof(*m)); \ m->label = g_strdup(lb); \ m->descr = g_strdup(dsc); \ if (fmt1) \ m->value_short = g_strdup_printf(fmt1, arg); \ if (fmt2) \ m->value_long = g_strdup_printf(fmt2, arg); \ if (fmt1 && fmt2 && !strcmp(fmt1, fmt2)) { \ if (!strcmp(fmt1, "%u") || \ !strcmp(fmt1, "%lu") || \ !strcmp(fmt1, "%llu") || \ !strcmp(fmt1, "%i") || \ !strcmp(fmt1, "%li") || \ !strcmp(fmt1, "%lli") || \ !strcmp(fmt1, "%d") || \ !strcmp(fmt1, "%ld") || \ !strcmp(fmt1, "%lld")) \ { \ m->is_int = 1; \ m->int_value = arg; \ } \ } \ SM_PUSH(ret, m); \ } while (0) #define METRICl(dsc, fmt2, ...) \ do { \ struct stats_metric *m = g_slice_alloc0(sizeof(*m)); \ m->descr = g_strdup(dsc); \ m->value_long = g_strdup_printf(fmt2, ## __VA_ARGS__); \ SM_PUSH(ret, m); \ } while (0) #define METRICsva(lb, fmt1, ...) \ do { \ struct stats_metric *m = g_slice_alloc0(sizeof(*m)); \ m->label = g_strdup(lb); \ m->value_short = g_strdup_printf(fmt1, ## __VA_ARGS__); \ SM_PUSH(ret, m); \ } while (0) #define METRICs(lb, fmt1, arg) \ do { \ struct stats_metric *m = g_slice_alloc0(sizeof(*m)); \ m->label = g_strdup(lb); \ m->value_short = g_strdup_printf(fmt1, arg); \ if (fmt1) { \ if (!strcmp(fmt1, "%u") || \ !strcmp(fmt1, "%lu") || \ !strcmp(fmt1, "%llu") || \ !strcmp(fmt1, "%i") || \ !strcmp(fmt1, "%li") || \ !strcmp(fmt1, "%lli") || \ !strcmp(fmt1, "%d") || \ !strcmp(fmt1, "%ld") || \ !strcmp(fmt1, "%lld")) \ { \ m->is_int = 1; \ m->int_value = arg; \ } \ } \ SM_PUSH(ret, m); \ } while (0) #define HEADER(fmt1, fmt2, ...) \ do { \ struct stats_metric *m = g_slice_alloc0(sizeof(*m)); \ if (fmt1) \ m->label = g_strdup_printf(fmt1, ## __VA_ARGS__); \ if (fmt2) \ m->descr = g_strdup_printf(fmt2, ## __VA_ARGS__); \ if (m->label && ( \ m->label[0] == '[' \ || m->label[0] == '{' \ || m->label[0] == '}' \ || m->label[0] == ']') \ && m->label[1] == 0) \ { \ m->is_bracket = 1; \ if (m->label[0] == '}' || m->label[0] == ']') \ m->is_close_bracket = 1; \ if (m->label[0] == '{' || m->label[0] == '}') \ m->is_brace = 1; \ } \ SM_PUSH(ret, m); \ } while (0) #define HEADERl(fmt2, ...) \ do { \ struct stats_metric *m = g_slice_alloc0(sizeof(*m)); \ m->descr = g_strdup_printf(fmt2, ## __VA_ARGS__); \ SM_PUSH(ret, m); \ } while (0) GQueue *statistics_gather_metrics(void) { GQueue *ret = g_queue_new(); double calls_dur_iv; uint64_t cur_sessions, num_sessions, min_sess_iv, max_sess_iv; HEADER("{", ""); HEADER("currentstatistics", "Statistics over currently running sessions:"); HEADER("{", ""); rwlock_lock_r(&rtpe_callhash_lock); cur_sessions = g_hash_table_size(rtpe_callhash); rwlock_unlock_r(&rtpe_callhash_lock); METRIC("sessionsown", "Owned sessions", UINT64F, UINT64F, cur_sessions - atomic64_get(&rtpe_stats_gauge.foreign_sessions)); PROM("sessions", "gauge"); PROMLAB("type=\"own\""); METRIC("sessionsforeign", "Foreign sessions", UINT64F, UINT64F, atomic64_get(&rtpe_stats_gauge.foreign_sessions)); PROM("sessions", "gauge"); PROMLAB("type=\"foreign\""); METRIC("sessionstotal", "Total sessions", UINT64F, UINT64F, cur_sessions); METRIC("transcodedmedia", "Transcoded media", UINT64F, UINT64F, atomic64_get(&rtpe_stats_gauge.transcoded_media)); PROM("transcoded_media", "gauge"); METRIC("packetrate_user", "Packets per second (userspace)", UINT64F, UINT64F, atomic64_get(&rtpe_stats.intv.packets_user)); METRIC("byterate_user", "Bytes per second (userspace)", UINT64F, UINT64F, atomic64_get(&rtpe_stats.intv.bytes_user)); METRIC("errorrate_user", "Errors per second (userspace)", UINT64F, UINT64F, atomic64_get(&rtpe_stats.intv.errors_user)); METRIC("packetrate_kernel", "Packets per second (kernel)", UINT64F, UINT64F, atomic64_get(&rtpe_stats.intv.packets_kernel)); METRIC("byterate_kernel", "Bytes per second (kernel)", UINT64F, UINT64F, atomic64_get(&rtpe_stats.intv.bytes_kernel)); METRIC("errorrate_kernel", "Errors per second (kernel)", UINT64F, UINT64F, atomic64_get(&rtpe_stats.intv.errors_kernel)); METRIC("packetrate", "Packets per second (total)", UINT64F, UINT64F, atomic64_get(&rtpe_stats.intv.packets_user) + atomic64_get(&rtpe_stats.intv.packets_kernel)); METRIC("byterate", "Bytes per second (total)", UINT64F, UINT64F, atomic64_get(&rtpe_stats.intv.bytes_user) + atomic64_get(&rtpe_stats.intv.bytes_kernel)); METRIC("errorrate", "Errors per second (total)", UINT64F, UINT64F, atomic64_get(&rtpe_stats.intv.errors_user) + atomic64_get(&rtpe_stats.intv.errors_kernel)); METRIC("media_userspace", "Userspace-only media streams", UINT64F, UINT64F, atomic64_get(&rtpe_stats_gauge.userspace_streams)); PROM("mediastreams", "gauge"); PROMLAB("type=\"userspace\""); METRIC("media_kernel", "Kernel-only media streams", UINT64F, UINT64F, atomic64_get(&rtpe_stats_gauge.kernel_only_streams)); PROM("mediastreams", "gauge"); PROMLAB("type=\"kernel\""); METRIC("media_mixed", "Mixed kernel/userspace media streams", UINT64F, UINT64F, atomic64_get(&rtpe_stats_gauge.kernel_user_streams)); PROM("mediastreams", "gauge"); PROMLAB("type=\"mixed\""); num_sessions = atomic64_get(&rtpe_stats_cumulative.managed_sess); uint64_t total_duration = atomic64_get(&rtpe_stats_cumulative.call_duration); uint64_t avg_us = num_sessions ? total_duration / num_sessions : 0; HEADER("}", ""); HEADER("totalstatistics", "Total statistics (does not include current running sessions):"); HEADER("{", ""); METRIC("uptime", "Uptime of rtpengine", "%llu", "%llu seconds", (long long) timeval_diff(&rtpe_now, &rtpe_started) / 1000000); PROM("uptime_seconds", "gauge"); METRIC("managedsessions", "Total managed sessions", UINT64F, UINT64F, num_sessions); PROM("sessions_total", "counter"); METRIC("rejectedsessions", "Total rejected sessions", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.rejected_sess)); PROM("closed_sessions_total", "counter"); PROMLAB("reason=\"rejected\""); METRIC("timeoutsessions", "Total timed-out sessions via TIMEOUT", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.timeout_sess)); PROM("closed_sessions_total", "counter"); PROMLAB("reason=\"timeout\""); METRIC("silenttimeoutsessions", "Total timed-out sessions via SILENT_TIMEOUT", UINT64F, UINT64F,atomic64_get(&rtpe_stats_cumulative.silent_timeout_sess)); PROM("closed_sessions_total", "counter"); PROMLAB("reason=\"silent_timeout\""); METRIC("finaltimeoutsessions", "Total timed-out sessions via FINAL_TIMEOUT", UINT64F, UINT64F,atomic64_get(&rtpe_stats_cumulative.final_timeout_sess)); PROM("closed_sessions_total", "counter"); PROMLAB("reason=\"final_timeout\""); METRIC("offertimeoutsessions", "Total timed-out sessions via OFFER_TIMEOUT", UINT64F, UINT64F,atomic64_get(&rtpe_stats_cumulative.offer_timeout_sess)); PROM("closed_sessions_total", "counter"); PROMLAB("reason=\"offer_timeout\""); METRIC("regularterminatedsessions", "Total regular terminated sessions", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.regular_term_sess)); PROM("closed_sessions_total", "counter"); PROMLAB("reason=\"terminated\""); METRIC("forcedterminatedsessions", "Total forced terminated sessions", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.forced_term_sess)); PROM("closed_sessions_total", "counter"); PROMLAB("reason=\"force_terminated\""); METRIC("relayedpackets_user", "Total relayed packets (userspace)", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.packets_user)); PROM("packets_total", "counter"); PROMLAB("type=\"userspace\""); METRIC("relayedpacketerrors_user", "Total relayed packet errors (userspace)", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.errors_user)); PROM("packet_errors_total", "counter"); PROMLAB("type=\"userspace\""); METRIC("relayedbytes_user", "Total relayed bytes (userspace)", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.bytes_user)); PROM("bytes_total", "counter"); PROMLAB("type=\"userspace\""); METRIC("relayedpackets_kernel", "Total relayed packets (kernel)", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.packets_kernel)); PROM("packets_total", "counter"); PROMLAB("type=\"kernel\""); METRIC("relayedpacketerrors_kernel", "Total relayed packet errors (kernel)", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.errors_kernel)); PROM("packet_errors_total", "counter"); PROMLAB("type=\"kernel\""); METRIC("relayedbytes_kernel", "Total relayed bytes (kernel)", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.bytes_kernel)); PROM("bytes_total", "counter"); PROMLAB("type=\"kernel\""); METRIC("relayedpackets", "Total relayed packets", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.packets_kernel) + atomic64_get(&rtpe_stats_cumulative.packets_user)); METRIC("relayedpacketerrors", "Total relayed packet errors", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.errors_kernel) + atomic64_get(&rtpe_stats_cumulative.errors_user)); METRIC("relayedbytes", "Total relayed bytes", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.bytes_kernel) + atomic64_get(&rtpe_stats_cumulative.bytes_user)); METRIC("zerowaystreams", "Total number of streams with no relayed packets", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.nopacket_relayed_sess)); PROM("zero_packet_streams_total", "counter"); METRIC("onewaystreams", "Total number of 1-way streams", UINT64F, UINT64F,atomic64_get(&rtpe_stats_cumulative.oneway_stream_sess)); PROM("one_way_sessions_total", "counter"); METRICva("avgcallduration", "Average call duration", "%.6f", "%.6f seconds", (double) avg_us / 1000000.0); PROM("call_duration_avg", "gauge"); METRICva("totalcallsduration", "Total calls duration", "%.6f", "%.6f seconds", (double) total_duration / 1000000.0); PROM("call_duration_total", "counter"); total_duration = atomic64_get(&rtpe_stats_cumulative.call_duration2); METRICva("totalcallsduration2", "Total calls duration squared", "%.6f", "%.6f seconds squared", (double) total_duration / 1000000.0); PROM("call_duration2_total", "counter"); double variance = num_sessions ? fabs((double) total_duration / (double) num_sessions - ((double) avg_us / 1000.0) * ((double) avg_us / 1000.0)) : 0.0; METRICva("totalcallsduration_stddev", "Total calls duration standard deviation", "%.6f", "%.6f seconds", sqrt(variance) / 1000.0); calls_dur_iv = (double) atomic64_get_na(&rtpe_stats_graphite_interval.total_calls_duration_intv) / 1000000.0; min_sess_iv = atomic64_get(&rtpe_stats_gauge_graphite_min_max_interval.min.total_sessions); max_sess_iv = atomic64_get(&rtpe_stats_gauge_graphite_min_max_interval.max.total_sessions); HEADER(NULL, ""); HEADER("}", ""); HEADER("intervalstatistics", "Graphite interval statistics (last reported values to graphite):"); HEADER("{", NULL); METRICva("totalcallsduration", "Total calls duration", "%.6f", "%.6f seconds", calls_dur_iv); HEADER(NULL, ""); METRIC("minmanagedsessions", "Min managed sessions", UINT64F, UINT64F, min_sess_iv); METRIC("maxmanagedsessions", "Max managed sessions", UINT64F, UINT64F, max_sess_iv); for (int i = 0; i < NGC_COUNT; i++) { double min = (double) atomic64_get(&rtpe_stats_gauge_graphite_min_max_interval.min.ng_command_times[i]) / 1000000.0; double max = (double) atomic64_get(&rtpe_stats_gauge_graphite_min_max_interval.max.ng_command_times[i]) / 1000000.0; double avg = (double) atomic64_get(&rtpe_stats_gauge_graphite_min_max_interval.avg.ng_command_times[i]) / 1000000.0; AUTO_CLEANUP(char *min_label, free_gbuf) = g_strdup_printf("min%sdelay", ng_command_strings[i]); AUTO_CLEANUP(char *max_label, free_gbuf) = g_strdup_printf("max%sdelay", ng_command_strings[i]); AUTO_CLEANUP(char *avg_label, free_gbuf) = g_strdup_printf("avg%sdelay", ng_command_strings[i]); AUTO_CLEANUP(char *long_label, free_gbuf) = g_strdup_printf("Min/Max/Avg %s processing delay", ng_command_strings[i]); METRICl(long_label, "%.6f/%.6f/%.6f sec", min, max, avg); METRICsva(min_label, "%.6f", min); METRICsva(max_label, "%.6f", max); METRICsva(avg_label, "%.6f", avg); } for (int i = 0; i < NGC_COUNT; i++) { uint64_t min = atomic64_get(&rtpe_stats_graphite_min_max_interval.min.ng_commands[i]); uint64_t max = atomic64_get(&rtpe_stats_graphite_min_max_interval.max.ng_commands[i]); uint64_t avg = atomic64_get(&rtpe_stats_graphite_min_max_interval.avg.ng_commands[i]); AUTO_CLEANUP(char *min_label, free_gbuf) = g_strdup_printf("min%srequestrate", ng_command_strings[i]); AUTO_CLEANUP(char *max_label, free_gbuf) = g_strdup_printf("max%srequestrate", ng_command_strings[i]); AUTO_CLEANUP(char *avg_label, free_gbuf) = g_strdup_printf("avg%srequestrate", ng_command_strings[i]); AUTO_CLEANUP(char *long_label, free_gbuf) = g_strdup_printf("Min/Max/Avg %s requests per second", ng_command_strings[i]); METRICl(long_label, "%" PRIu64 "/%" PRIu64 "/%" PRIu64 " per sec", min, max, avg); METRICsva(min_label, "%" PRIu64 "", min); METRICsva(max_label, "%" PRIu64 "", max); METRICsva(avg_label, "%" PRIu64 "", avg); } HEADER(NULL, ""); HEADER("}", ""); uint64_t metric_num, metric_tot, metric2_tot; double metric_mean, metric_variance; #define STAT_GET_PRINT(stat_name, name, divisor) \ metric_num = atomic64_get(&rtpe_stats_gauge_cumulative.count.stat_name); \ metric_tot = atomic64_get(&rtpe_stats_gauge_cumulative.avg.stat_name); \ metric2_tot = atomic64_get(&rtpe_stats_gauge_cumulative.stddev.stat_name); \ metric_mean = metric_num ? (double) metric_tot / (double) metric_num : 0.0; \ metric_variance = metric_num \ ? fabs((double) metric2_tot / (double) metric_num - metric_mean * metric_mean) \ : 0.0; \ METRIC(#stat_name "_total", "Sum of all " name " values sampled", "%.6f", "%.6f", \ (double) metric_tot / (divisor)); \ PROM(#stat_name "_total", "counter"); \ METRIC(#stat_name "2_total", "Sum of all " name " square values sampled", "%.6f", "%.6f", \ (double) metric2_tot / (divisor * divisor)); \ PROM(#stat_name "2_total", "counter"); \ METRIC(#stat_name "_samples_total", "Total number of " name " samples", UINT64F, UINT64F, metric_num); \ PROM(#stat_name "_samples_total", "counter"); \ METRIC(#stat_name "_average", "Average " name, "%.6f", "%.6f", metric_mean / 10.0); \ METRIC(#stat_name "_stddev", name " standard deviation", "%.6f", "%.6f", sqrt(metric_variance) / 10.0); \ HEADER("mos", "MOS statistics:"); HEADER("{", ""); STAT_GET_PRINT(mos, "MOS", 10.0); HEADER(NULL, ""); HEADER("}", ""); HEADER("voip_metrics", "VoIP metrics:"); HEADER("{", ""); STAT_GET_PRINT(jitter, "jitter (reported)", 1.0); STAT_GET_PRINT(rtt_e2e, "end-to-end round-trip time", 1.0); STAT_GET_PRINT(rtt_dsct, "discrete round-trip time", 1.0); STAT_GET_PRINT(packetloss, "packet loss", 1.0); STAT_GET_PRINT(jitter_measured, "jitter (measured)", 1.0); METRIC("packets_lost", "Packets lost", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.packets_lost)); PROM("packets_lost", "counter"); METRIC("rtp_duplicates", "Duplicate RTP packets", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.rtp_duplicates)); PROM("rtp_duplicates", "counter"); METRIC("rtp_skips", "RTP sequence skips", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.rtp_skips)); PROM("rtp_skips", "counter"); METRIC("rtp_seq_resets", "RTP sequence resets", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.rtp_seq_resets)); PROM("rtp_seq_resets", "counter"); METRIC("rtp_reordered", "Out-of-order RTP packets", UINT64F, UINT64F, atomic64_get(&rtpe_stats_cumulative.rtp_reordered)); PROM("rtp_reordered", "counter"); HEADER(NULL, ""); HEADER("}", ""); HEADER("controlstatistics", "Control statistics:"); HEADER("{", ""); HEADER("proxies", NULL); HEADER("[", NULL); GString *tmp = g_string_new(""); g_string_append_printf(tmp, " %20s ", "Proxy"); for (int i = 0; i < NGC_COUNT; i++) g_string_append_printf(tmp, "| %10s ", ng_command_strings_short[i]); HEADERl("%s", tmp->str); g_string_free(tmp, TRUE); struct control_ng_stats total = {0,}; mutex_lock(&rtpe_cngs_lock); GList *list = g_hash_table_get_values(rtpe_cngs_hash); if (!list) { //streambuf_printf(replybuffer, "\n No proxies have yet tried to send data."); } for (GList *l = list; l; l = l->next) { struct control_ng_stats* cur = l->data; HEADER("{", NULL); GString *tmp = g_string_new(""); METRICsva("proxy", "\"%s\"", sockaddr_print_buf(&cur->proxy)); g_string_append_printf(tmp, " %20s ", sockaddr_print_buf(&cur->proxy)); for (int i = 0; i < NGC_COUNT; i++) { mutex_lock(&cur->cmd[i].lock); g_string_append_printf(tmp, "| %10u ", cur->cmd[i].count); total.cmd[i].count += cur->cmd[i].count; char *mn = g_strdup_printf("%scount", ng_command_strings_short[i]); char *lw = g_ascii_strdown(mn, -1); METRICs(lw, "%u", cur->cmd[i].count); PROM("requests_total", "counter"); PROMLAB("proxy=\"%s\",request=\"%s\"", sockaddr_print_buf(&cur->proxy), ng_command_strings[i]); free(mn); free(lw); mn = g_strdup_printf("%sduration", ng_command_strings_short[i]); lw = g_ascii_strdown(mn, -1); METRICsva(lw, "%llu.%06llu", (unsigned long long) cur->cmd[i].time.tv_sec, (unsigned long long) cur->cmd[i].time.tv_usec); PROM("request_seconds_total", "counter"); PROMLAB("proxy=\"%s\",request=\"%s\"", sockaddr_print_buf(&cur->proxy), ng_command_strings[i]); free(mn); free(lw); mutex_unlock(&cur->cmd[i].lock); } METRICl("", "%s", tmp->str); g_string_free(tmp, TRUE); int errors = g_atomic_int_get(&cur->errors); total.errors += errors; METRICs("errorcount", "%i", errors); PROM("errors_total", "counter"); PROMLAB("proxy=\"%s\"", sockaddr_print_buf(&cur->proxy)); HEADER("}", NULL); } mutex_unlock(&rtpe_cngs_lock); g_list_free(list); HEADER("]", ""); for (int i = 0; i < NGC_COUNT; i++) { char *mn = g_strdup_printf("total%scount", ng_command_strings_short[i]); char *lw = g_ascii_strdown(mn, -1); METRICs(lw, "%u", total.cmd[i].count); free(mn); free(lw); } HEADER("}", ""); HEADER("interfaces", NULL); HEADER("[", NULL); for (GList *l = all_local_interfaces.head; l; l = l->next) { struct local_intf *lif = l->data; // only show first-order interface entries: socket families must match if (lif->logical->preferred_family != lif->spec->local_address.addr.family) continue; HEADER("{", NULL); METRICsva("name", "\"%s\"", lif->logical->name.s); METRICsva("address", "\"%s\"", sockaddr_print_buf(&lif->spec->local_address.addr)); HEADER("ports", NULL); HEADER("{", NULL); METRICs("min", "%u", lif->spec->port_pool.min); METRICs("max", "%u", lif->spec->port_pool.max); unsigned int f = g_atomic_int_get(&lif->spec->port_pool.free_ports); unsigned int l = g_atomic_int_get(&lif->spec->port_pool.last_used); unsigned int r = lif->spec->port_pool.max - lif->spec->port_pool.min + 1; METRICs("used", "%u", r - f); PROM("ports_used", "gauge"); PROMLAB("name=\"%s\",address=\"%s\"", lif->logical->name.s, sockaddr_print_buf(&lif->spec->local_address.addr)); METRICs("used_pct", "%.2f", (double) (r - f) * 100.0 / r); METRICs("free", "%u", f); PROM("ports_free", "gauge"); PROMLAB("name=\"%s\",address=\"%s\"", lif->logical->name.s, sockaddr_print_buf(&lif->spec->local_address.addr)); METRICs("totals", "%u", r); PROM("ports", "gauge"); PROMLAB("name=\"%s\",address=\"%s\"", lif->logical->name.s, sockaddr_print_buf(&lif->spec->local_address.addr)); METRICs("last", "%u", l); HEADER("}", NULL); HEADER("}", NULL); } HEADER("]", NULL); mutex_lock(&rtpe_codec_stats_lock); HEADER("transcoders", NULL); HEADER("[", ""); GList *chains = g_hash_table_get_keys(rtpe_codec_stats); int last_tv_sec = rtpe_now.tv_sec - 1; unsigned int idx = last_tv_sec & 1; for (GList *l = chains; l; l = l->next) { char *chain = l->data; struct codec_stats *stats_entry = g_hash_table_lookup(rtpe_codec_stats, chain); HEADER("{", ""); METRICsva("chain", "\"%s\"", chain); METRICs("num", "%i", g_atomic_int_get(&stats_entry->num_transcoders)); PROM("transcoders", "gauge"); PROMLAB("chain=\"%s\"", chain); if (g_atomic_int_get(&stats_entry->last_tv_sec[idx]) == last_tv_sec) { METRICs("packetrate", UINT64F, atomic64_get(&stats_entry->packets_input[idx])); METRICs("byterate", UINT64F, atomic64_get(&stats_entry->bytes_input[idx])); METRICs("samplerate", UINT64F, atomic64_get(&stats_entry->pcm_samples[idx])); } METRICs("packets", UINT64F, atomic64_get(&stats_entry->packets_input[2])); PROM("transcode_packets_total", "counter"); PROMLAB("chain=\"%s\"", chain); METRICs("bytes", UINT64F, atomic64_get(&stats_entry->bytes_input[2])); PROM("transcode_bytes_total", "counter"); PROMLAB("chain=\"%s\"", chain); METRICs("samples", UINT64F, atomic64_get(&stats_entry->pcm_samples[2])); PROM("transcode_samples_total", "counter"); PROMLAB("chain=\"%s\"", chain); HEADER("}", ""); } mutex_unlock(&rtpe_codec_stats_lock); g_list_free(chains); HEADER("]", ""); HEADER("}", NULL); return ret; } #pragma GCC diagnostic warning "-Wformat-zero-length" static void free_stats_metric(void *p) { struct stats_metric *m = p; g_free(m->descr); g_free(m->label); g_free(m->value_long); g_free(m->value_short); g_free(m->prom_label); g_slice_free1(sizeof(*m), m); } void statistics_free_metrics(GQueue **q) { g_queue_free_full(*q, free_stats_metric); *q = NULL; } void statistics_free() { mutex_destroy(&rtpe_codec_stats_lock); g_hash_table_destroy(rtpe_codec_stats); } static void codec_stats_free(void *p) { struct codec_stats *stats_entry = p; free(stats_entry->chain); g_free(stats_entry->chain_brief); g_slice_free1(sizeof(*stats_entry), stats_entry); } void statistics_init() { gettimeofday(&rtpe_started, NULL); //rtpe_totalstats_interval.managed_sess_min = 0; // already zeroed //rtpe_totalstats_interval.managed_sess_max = 0; mutex_init(&rtpe_codec_stats_lock); rtpe_codec_stats = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, codec_stats_free); } const char *statistics_ng(bencode_item_t *input, bencode_item_t *output) { AUTO_CLEANUP_INIT(GQueue *metrics, statistics_free_metrics, statistics_gather_metrics()); AUTO_CLEANUP_INIT(GQueue bstack, g_queue_clear, G_QUEUE_INIT); bencode_item_t *dict = output; const char *sub_label = "statistics"; // top level bencode_buffer_t *buf = output->buffer; for (GList *l = metrics->head; l; l = l->next) { struct stats_metric *m = l->data; if (!m->label) continue; // key:value entry? if (m->value_short) { if (m->is_int) bencode_dictionary_add_integer(dict, bencode_strdup(buf, m->label), m->int_value); else { size_t len = strlen(m->value_short); if (len >= 2 && m->value_short[0] == '"' && m->value_short[len-1] == '"') bencode_dictionary_add(dict, bencode_strdup(buf, m->label), bencode_string_len_dup(buf, m->value_short+1, len-2)); else bencode_dictionary_add_string_dup(dict, bencode_strdup(buf, m->label), m->value_short); } continue; } // list or dict end? if (m->is_close_bracket) { dict = g_queue_pop_tail(&bstack); assert(dict != NULL); continue; } // label without value precedes an immediate sub-entry, so save the label if (!m->is_bracket) { assert(sub_label == NULL); sub_label = m->label; continue; } // open bracket of some sort - new sub-entry follows bencode_item_t *sub = NULL; if (m->is_brace) sub = bencode_dictionary(buf); else sub = bencode_list(buf); assert(sub != NULL); // is this a dictionary? if (dict->type == BENCODE_DICTIONARY) { assert(sub_label != NULL); bencode_dictionary_add(dict, bencode_strdup(buf, sub_label), sub); } else if (dict->type == BENCODE_LIST) bencode_list_add(dict, sub); else abort(); sub_label = NULL; g_queue_push_tail(&bstack, dict); dict = sub; } return NULL; }