summaryrefslogtreecommitdiff
path: root/channels/pjsip
diff options
context:
space:
mode:
authorGeorge Joseph <george.joseph@fairview5.com>2016-03-26 21:33:14 -0600
committerJoshua Colp <jcolp@digium.com>2016-03-29 14:35:17 -0500
commitc7eb18d8655272c7e1bdecce176891e99c140795 (patch)
tree2f3994c9f87ddc428ab3f5129530f94d7dc3a187 /channels/pjsip
parente595f02f83137cf92dd2a7f2567a73d6a88a2ff2 (diff)
chan_pjsip: Add 'pjsip show channelstats'
Added the ability to show channel statistics to chan_pjsip (cli_functions.c) Moved the existing 'pjsip show channel(s)' functionality from pjsip_configuration to cli_functions.c. The stats needed chan_pjsip's private header so it made sense to move the existing channel commands as well. Now using stasis_cache_dump to get the channel snapshots rather than retrieving all endpoints, then getting each one's channel snapshots. Much more efficient. Change-Id: I03b114522126d27434030b285bf6d531ddd79869
Diffstat (limited to 'channels/pjsip')
-rw-r--r--channels/pjsip/cli_commands.c467
-rw-r--r--channels/pjsip/include/cli_functions.h45
2 files changed, 512 insertions, 0 deletions
diff --git a/channels/pjsip/cli_commands.c b/channels/pjsip/cli_commands.c
new file mode 100644
index 000000000..8d99379ff
--- /dev/null
+++ b/channels/pjsip/cli_commands.c
@@ -0,0 +1,467 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2016, Fairview 5 Engineering, LLC
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ *
+ * \author \verbatim George Joseph <george.joseph@fairview5.com> \endverbatim
+ *
+ * \ingroup functions
+ *
+ * \brief PJSIP channel CLI functions
+ */
+
+#include "asterisk.h"
+
+ASTERISK_REGISTER_FILE()
+
+#include <pjsip.h>
+#include <pjlib.h>
+#include <pjsip_ua.h>
+
+#include "asterisk/astobj2.h"
+#include "asterisk/channel.h"
+#include "asterisk/format.h"
+#include "asterisk/res_pjsip.h"
+#include "asterisk/res_pjsip_session.h"
+#include "asterisk/res_pjsip_cli.h"
+#include "asterisk/stasis.h"
+#include "asterisk/time.h"
+#include "include/chan_pjsip.h"
+#include "include/cli_functions.h"
+
+
+static int cli_channel_iterate(void *endpoint, ao2_callback_fn callback, void *arg)
+{
+ return ast_sip_for_each_channel(endpoint, callback, arg);
+}
+
+static int cli_channelstats_iterate(void *endpoint, ao2_callback_fn callback, void *arg)
+{
+ return ast_sip_for_each_channel(endpoint, callback, arg);
+}
+
+static int cli_channel_sort(const void *obj, const void *arg, int flags)
+{
+ const struct ast_channel_snapshot *left_obj = obj;
+ const struct ast_channel_snapshot *right_obj = arg;
+ const char *right_key = arg;
+ int cmp;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ right_key = right_obj->name;
+ /* Fall through */
+ case OBJ_SEARCH_KEY:
+ cmp = strcmp(left_obj->name, right_key);
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ cmp = strncmp(left_obj->name, right_key, strlen(right_key));
+ break;
+ default:
+ cmp = 0;
+ break;
+ }
+
+ return cmp;
+}
+
+static int cli_channelstats_sort(const void *obj, const void *arg, int flags)
+{
+ const struct ast_channel_snapshot *left_obj = obj;
+ const struct ast_channel_snapshot *right_obj = arg;
+ const char *right_key = arg;
+ int cmp;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ cmp = strcmp(left_obj->bridgeid, right_obj->bridgeid);
+ if (cmp) {
+ return cmp;
+ }
+ right_key = right_obj->name;
+ /* Fall through */
+ case OBJ_SEARCH_KEY:
+ cmp = strcmp(left_obj->name, right_key);
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ cmp = strncmp(left_obj->name, right_key, strlen(right_key));
+ break;
+ default:
+ cmp = 0;
+ break;
+ }
+
+ return cmp;
+}
+
+static int cli_channel_compare(void *obj, void *arg, int flags)
+{
+ const struct ast_channel_snapshot *left_obj = obj;
+ const struct ast_channel_snapshot *right_obj = arg;
+ const char *right_key = arg;
+ int cmp = 0;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ right_key = right_obj->name;
+ /* Fall through */
+ case OBJ_SEARCH_KEY:
+ if (strcmp(left_obj->name, right_key) == 0) {
+ cmp = CMP_MATCH | CMP_STOP;
+ }
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ if (strncmp(left_obj->name, right_key, strlen(right_key)) == 0) {
+ cmp = CMP_MATCH;
+ }
+ break;
+ default:
+ cmp = 0;
+ break;
+ }
+
+ return cmp;
+}
+
+static int cli_channelstats_compare(void *obj, void *arg, int flags)
+{
+ const struct ast_channel_snapshot *left_obj = obj;
+ const struct ast_channel_snapshot *right_obj = arg;
+ const char *right_key = arg;
+ int cmp = 0;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ if (strcmp(left_obj->bridgeid, right_obj->bridgeid) == 0
+ && strcmp(left_obj->name, right_obj->name) == 0) {
+ return CMP_MATCH | CMP_STOP;
+ }
+ break;
+ case OBJ_SEARCH_KEY:
+ if (strcmp(left_obj->name, right_key) == 0) {
+ cmp = CMP_MATCH | CMP_STOP;
+ }
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ if (strncmp(left_obj->name, right_key, strlen(right_key)) == 0) {
+ cmp = CMP_MATCH;
+ }
+ break;
+ default:
+ cmp = 0;
+ break;
+ }
+
+ return cmp;
+}
+
+static int cli_message_to_snapshot(void *obj, void *arg, int flags)
+{
+ struct stasis_message *message = obj;
+ struct ao2_container *snapshots = arg;
+ struct ast_channel_snapshot *snapshot = stasis_message_data(message);
+
+ if (!strcmp(snapshot->type, "PJSIP")) {
+ ao2_link(snapshots, snapshot);
+ return CMP_MATCH;
+ }
+
+ return 0;
+}
+
+static int cli_filter_channels(void *obj, void *arg, int flags)
+{
+ struct ast_channel_snapshot *channel = obj;
+ regex_t *regexbuf = arg;
+
+ if (!regexec(regexbuf, channel->name, 0, NULL, 0)
+ || !regexec(regexbuf, channel->appl, 0, NULL, 0)) {
+ return 0;
+ }
+
+ return CMP_MATCH;
+}
+
+static struct ao2_container *get_container(const char *regex, ao2_sort_fn sort_fn, ao2_callback_fn compare_fn)
+{
+ struct ao2_container *child_container;
+ regex_t regexbuf;
+ RAII_VAR(struct ao2_container *, parent_container,
+ stasis_cache_dump(ast_channel_cache_by_name(), ast_channel_snapshot_type()), ao2_cleanup);
+
+ if (!parent_container) {
+ return NULL;
+ }
+
+ child_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, sort_fn, compare_fn);
+ if (!child_container) {
+ return NULL;
+ }
+
+ ao2_callback(parent_container, OBJ_MULTIPLE | OBJ_NODATA, cli_message_to_snapshot, child_container);
+
+ if (!ast_strlen_zero(regex)) {
+ if (regcomp(&regexbuf, regex, REG_EXTENDED | REG_NOSUB)) {
+ ao2_ref(child_container, -1);
+ return NULL;
+ }
+ ao2_callback(child_container, OBJ_UNLINK | OBJ_MULTIPLE | OBJ_NODATA, cli_filter_channels, &regexbuf);
+ regfree(&regexbuf);
+ }
+
+ return child_container;
+}
+
+static struct ao2_container *cli_channel_get_container(const char *regex)
+{
+ return get_container(regex, cli_channel_sort, cli_channel_compare);
+}
+
+static struct ao2_container *cli_channelstats_get_container(const char *regex)
+{
+ return get_container(regex, cli_channelstats_sort, cli_channelstats_compare);
+}
+
+static const char *cli_channel_get_id(const void *obj)
+{
+ const struct ast_channel_snapshot *snapshot = obj;
+
+ return snapshot->name;
+}
+
+static void *cli_channel_retrieve_by_id(const char *id)
+{
+ return ast_channel_snapshot_get_latest_by_name(id);
+}
+
+static int cli_channel_print_header(void *obj, void *arg, int flags)
+{
+ struct ast_sip_cli_context *context = arg;
+ int indent = CLI_INDENT_TO_SPACES(context->indent_level);
+ int filler = CLI_LAST_TABSTOP - indent - 13;
+
+ ast_assert(context->output_buffer != NULL);
+
+ ast_str_append(&context->output_buffer, 0,
+ "%*s: <ChannelId%*.*s> <State.....> <Time.....>\n",
+ indent, "Channel", filler, filler, CLI_HEADER_FILLER);
+ if (context->recurse) {
+ context->indent_level++;
+ indent = CLI_INDENT_TO_SPACES(context->indent_level);
+ filler = CLI_LAST_TABSTOP - indent - 38;
+ ast_str_append(&context->output_buffer, 0,
+ "%*s: <DialedExten%*.*s> CLCID: <ConnectedLineCID.......>\n",
+ indent, "Exten", filler, filler, CLI_HEADER_FILLER);
+ context->indent_level--;
+ }
+
+ return 0;
+}
+
+static int cli_channel_print_body(void *obj, void *arg, int flags)
+{
+ const struct ast_channel_snapshot *snapshot = obj;
+ struct ast_sip_cli_context *context = arg;
+ char *print_name = NULL;
+ int print_name_len;
+ int indent;
+ int flexwidth;
+ char *print_time = alloca(32);
+
+ ast_assert(context->output_buffer != NULL);
+
+ print_name_len = strlen(snapshot->name) + strlen(snapshot->appl) + 2;
+ print_name = alloca(print_name_len);
+
+ /* Append the application */
+ snprintf(print_name, print_name_len, "%s/%s", snapshot->name, snapshot->appl);
+
+ indent = CLI_INDENT_TO_SPACES(context->indent_level);
+ flexwidth = CLI_LAST_TABSTOP - indent;
+
+ ast_format_duration_hh_mm_ss(ast_tvnow().tv_sec - snapshot->creationtime.tv_sec, print_time, 32);
+
+ ast_str_append(&context->output_buffer, 0, "%*s: %-*.*s %-12.12s %-11.11s\n",
+ CLI_INDENT_TO_SPACES(context->indent_level), "Channel",
+ flexwidth, flexwidth,
+ print_name,
+ ast_state2str(snapshot->state),
+ print_time);
+
+ if (context->recurse) {
+ context->indent_level++;
+ indent = CLI_INDENT_TO_SPACES(context->indent_level);
+ flexwidth = CLI_LAST_TABSTOP - indent - 25;
+
+ ast_str_append(&context->output_buffer, 0,
+ "%*s: %-*.*s CLCID: \"%s\" <%s>\n",
+ indent, "Exten",
+ flexwidth, flexwidth,
+ snapshot->exten,
+ snapshot->connected_name,
+ snapshot->connected_number
+ );
+ context->indent_level--;
+ if (context->indent_level == 0) {
+ ast_str_append(&context->output_buffer, 0, "\n");
+ }
+ }
+
+ return 0;
+}
+
+static int cli_channelstats_print_header(void *obj, void *arg, int flags)
+{
+ struct ast_sip_cli_context *context = arg;
+
+ ast_assert(context->output_buffer != NULL);
+
+ ast_str_append(&context->output_buffer, 0,
+ " ...........Receive......... .........Transmit..........\n"
+ " BridgeId ChannelId ........ UpTime.. Codec. Count Lost Pct Jitter Count Lost Pct Jitter RTT....\n"
+ " =================");
+
+ return 0;
+}
+
+static int cli_channelstats_print_body(void *obj, void *arg, int flags)
+{
+ struct ast_sip_cli_context *context = arg;
+ const struct ast_channel_snapshot *snapshot = obj;
+ struct ast_channel *channel = ast_channel_get_by_name(snapshot->name);
+ struct ast_sip_channel_pvt *cpvt = channel ? ast_channel_tech_pvt(channel) : NULL;
+ struct chan_pjsip_pvt *pvt = cpvt ? cpvt->pvt : NULL;
+ struct ast_sip_session_media *media = pvt ? pvt->media[SIP_MEDIA_AUDIO] : NULL;
+ struct ast_rtp_codecs *codecs = media && media->rtp ? ast_rtp_instance_get_codecs(media->rtp) : NULL;
+ struct ast_format *format = codecs ? ast_rtp_codecs_get_payload_format(codecs, 0) : NULL;
+ struct ast_rtp_instance_stats stats;
+ char *print_name = NULL;
+ char *print_time = alloca(32);
+
+ ast_assert(context->output_buffer != NULL);
+
+ if (!media || !media->rtp) {
+ ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name);
+ ao2_cleanup(channel);
+ return -1;
+ }
+
+ print_name = ast_strdupa(snapshot->name);
+ /* Skip the PJSIP/. We know what channel type it is and we need the space. */
+ print_name += 6;
+
+ ast_format_duration_hh_mm_ss(ast_tvnow().tv_sec - snapshot->creationtime.tv_sec, print_time, 32);
+
+ if (ast_rtp_instance_get_stats(media->rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) {
+ ast_str_append(&context->output_buffer, 0, "%s direct media\n", snapshot->name);
+ } else {
+ ast_str_append(&context->output_buffer, 0,
+ " %8.8s %-18.18s %-8.8s %-6.6s %6u%s %6u%s %3u %7.3f %6u%s %6u%s %3u %7.3f %7.3f\n",
+ snapshot->bridgeid,
+ print_name,
+ print_time,
+ format ? ast_format_get_name(format) : "",
+ stats.rxcount > 100000 ? stats.rxcount / 1000 : stats.rxcount,
+ stats.rxcount > 100000 ? "K": " ",
+ stats.rxploss > 100000 ? stats.rxploss / 1000 : stats.rxploss,
+ stats.rxploss > 100000 ? "K": " ",
+ stats.rxcount ? (stats.rxploss * 100) / stats.rxcount : 0,
+ MIN(stats.rxjitter, 999.999),
+ stats.txcount > 100000 ? stats.txcount / 1000 : stats.txcount,
+ stats.txcount > 100000 ? "K": " ",
+ stats.txploss > 100000 ? stats.txploss / 1000 : stats.txploss,
+ stats.txploss > 100000 ? "K": " ",
+ stats.txcount ? (stats.txploss * 100) / stats.txcount : 0,
+ MIN(stats.txjitter, 999.999),
+ MIN(stats.normdevrtt, 999.999)
+ );
+ }
+
+ ao2_cleanup(format);
+ ao2_cleanup(channel);
+
+ return 0;
+}
+
+static struct ast_cli_entry cli_commands[] = {
+ AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "List PJSIP Channels",
+ .command = "pjsip list channels",
+ .usage = "Usage: pjsip list channels [ like <pattern> ]\n"
+ " List the active PJSIP channels\n"
+ " Optional regular expression pattern is used to filter the list.\n"),
+ AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Channels",
+ .command = "pjsip show channels",
+ .usage = "Usage: pjsip show channels [ like <pattern> ]\n"
+ " List(detailed) the active PJSIP channels\n"
+ " Optional regular expression pattern is used to filter the list.\n"),
+ AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Channel",
+ .command = "pjsip show channel",
+ .usage = "Usage: pjsip show channel\n"
+ " List(detailed) the active PJSIP channel\n"),
+
+ AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Channel Stats",
+ .command = "pjsip show channelstats",
+ .usage = "Usage: pjsip show channelstats [ like <pattern> ]\n"
+ " List(detailed) the active PJSIP channel stats\n"
+ " Optional regular expression pattern is used to filter the list.\n"),
+};
+
+struct ast_sip_cli_formatter_entry *channelstats_formatter;
+struct ast_sip_cli_formatter_entry *channel_formatter;
+
+int pjsip_channel_cli_register(void)
+{
+ channel_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
+ if (!channel_formatter) {
+ ast_log(LOG_ERROR, "Unable to allocate memory for channel_formatter\n");
+ return -1;
+ }
+ channel_formatter->name = "channel";
+ channel_formatter->print_header = cli_channel_print_header;
+ channel_formatter->print_body = cli_channel_print_body;
+ channel_formatter->get_container = cli_channel_get_container;
+ channel_formatter->iterate = cli_channel_iterate;
+ channel_formatter->retrieve_by_id = cli_channel_retrieve_by_id;
+ channel_formatter->get_id = cli_channel_get_id;
+
+ channelstats_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
+ if (!channelstats_formatter) {
+ ao2_ref(channel_formatter, -1);
+ ast_log(LOG_ERROR, "Unable to allocate memory for channelstats_formatter\n");
+ return -1;
+ }
+ channelstats_formatter->name = "channelstat";
+ channelstats_formatter->print_header = cli_channelstats_print_header;
+ channelstats_formatter->print_body = cli_channelstats_print_body;
+ channelstats_formatter->get_container = cli_channelstats_get_container;
+ channelstats_formatter->iterate = cli_channelstats_iterate;
+ channelstats_formatter->retrieve_by_id = cli_channel_retrieve_by_id;
+ channelstats_formatter->get_id = cli_channel_get_id;
+
+ ast_sip_register_cli_formatter(channel_formatter);
+ ast_sip_register_cli_formatter(channelstats_formatter);
+ ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
+
+ return 0;
+}
+
+void pjsip_channel_cli_unregister(void)
+{
+ ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
+ ast_sip_unregister_cli_formatter(channel_formatter);
+ ast_sip_unregister_cli_formatter(channelstats_formatter);
+}
diff --git a/channels/pjsip/include/cli_functions.h b/channels/pjsip/include/cli_functions.h
new file mode 100644
index 000000000..87200774e
--- /dev/null
+++ b/channels/pjsip/include/cli_functions.h
@@ -0,0 +1,45 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2016, Fairview 5 Engineering, LLC.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ *
+ * \author \verbatim George Joseph <george.joseph@fairview5.com> \endverbatim
+ *
+ * \brief PJSIP CLI functions header file
+ */
+
+#ifndef _PJSIP_CLI_FUNCTIONS
+#define _PJSIP_CLI_FUNCTIONS
+
+
+/*!
+ * \brief Registers the channel cli commands
+ * \since 13.9.0
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int pjsip_channel_cli_register(void);
+
+/*!
+ * \brief Unregisters the channel cli commands
+ * \since 13.9.0
+ */
+void pjsip_channel_cli_unregister(void);
+
+
+#endif /* _PJSIP_CLI_FUNCTIONS */