diff --git a/README.md b/README.md index d7ae31893..10fef7059 100644 --- a/README.md +++ b/README.md @@ -522,6 +522,7 @@ a string and determines the type of message. Currently the following commands ar * play media * stop media * play DTMF +* statistics The response dictionary must contain at least one key called `result`. The value can be either `ok` or `error`. For the `ping` command, the additional value `pong` is allowed. If the result is `error`, then another key @@ -1672,3 +1673,108 @@ received during a generated DTMF event will be suppressed. The call must be marked for DTMF injection using the `inject DTMF` flag used in both `offer` and `answer` messages. Enabling this flag forces all audio to go through the transcoding engine, even if input and output codecs are the same (similar to DTMF transcoding, see above). + +`statistics` Message +-------------------- + +Returns a set of general statistics metrics with identical content and format as the `list jsonstats` CLI +command. Sample return dictionary: + + { + "statistics": { + "currentstatistics": { + "sessionsown": 0, + "sessionsforeign": 0, + "sessionstotal": 0, + "transcodedmedia": 0, + "packetrate": 0, + "byterate": 0, + "errorrate": 0 + }, + "totalstatistics": { + "uptime": "18", + "managedsessions": 0, + "rejectedsessions": 0, + "timeoutsessions": 0, + "silenttimeoutsessions": 0, + "finaltimeoutsessions": 0, + "offertimeoutsessions": 0, + "regularterminatedsessions": 0, + "forcedterminatedsessions": 0, + "relayedpackets": 0, + "relayedpacketerrors": 0, + "zerowaystreams": 0, + "onewaystreams": 0, + "avgcallduration": "0.000000" + }, + "intervalstatistics": { + "totalcallsduration": "0.000000", + "minmanagedsessions": 0, + "maxmanagedsessions": 0, + "minofferdelay": "0.000000", + "maxofferdelay": "0.000000", + "avgofferdelay": "0.000000", + "minanswerdelay": "0.000000", + "maxanswerdelay": "0.000000", + "avganswerdelay": "0.000000", + "mindeletedelay": "0.000000", + "maxdeletedelay": "0.000000", + "avgdeletedelay": "0.000000", + "minofferrequestrate": 0, + "maxofferrequestrate": 0, + "avgofferrequestrate": 0, + "minanswerrequestrate": 0, + "maxanswerrequestrate": 0, + "avganswerrequestrate": 0, + "mindeleterequestrate": 0, + "maxdeleterequestrate": 0, + "avgdeleterequestrate": 0 + }, + "controlstatistics": { + "proxies": [ + { + "proxy": "127.0.0.1", + "pingcount": 0, + "offercount": 0, + "answercount": 0, + "deletecount": 0, + "querycount": 0, + "listcount": 0, + "startreccount": 0, + "stopreccount": 0, + "startfwdcount": 0, + "stopfwdcount": 0, + "blkdtmfcount": 0, + "unblkdtmfcount": 0, + "blkmedia": 0, + "unblkmedia": 0, + "playmedia": 0, + "stopmedia": 0, + "playdtmf": 0, + "statistics": 0, + "errorcount": 0 + } + ], + "totalpingcount": 0, + "totaloffercount": 0, + "totalanswercount": 0, + "totaldeletecount": 0, + "totalquerycount": 0, + "totallistcount": 0, + "totalstartreccount": 0, + "totalstopreccount": 0, + "totalstartfwdcount": 0, + "totalstopfwdcount": 0, + "totalblkdtmfcount": 0, + "totalunblkdtmfcount": 0, + "totalblkmedia": 0, + "totalunblkmedia": 0, + "totalplaymedia": 0, + "totalstopmedia": 0, + "totalplaydtmf": 0, + "totalstatistics": 0, + "totalerrorcount": 0 + } + }, + "result": "ok" + } diff --git a/daemon/control_ng.c b/daemon/control_ng.c index b96199044..edac69cb8 100644 --- a/daemon/control_ng.c +++ b/daemon/control_ng.c @@ -15,6 +15,7 @@ #include "socket.h" #include "log_funcs.h" #include "main.h" +#include "statistics.h" mutex_t rtpe_cngs_lock; @@ -260,6 +261,10 @@ static void control_ng_incoming(struct obj *obj, str *buf, const endpoint_t *sin errstr = call_play_dtmf_ng(dict, resp); g_atomic_int_inc(&cur->play_dtmf); break; + case CSH_LOOKUP("statistics"): + errstr = statistics_ng(dict, resp); + g_atomic_int_inc(&cur->statistics); + break; default: errstr = "Unrecognized command"; } diff --git a/daemon/statistics.c b/daemon/statistics.c index ee683e33e..9f3aa0ff1 100644 --- a/daemon/statistics.c +++ b/daemon/statistics.c @@ -521,6 +521,7 @@ GQueue *statistics_gather_metrics(void) { total.play_media += cur->play_media; total.stop_media += cur->stop_media; total.play_dtmf += cur->play_dtmf; + total.statistics += cur->statistics; total.errors += cur->errors; HEADER("{", NULL); METRICsva("proxy", "\"%s\"", sockaddr_print_buf(&cur->proxy)); @@ -541,6 +542,7 @@ GQueue *statistics_gather_metrics(void) { METRICs("playmedia", "%u", cur->play_media); METRICs("stopmedia", "%u", cur->stop_media); METRICs("playdtmf", "%u", cur->play_dtmf); + METRICs("statistics", "%u", cur->statistics); METRICs("errorcount", "%u", cur->errors); HEADER("}", NULL); @@ -567,6 +569,7 @@ GQueue *statistics_gather_metrics(void) { METRICs("totalplaymedia", "%u", total.play_media); METRICs("totalstopmedia", "%u", total.stop_media); METRICs("totalplaydtmf", "%u", total.play_dtmf); + METRICs("totalstatistics", "%u", total.statistics); METRICs("totalerrorcount", "%u", total.errors); HEADER("}", ""); @@ -613,3 +616,74 @@ void statistics_init() { mutex_init(&rtpe_codec_stats_lock); rtpe_codec_stats = g_hash_table_new(g_str_hash, g_str_equal); } + +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; +} diff --git a/include/bencode.h b/include/bencode.h index 2bcf24ec7..fb7a1efc5 100644 --- a/include/bencode.h +++ b/include/bencode.h @@ -86,6 +86,9 @@ void bencode_buffer_destroy_add(bencode_buffer_t *buf, free_func_t, void *); /* Returns the buffer associated with an item, or NULL if pointer given is NULL */ INLINE bencode_buffer_t *bencode_item_buffer(bencode_item_t *); +/* like strdup() but uses the bencode buffer to store the string */ +INLINE char *bencode_strdup(bencode_buffer_t *, const char *); + @@ -373,6 +376,12 @@ INLINE bencode_item_t *bencode_str_dup(bencode_buffer_t *buf, const str *s) { return bencode_string_len_dup(buf, s->s, s->len); } +INLINE char *bencode_strdup(bencode_buffer_t *buf, const char *s) { + char *ret = bencode_buffer_alloc(buf, strlen(s) + 1); + strcpy(ret, s); + return ret; +} + INLINE bencode_item_t *bencode_dictionary_add(bencode_item_t *dict, const char *key, bencode_item_t *val) { if (!key) return NULL; diff --git a/include/control_ng.h b/include/control_ng.h index c0003374b..21d9d2e0a 100644 --- a/include/control_ng.h +++ b/include/control_ng.h @@ -28,6 +28,7 @@ struct control_ng_stats { int play_media; int stop_media; int play_dtmf; + int statistics; int errors; }; diff --git a/include/statistics.h b/include/statistics.h index 7fcd0cafb..17d211c0c 100644 --- a/include/statistics.h +++ b/include/statistics.h @@ -2,6 +2,7 @@ #define STATISTICS_H_ #include "aux.h" +#include "bencode.h" struct call; struct packet_stream; @@ -126,6 +127,7 @@ void statistics_update_totals(struct packet_stream *) ; GQueue *statistics_gather_metrics(void); void statistics_free_metrics(GQueue **); +const char *statistics_ng(bencode_item_t *input, bencode_item_t *output); void statistics_init(void);