diff --git a/CHANGES b/CHANGES index 334632a8fe..e9e651b2b3 100644 --- a/CHANGES +++ b/CHANGES @@ -723,8 +723,12 @@ Queue changes is typically placed. * The configuration method for the "joinempty" and "leavewhenempty" options has changed to a comma-separated list of methods of determining member availability - instead of vague terms such as "yes," "loose," "no," and "strict." These old four - values are still accepted for backwards-compatibility, though. + instead of vague terms such as "yes," "loose," "no," and "strict." These old four + values are still accepted for backwards-compatibility, though. + * The average talktime is now calculated on queues. This information is reported via the + CLI commands "queue show" and "queues show"; through the AMI events AgentComplete, QueueSummary, + and QueueParams; and through the channelvariable QUEUETALKTIME if setinterfacevar=yes is set for + the queue. MeetMe Changes -------------- diff --git a/apps/app_queue.c b/apps/app_queue.c index 75915cdb34..0ce20174a7 100644 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -764,6 +764,7 @@ struct call_queue { int randomperiodicannounce; /*!< Are periodic announcments randomly chosen */ int roundingseconds; /*!< How many seconds do we round to? */ int holdtime; /*!< Current avg holdtime, based on an exponential average */ + int talktime; /*!< Current avg talktime, based on the same exponential average */ int callscompleted; /*!< Number of queue calls completed */ int callsabandoned; /*!< Number of queue calls abandoned */ int servicelevel; /*!< seconds setting for servicelevel*/ @@ -885,8 +886,8 @@ static void set_queue_variables(struct queue_ent *qe) sl = 100 * ((float) qe->parent->callscompletedinsl / (float) qe->parent->callscompleted); snprintf(interfacevar, sizeof(interfacevar), - "QUEUENAME=%s,QUEUEMAX=%d,QUEUESTRATEGY=%s,QUEUECALLS=%d,QUEUEHOLDTIME=%d,QUEUECOMPLETED=%d,QUEUEABANDONED=%d,QUEUESRVLEVEL=%d,QUEUESRVLEVELPERF=%2.1f", - qe->parent->name, qe->parent->maxlen, int2strat(qe->parent->strategy), qe->parent->count, qe->parent->holdtime, qe->parent->callscompleted, + "QUEUENAME=%s,QUEUEMAX=%d,QUEUESTRATEGY=%s,QUEUECALLS=%d,QUEUEHOLDTIME=%d,QUEUETALKTIME=%d,QUEUECOMPLETED=%d,QUEUEABANDONED=%d,QUEUESRVLEVEL=%d,QUEUESRVLEVELPERF=%2.1f", + qe->parent->name, qe->parent->maxlen, int2strat(qe->parent->strategy), qe->parent->count, qe->parent->holdtime, qe->parent->talktime, qe->parent->callscompleted, qe->parent->callsabandoned, qe->parent->servicelevel, sl); pbx_builtin_setvar_multiple(qe->chan, interfacevar); @@ -3139,8 +3140,10 @@ static int wait_our_turn(struct queue_ent *qe, int ringing, enum queue_result *r * \brief update the queue status * \retval Always 0 */ -static int update_queue(struct call_queue *q, struct member *member, int callcompletedinsl) +static int update_queue(struct call_queue *q, struct member *member, int callcompletedinsl, int newtalktime) { + int oldtalktime; + struct member *mem; struct call_queue *qtmp; struct ao2_iterator queue_iter; @@ -3169,6 +3172,9 @@ static int update_queue(struct call_queue *q, struct member *member, int callcom q->callscompleted++; if (callcompletedinsl) q->callscompletedinsl++; + /* Calculate talktime using the same exponential average as holdtime code*/ + oldtalktime = q->talktime; + q->talktime = (((oldtalktime << 2) - oldtalktime) + newtalktime) >> 2; ao2_unlock(q); return 0; } @@ -3324,7 +3330,7 @@ static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struc new_chan->exten, new_chan->context, (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos); - update_queue(qe->parent, member, callcompletedinsl); + update_queue(qe->parent, member, callcompletedinsl, (time(NULL) - callstart)); if ((datastore = ast_channel_datastore_find(new_chan, &queue_transfer_info, NULL))) { ast_channel_datastore_remove(new_chan, datastore); @@ -4093,7 +4099,7 @@ static int try_calling(struct queue_ent *qe, const char *options, char *announce ast_channel_datastore_remove(qe->chan, tds); } ast_channel_unlock(qe->chan); - update_queue(qe->parent, member, callcompletedinsl); + update_queue(qe->parent, member, callcompletedinsl, (time(NULL) - callstart)); } if (transfer_ds) { @@ -5114,8 +5120,8 @@ static int queue_function_var(struct ast_channel *chan, const char *cmd, char *d } snprintf(interfacevar, sizeof(interfacevar), - "QUEUEMAX=%d,QUEUESTRATEGY=%s,QUEUECALLS=%d,QUEUEHOLDTIME=%d,QUEUECOMPLETED=%d,QUEUEABANDONED=%d,QUEUESRVLEVEL=%d,QUEUESRVLEVELPERF=%2.1f", - q->maxlen, int2strat(q->strategy), q->count, q->holdtime, q->callscompleted, q->callsabandoned, q->servicelevel, sl); + "QUEUEMAX=%d,QUEUESTRATEGY=%s,QUEUECALLS=%d,QUEUEHOLDTIME=%d,QUEUETALKTIME=%d,QUEUECOMPLETED=%d,QUEUEABANDONED=%d,QUEUESRVLEVEL=%d,QUEUESRVLEVELPERF=%2.1f", + q->maxlen, int2strat(q->strategy), q->count, q->holdtime, q->talktime, q->callscompleted, q->callsabandoned, q->servicelevel, sl); pbx_builtin_setvar_multiple(chan, interfacevar); } @@ -5753,8 +5759,8 @@ static char *__queues_show(struct mansession *s, int fd, int argc, char **argv) sl = 0; if (q->callscompleted > 0) sl = 100 * ((float) q->callscompletedinsl / (float) q->callscompleted); - ast_str_append(&out, 0, ") in '%s' strategy (%ds holdtime), W:%d, C:%d, A:%d, SL:%2.1f%% within %ds", - int2strat(q->strategy), q->holdtime, q->weight, + ast_str_append(&out, 0, ") in '%s' strategy (%ds holdtime, %ds talktime), W:%d, C:%d, A:%d, SL:%2.1f%% within %ds", + int2strat(q->strategy), q->holdtime, q->talktime, q->weight, q->callscompleted, q->callsabandoned,sl,q->servicelevel); do_print(s, fd, ast_str_buffer(out)); if (!ao2_container_count(q->members)) @@ -5951,10 +5957,11 @@ static int manager_queues_summary(struct mansession *s, const struct message *m) "Available: %d\r\n" "Callers: %d\r\n" "HoldTime: %d\r\n" + "TalkTime: %d\r\n" "LongestHoldTime: %d\r\n" "%s" "\r\n", - q->name, qmemcount, qmemavail, qchancount, q->holdtime, qlongestholdtime, idText); + q->name, qmemcount, qmemavail, qchancount, q->holdtime, q->talktime, qlongestholdtime, idText); } ao2_unlock(q); queue_unref(q); @@ -6001,6 +6008,7 @@ static int manager_queues_status(struct mansession *s, const struct message *m) "Strategy: %s\r\n" "Calls: %d\r\n" "Holdtime: %d\r\n" + "TalkTime: %d\r\n" "Completed: %d\r\n" "Abandoned: %d\r\n" "ServiceLevel: %d\r\n" @@ -6008,7 +6016,7 @@ static int manager_queues_status(struct mansession *s, const struct message *m) "Weight: %d\r\n" "%s" "\r\n", - q->name, q->maxlen, int2strat(q->strategy), q->count, q->holdtime, q->callscompleted, + q->name, q->maxlen, int2strat(q->strategy), q->count, q->holdtime, q->talktime, q->callscompleted, q->callsabandoned, q->servicelevel, sl, q->weight, idText); /* List Queue Members */ mem_iter = ao2_iterator_init(q->members, 0); diff --git a/channels/chan_sip.c b/channels/chan_sip.c index f96322c3d6..4418b9ad7e 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -13221,6 +13221,76 @@ static char *sip_show_tcp(struct ast_cli_entry *e, int cmd, struct ast_cli_args #undef FORMAT2 } +/*! \brief CLI Command 'SIP Show Users' */ +static char *sip_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + regex_t regexbuf; + int havepattern = FALSE; + struct ao2_iterator user_iter; + struct sip_peer *user; + +#define FORMAT "%-25.25s %-15.15s %-15.15s %-15.15s %-5.5s%-10.10s\n" + + switch (cmd) { + case CLI_INIT: + e->command = "sip show users"; + e->usage = + "Usage: sip show users [like ]\n" + " Lists all known SIP users.\n" + " Optional regular expression pattern is used to filter the user list.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + switch (a->argc) { + case 5: + if (!strcasecmp(a->argv[3], "like")) { + if (regcomp(®exbuf, a->argv[4], REG_EXTENDED | REG_NOSUB)) + return CLI_SHOWUSAGE; + havepattern = TRUE; + } else + return CLI_SHOWUSAGE; + case 3: + break; + default: + return CLI_SHOWUSAGE; + } + + ast_cli(a->fd, FORMAT, "Username", "Secret", "Accountcode", "Def.Context", "ACL", "NAT"); + + user_iter = ao2_iterator_init(peers, 0); + while ((user = ao2_iterator_next(&user_iter))) { + ao2_lock(user); + if (user->onlymatchonip == FALSE) { + ao2_unlock(user); + unref_peer(user, "sip show users"); + continue; + } + + if (havepattern && regexec(®exbuf, user->name, 0, NULL, 0)) { + ao2_unlock(user); + unref_peer(user, "sip show users"); + continue; + } + + ast_cli(a->fd, FORMAT, user->name, + user->secret, + user->accountcode, + user->context, + cli_yesno(user->ha != NULL), + nat2str(ast_test_flag(&user->flags[0], SIP_NAT))); + ao2_unlock(user); + unref_peer(user, "sip show users"); + } + + if (havepattern) + regfree(®exbuf); + + return CLI_SUCCESS; +#undef FORMAT +} + /*! \brief Manager Action SIPShowRegistry description */ static char mandescr_show_registry[] = "Description: Lists all registration requests and status\n" @@ -14244,6 +14314,117 @@ static char *_sip_show_peer(int type, int fd, struct mansession *s, const struct return CLI_SUCCESS; } +/*! \brief Do completion on user name */ +static char *complete_sip_user(const char *word, int state) +{ + char *result = NULL; + int wordlen = strlen(word); + int which = 0; + struct ao2_iterator user_iter; + struct sip_peer *user; + + user_iter = ao2_iterator_init(peers, 0); + while ((user = ao2_iterator_next(&user_iter))) { + ao2_lock(user); + if (user->onlymatchonip == FALSE) { + ao2_unlock(user); + unref_peer(user, "complete sip user"); + continue; + } + /* locking of the object is not required because only the name and flags are being compared */ + if (!strncasecmp(word, user->name, wordlen) && ++which > state) { + result = ast_strdup(user->name); + } + ao2_unlock(user); + unref_peer(user, "complete sip user"); + } + return result; +} +/*! \brief Support routine for 'sip show user' CLI */ +static char *complete_sip_show_user(const char *line, const char *word, int pos, int state) +{ + if (pos == 3) + return complete_sip_user(word, state); + + return NULL; +} + +/*! \brief Show one user in detail */ +static char *sip_show_user(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + char cbuf[256]; + struct sip_peer *user; + struct ast_variable *v; + int load_realtime; + + switch (cmd) { + case CLI_INIT: + e->command = "sip show user"; + e->usage = + "Usage: sip show user [load]\n" + " Shows all details on one SIP user and the current status.\n" + " Option \"load\" forces lookup of peer in realtime storage.\n"; + return NULL; + case CLI_GENERATE: + return complete_sip_show_user(a->line, a->word, a->pos, a->n); + } + + if (a->argc < 4) + return CLI_SHOWUSAGE; + + /* Load from realtime storage? */ + load_realtime = (a->argc == 5 && !strcmp(a->argv[4], "load")) ? TRUE : FALSE; + + if ((user = find_peer(a->argv[3], NULL, load_realtime, TRUE, FALSE))) { + ao2_lock(user); + ast_cli(a->fd, "\n\n"); + ast_cli(a->fd, " * Name : %s\n", user->name); + ast_cli(a->fd, " Secret : %s\n", ast_strlen_zero(user->secret)?"":""); + ast_cli(a->fd, " MD5Secret : %s\n", ast_strlen_zero(user->md5secret)?"":""); + ast_cli(a->fd, " Context : %s\n", user->context); + ast_cli(a->fd, " Language : %s\n", user->language); + if (!ast_strlen_zero(user->accountcode)) + ast_cli(a->fd, " Accountcode : %s\n", user->accountcode); + ast_cli(a->fd, " AMA flags : %s\n", ast_cdr_flags2str(user->amaflags)); + ast_cli(a->fd, " Transfer mode: %s\n", transfermode2str(user->allowtransfer)); + ast_cli(a->fd, " MaxCallBR : %d kbps\n", user->maxcallbitrate); + ast_cli(a->fd, " CallingPres : %s\n", ast_describe_caller_presentation(user->callingpres)); + ast_cli(a->fd, " Call limit : %d\n", user->call_limit); + ast_cli(a->fd, " Callgroup : "); + print_group(a->fd, user->callgroup, 0); + ast_cli(a->fd, " Pickupgroup : "); + print_group(a->fd, user->pickupgroup, 0); + ast_cli(a->fd, " Callerid : %s\n", ast_callerid_merge(cbuf, sizeof(cbuf), user->cid_name, user->cid_num, "")); + ast_cli(a->fd, " ACL : %s\n", cli_yesno(user->ha != NULL)); + ast_cli(a->fd, " Sess-Timers : %s\n", stmode2str(user->stimer.st_mode_oper)); + ast_cli(a->fd, " Sess-Refresh : %s\n", strefresher2str(user->stimer.st_ref)); + ast_cli(a->fd, " Sess-Expires : %d secs\n", user->stimer.st_max_se); + ast_cli(a->fd, " Sess-Min-SE : %d secs\n", user->stimer.st_min_se); + + ast_cli(a->fd, " Codec Order : ("); + print_codec_to_cli(a->fd, &user->prefs); + ast_cli(a->fd, ")\n"); + + ast_cli(a->fd, " Auto-Framing: %s \n", cli_yesno(user->autoframing)); + if (user->chanvars) { + ast_cli(a->fd, " Variables :\n"); + for (v = user->chanvars ; v ; v = v->next) + ast_cli(a->fd, " %s = %s\n", v->name, v->value); + } + + ast_cli(a->fd, "\n"); + + ao2_unlock(user); + unref_peer(user, "sip show user"); + } else { + ast_cli(a->fd, "User %s not found.\n", a->argv[3]); + ast_cli(a->fd, "\n"); + } + + return CLI_SUCCESS; +} + + static char *sip_show_sched(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { char cbuf[2256]; @@ -24002,6 +24183,8 @@ static struct ast_cli_entry cli_sip[] = { AST_CLI_DEFINE(sip_show_channel, "Show detailed SIP channel info"), AST_CLI_DEFINE(sip_show_history, "Show SIP dialog history"), AST_CLI_DEFINE(sip_show_peer, "Show details on specific SIP peer"), + AST_CLI_DEFINE(sip_show_users, "List defined SIP users"), + AST_CLI_DEFINE(sip_show_user, "Show details on specific SIP user"), AST_CLI_DEFINE(sip_qualify_peer, "Send an OPTIONS packet to a peer"), AST_CLI_DEFINE(sip_show_sched, "Present a report on the status of the sched queue"), AST_CLI_DEFINE(sip_prune_realtime, "Prune cached Realtime users/peers"),