mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-05 04:11:08 +00:00
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
This commit is contained in:
committed by
Joshua Colp
parent
03845666da
commit
c4064727d2
3
CHANGES
3
CHANGES
@@ -263,6 +263,9 @@ res_parking:
|
|||||||
for these variables. The indefinite inheritance is also recommended
|
for these variables. The indefinite inheritance is also recommended
|
||||||
for the PARKINGEXTEN variable.
|
for the PARKINGEXTEN variable.
|
||||||
|
|
||||||
|
chan_pjsip
|
||||||
|
------------------
|
||||||
|
* Added 'pjsip show channelstats' CLI command.
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
--- Functionality changes from Asterisk 13.7.0 to Asterisk 13.8.0 ------------
|
--- Functionality changes from Asterisk 13.7.0 to Asterisk 13.8.0 ------------
|
||||||
|
@@ -69,6 +69,7 @@ ASTERISK_REGISTER_FILE()
|
|||||||
|
|
||||||
#include "pjsip/include/chan_pjsip.h"
|
#include "pjsip/include/chan_pjsip.h"
|
||||||
#include "pjsip/include/dialplan_functions.h"
|
#include "pjsip/include/dialplan_functions.h"
|
||||||
|
#include "pjsip/include/cli_functions.h"
|
||||||
|
|
||||||
AST_THREADSTORAGE(uniqueid_threadbuf);
|
AST_THREADSTORAGE(uniqueid_threadbuf);
|
||||||
#define UNIQUEID_BUFSIZE 256
|
#define UNIQUEID_BUFSIZE 256
|
||||||
@@ -2468,6 +2469,15 @@ static int load_module(void)
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pjsip_channel_cli_register()) {
|
||||||
|
ast_log(LOG_ERROR, "Unable to register PJSIP Channel CLI\n");
|
||||||
|
ast_sip_session_unregister_supplement(&chan_pjsip_ack_supplement);
|
||||||
|
ast_sip_session_unregister_supplement(&pbx_start_supplement);
|
||||||
|
ast_sip_session_unregister_supplement(&chan_pjsip_supplement);
|
||||||
|
ast_sip_session_unregister_supplement(&call_pickup_supplement);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
/* since endpoints are loaded before the channel driver their device
|
/* since endpoints are loaded before the channel driver their device
|
||||||
states get set to 'invalid', so they need to be updated */
|
states get set to 'invalid', so they need to be updated */
|
||||||
if ((endpoints = ast_sip_get_endpoints())) {
|
if ((endpoints = ast_sip_get_endpoints())) {
|
||||||
@@ -2494,6 +2504,8 @@ static int unload_module(void)
|
|||||||
ao2_cleanup(pjsip_uids_onhold);
|
ao2_cleanup(pjsip_uids_onhold);
|
||||||
pjsip_uids_onhold = NULL;
|
pjsip_uids_onhold = NULL;
|
||||||
|
|
||||||
|
pjsip_channel_cli_unregister();
|
||||||
|
|
||||||
ast_sip_session_unregister_supplement(&chan_pjsip_supplement);
|
ast_sip_session_unregister_supplement(&chan_pjsip_supplement);
|
||||||
ast_sip_session_unregister_supplement(&pbx_start_supplement);
|
ast_sip_session_unregister_supplement(&pbx_start_supplement);
|
||||||
ast_sip_session_unregister_supplement(&chan_pjsip_ack_supplement);
|
ast_sip_session_unregister_supplement(&chan_pjsip_ack_supplement);
|
||||||
|
467
channels/pjsip/cli_commands.c
Normal file
467
channels/pjsip/cli_commands.c
Normal file
@@ -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(®exbuf, 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, ®exbuf);
|
||||||
|
regfree(®exbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
45
channels/pjsip/include/cli_functions.h
Normal file
45
channels/pjsip/include/cli_functions.h
Normal file
@@ -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 */
|
@@ -197,7 +197,7 @@ char *ast_sip_cli_traverse_objects(struct ast_cli_entry *e, int cmd, struct ast_
|
|||||||
ast_str_append(&context.output_buffer, 0, "\n");
|
ast_str_append(&context.output_buffer, 0, "\n");
|
||||||
formatter_entry->print_header(NULL, &context, 0);
|
formatter_entry->print_header(NULL, &context, 0);
|
||||||
ast_str_append(&context.output_buffer, 0,
|
ast_str_append(&context.output_buffer, 0,
|
||||||
" =========================================================================================\n\n");
|
"==========================================================================================\n\n");
|
||||||
|
|
||||||
if (is_container || cmd == CLI_GENERATE) {
|
if (is_container || cmd == CLI_GENERATE) {
|
||||||
container = formatter_entry->get_container(regex);
|
container = formatter_entry->get_container(regex);
|
||||||
|
@@ -1432,235 +1432,6 @@ static struct ao2_container *cli_endpoint_get_container(const char *regex)
|
|||||||
return s_container;
|
return s_container;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cli_channel_populate_container(void *obj, void *arg, int flags)
|
|
||||||
{
|
|
||||||
struct ast_channel_snapshot *snapshot = obj;
|
|
||||||
|
|
||||||
ao2_link(arg, snapshot);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cli_channel_iterate(void *container, ao2_callback_fn callback, void *args)
|
|
||||||
{
|
|
||||||
const struct ast_sip_endpoint *endpoint = container;
|
|
||||||
|
|
||||||
ast_sip_for_each_channel(endpoint, callback, args);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
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_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_channel_hash(const void *obj, int flags)
|
|
||||||
{
|
|
||||||
const struct ast_channel_snapshot *snapshot = obj;
|
|
||||||
|
|
||||||
if (flags & OBJ_SEARCH_OBJECT) {
|
|
||||||
return ast_str_hash(snapshot->name);
|
|
||||||
} else if (flags & OBJ_SEARCH_KEY) {
|
|
||||||
return ast_str_hash(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cli_endpoint_gather_channels(void *obj, void *arg, int flags)
|
|
||||||
{
|
|
||||||
struct ast_sip_endpoint *endpoint = obj;
|
|
||||||
struct ao2_container *channels = arg;
|
|
||||||
|
|
||||||
ast_sip_for_each_channel(endpoint, cli_channel_populate_container, channels);
|
|
||||||
|
|
||||||
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 *cli_channel_get_container(const char *regex)
|
|
||||||
{
|
|
||||||
RAII_VAR(struct ao2_container *, parent_container, NULL, ao2_cleanup);
|
|
||||||
struct ao2_container *child_container;
|
|
||||||
regex_t regexbuf;
|
|
||||||
|
|
||||||
parent_container = cli_endpoint_get_container("");
|
|
||||||
if (!parent_container) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
child_container = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, 17,
|
|
||||||
cli_channel_hash, cli_channel_sort, cli_channel_compare);
|
|
||||||
if (!child_container) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
ao2_callback(parent_container, OBJ_NODATA, cli_endpoint_gather_channels, child_container);
|
|
||||||
|
|
||||||
if (!ast_strlen_zero(regex)) {
|
|
||||||
if (regcomp(®exbuf, 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, ®exbuf);
|
|
||||||
regfree(®exbuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
return child_container;
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
RAII_VAR(struct ao2_container *, container, cli_channel_get_container(""), ao2_cleanup);
|
|
||||||
|
|
||||||
return ao2_find(container, id, OBJ_KEY | OBJ_NOLOCK);
|
|
||||||
}
|
|
||||||
|
|
||||||
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(sec)>\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;
|
|
||||||
struct timeval current_time;
|
|
||||||
char *print_name = NULL;
|
|
||||||
int print_name_len;
|
|
||||||
int indent;
|
|
||||||
int flexwidth;
|
|
||||||
|
|
||||||
ast_assert(context->output_buffer != NULL);
|
|
||||||
|
|
||||||
gettimeofday(¤t_time, NULL);
|
|
||||||
|
|
||||||
print_name_len = strlen(snapshot->name) + strlen(snapshot->appl) + 2;
|
|
||||||
if (!(print_name = alloca(print_name_len))) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
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_str_append(&context->output_buffer, 0, "%*s: %-*.*s %-12.12s %11ld\n",
|
|
||||||
CLI_INDENT_TO_SPACES(context->indent_level), "Channel",
|
|
||||||
flexwidth, flexwidth,
|
|
||||||
print_name,
|
|
||||||
ast_state2str(snapshot->state),
|
|
||||||
current_time.tv_sec - snapshot->creationtime.tv_sec);
|
|
||||||
|
|
||||||
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_endpoint_iterate(void *obj, ao2_callback_fn callback, void *args)
|
static int cli_endpoint_iterate(void *obj, ao2_callback_fn callback, void *args)
|
||||||
{
|
{
|
||||||
ao2_callback(obj, OBJ_NODATA, callback, args);
|
ao2_callback(obj, OBJ_NODATA, callback, args);
|
||||||
@@ -1779,21 +1550,6 @@ static int cli_endpoint_print_body(void *obj, void *arg, int flags)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static struct ast_cli_entry cli_commands[] = {
|
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, "List PJSIP Endpoints",
|
AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "List PJSIP Endpoints",
|
||||||
.command = "pjsip list endpoints",
|
.command = "pjsip list endpoints",
|
||||||
.usage = "Usage: pjsip list endpoints [ like <pattern> ]\n"
|
.usage = "Usage: pjsip list endpoints [ like <pattern> ]\n"
|
||||||
@@ -1987,21 +1743,6 @@ int ast_res_pjsip_initialize_configuration(void)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
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");
|
|
||||||
ast_sorcery_unref(sip_sorcery);
|
|
||||||
sip_sorcery = NULL;
|
|
||||||
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;
|
|
||||||
|
|
||||||
endpoint_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
|
endpoint_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
|
||||||
if (!endpoint_formatter) {
|
if (!endpoint_formatter) {
|
||||||
ast_log(LOG_ERROR, "Unable to allocate memory for endpoint_formatter\n");
|
ast_log(LOG_ERROR, "Unable to allocate memory for endpoint_formatter\n");
|
||||||
@@ -2017,7 +1758,6 @@ int ast_res_pjsip_initialize_configuration(void)
|
|||||||
endpoint_formatter->retrieve_by_id = cli_endpoint_retrieve_by_id;
|
endpoint_formatter->retrieve_by_id = cli_endpoint_retrieve_by_id;
|
||||||
endpoint_formatter->get_id = ast_sorcery_object_get_id;
|
endpoint_formatter->get_id = ast_sorcery_object_get_id;
|
||||||
|
|
||||||
ast_sip_register_cli_formatter(channel_formatter);
|
|
||||||
ast_sip_register_cli_formatter(endpoint_formatter);
|
ast_sip_register_cli_formatter(endpoint_formatter);
|
||||||
ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
|
ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
|
||||||
|
|
||||||
@@ -2044,7 +1784,6 @@ void ast_res_pjsip_destroy_configuration(void)
|
|||||||
ast_manager_unregister(AMI_SHOW_ENDPOINTS);
|
ast_manager_unregister(AMI_SHOW_ENDPOINTS);
|
||||||
ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
|
ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
|
||||||
ast_sip_unregister_cli_formatter(endpoint_formatter);
|
ast_sip_unregister_cli_formatter(endpoint_formatter);
|
||||||
ast_sip_unregister_cli_formatter(channel_formatter);
|
|
||||||
ast_sip_destroy_cli();
|
ast_sip_destroy_cli();
|
||||||
ao2_cleanup(persistent_endpoints);
|
ao2_cleanup(persistent_endpoints);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user