[Asterisk-cvs] asterisk/apps app_queue.c,1.50,1.51

markster at lists.digium.com markster at lists.digium.com
Sat Mar 13 01:07:11 CST 2004


Update of /usr/cvsroot/asterisk/apps
In directory mongoose.digium.com:/tmp/cvs-serv18011/apps

Modified Files:
	app_queue.c 
Log Message:
Merge queue changes from Bug #214


Index: app_queue.c
===================================================================
RCS file: /usr/cvsroot/asterisk/apps/app_queue.c,v
retrieving revision 1.50
retrieving revision 1.51
diff -u -d -r1.50 -r1.51
--- app_queue.c	3 Mar 2004 08:53:04 -0000	1.50
+++ app_queue.c	13 Mar 2004 06:00:41 -0000	1.51
@@ -7,6 +7,21 @@
  *
  * Mark Spencer <markster at linux-support.net>
  *
+ * These features added by David C. Troy <dave at toad.net>:
+ *    - Per-queue holdtime calculation
+ *    - Estimated holdtime announcement
+ *    - Position announcement
+ *    - Abandoned/completed call counters
+ *    - Failout timer passed as optional app parameter
+ *    - Optional monitoring of calls, started when call is answered
+ *
+ * Patch Version 1.07 2003-12-24 01
+ *
+ * Added servicelevel statistic by Michiel Betel <michiel at betel.nl>
+ *
+ * Fixed ot work with CVS as of 2004-02-25 and released as 1.07a
+ * by Matthew Enger <m.enger at xi.com.au>
+ *
  * This program is free software, distributed under the terms of
  * the GNU General Public License
  */
@@ -25,6 +40,7 @@
 #include <asterisk/cli.h>
 #include <asterisk/manager.h> /* JDG */
 #include <asterisk/config.h>
+#include <asterisk/monitor.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <unistd.h>
@@ -67,7 +83,7 @@
 static char *synopsis = "Queue a call for a call queue";
 
 static char *descrip =
-"  Queue(queuename[|options[|URL][|announceoverride]]):\n"
+"  Queue(queuename[|options[|URL][|announceoverride][|timeout]]):\n"
 "Queues an incoming call in a particular call queue as defined in queues.conf.\n"
 "  This application returns -1 if the originating channel hangs up, or if the\n"
 "call is bridged and  either of the parties in the bridge terminate the call.\n"
@@ -81,7 +97,9 @@
 "  In addition to transferring the call, a call may be parked and then picked\n"
 "up by another user.\n"
 "  The optional URL will be sent to the called party if the channel supports\n"
-"it.\n";
+"it.\n"
+"  The timeout will cause the queue to fail out after a specified number of\n"
+"seconds, checked between each queues.conf 'timeout' and 'retry' cycle.\n";
 
 // [PHM 06/26/03]
 static char *app_aqm = "AddQueueMember" ;
@@ -127,12 +145,15 @@
 struct queue_ent {
 	struct ast_call_queue *parent;	/* What queue is our parent */
 	char moh[80];				/* Name of musiconhold to be used */
-	char announce[80];		/* Announcement to play */
+	char announce[80];		/* Announcement to play for member when call is answered */
 	char context[80];		/* Context when user exits queue */
 	int pos;					/* Where we are in the queue */
+	int last_pos_said;              /* Last position we told the user */
+	time_t last_pos;                /* Last time we told the user their position */
 	int opos;					/* Where we started in the queue */
 	int handled;				/* Whether our call was handled */
 	time_t start;				/* When we started holding */
+	int queuetimeout;               /* How many seconds before timing out of queue */
 	struct ast_channel *chan;	/* Our channel */
 	struct queue_ent *next;		/* The next queue entry */
 };
@@ -151,10 +172,24 @@
 	ast_mutex_t	lock;	
 	char name[80];			/* Name of the queue */
 	char moh[80];			/* Name of musiconhold to be used */
-	char announce[80];		/* Announcement to play */
-	char context[80];		/* Announcement to play */
+	char announce[80];		/* Announcement to play when call is answered */
+	char context[80];		/* Context for this queue */
 	int strategy;			/* Queueing strategy */
-	int announcetimeout;	/* How often to announce their position */
+	int announcefrequency;          /* How often to announce their position */
+	int announceholdtime;           /* When to announce holdtime: 0 = never, -1 = every announcement, 1 = only once */
+	int holdtime;                   /* Current avg holdtime for this queue, based on recursive boxcar filter */
+	int callscompleted;             /* Number of queue calls completed */
+	int callsabandoned;             /* Number of queue calls abandoned */
+	int servicelevel;               /* seconds setting for servicelevel*/
+	int callscompletedinsl;         /* Number of queue calls answererd with servicelevel*/
+	char monfmt[8];                 /* Format to use when recording calls */
+	char sound_next[80];            /* Sound file: "Your call is now first in line" (def. queue-youarenext) */
+	char sound_thereare[80];        /* Sound file: "There are currently" (def. queue-thereare) */
+	char sound_calls[80];           /* Sound file: "calls waiting to speak to a representative." (def. queue-callswaiting)*/
+	char sound_holdtime[80];        /* Sound file: "The current estimated total holdtime is" (def. queue-holdtime) */
+	char sound_minutes[80];         /* Sound file: "minutes." (def. queue-minutes) */
+	char sound_thanks[80];          /* Sound file: "Thank you for your patience." (def. queue-thankyou) */
+
 	int count;				/* How many entries are in the queue */
 	int maxlen;				/* Max number of entries in queue */
 
@@ -288,6 +323,102 @@
 	free(q);
 }
 
+static int play_file(struct ast_channel *chan, char *filename)
+{
+	int res;
+
+	ast_stopstream(chan);
+	res = ast_streamfile(chan, filename, chan->language);
+
+	if (!res)
+		res = ast_waitstream(chan, "");
+	else
+		res = 0;
+
+	if (res) {
+		ast_log(LOG_WARNING, "ast_streamfile failed on %s \n", chan->name);
+		res = 0;
+	}
+	ast_stopstream(chan);
+
+	return res;
+}
+
+static int say_position(struct queue_ent *qe)
+{
+	int res = 0, avgholdmins;
+	time_t now;
+
+	/* Check to see if this is ludicrous -- if we just announced position, don't do it again*/
+	time(&now);
+	if ( (now - qe->last_pos) < 15 )
+		return -1;
+
+	/* If either our position has changed, or we are over the freq timer, say position */
+	if ( (qe->last_pos_said == qe->pos) && ((now - qe->last_pos) < qe->parent->announcefrequency) )
+		return -1;
+
+	ast_moh_stop(qe->chan);
+	/* Say we're next, if we are */
+	if (qe->pos == 1) {
+		res += play_file(qe->chan, qe->parent->sound_next);
+		goto posout;
+	} else {
+		res += play_file(qe->chan, qe->parent->sound_thereare);
+		res += ast_say_number(qe->chan, qe->pos, AST_DIGIT_ANY, qe->chan->language);
+		res += play_file(qe->chan, qe->parent->sound_calls);
+	}
+
+	/* Round hold time to nearest minute */
+	avgholdmins = ( (qe->parent->holdtime + 30) - (now - qe->start) ) / 60;
+	ast_verbose(VERBOSE_PREFIX_3 "Hold time for %s is %d minutes\n", qe->parent->name, avgholdmins);
+
+	/* If the hold time is >1 min, if it's enabled, and if it's not
+	   supposed to be only once and we have already said it, say it */
+	if (avgholdmins > 1 && (qe->parent->announceholdtime) && (!(qe->parent->announceholdtime==1 && qe->last_pos)) ) {
+		res += play_file(qe->chan, qe->parent->sound_holdtime);
+		res += ast_say_number(qe->chan, avgholdmins, AST_DIGIT_ANY, qe->chan->language);
+		res += play_file(qe->chan, qe->parent->sound_minutes);
+	}
+
+	posout:
+	/* Set our last_pos indicators */
+ 	qe->last_pos = now;
+	qe->last_pos_said = qe->pos;
+
+	ast_verbose(VERBOSE_PREFIX_3 "Told %s in %s their queue position (which was %d)\n", qe->chan->name, qe->parent->name, qe->pos);
+	res += play_file(qe->chan, qe->parent->sound_thanks);
+	ast_moh_start(qe->chan, qe->moh);
+
+	return (res>0);
+}
+
+static void record_abandoned(struct queue_ent *qe)
+{
+	ast_mutex_lock(&qe->parent->lock);
+	qe->parent->callsabandoned++;
+	ast_mutex_unlock(&qe->parent->lock);
+}
+
+static void recalc_holdtime(struct queue_ent *qe)
+{
+	int oldvalue, newvalue;
+
+	/* Calculate holdtime using a recursive boxcar filter */
+	/* Thanks to SRT for this contribution */
+	/* 2^2 (4) is the filter coefficient; a higher exponent would give old entries more weight */
+
+	newvalue = time(NULL) - qe->start;
+
+	ast_mutex_lock(&qe->parent->lock);
+	if (newvalue <= qe->parent->servicelevel)
+       		qe->parent->callscompletedinsl++;
+	oldvalue = qe->parent->holdtime;
+	qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newvalue) >> 2;
+	ast_mutex_unlock(&qe->parent->lock);
+}
+
+
 static void leave_queue(struct queue_ent *qe)
 {
 	struct ast_call_queue *q;
@@ -317,6 +448,7 @@
 			else
 				q->head = cur->next;
 		} else {
+			/* Renumber the people after us in the queue based on a new count */
 			cur->pos = ++pos;
 			prev = cur;
 		}
@@ -611,12 +743,29 @@
 {
 	struct queue_ent *ch;
 	int res = 0;
+	time_t now;
+
+	/* This is the holding pen for callers 2 through maxlen */
 	for (;;) {
 		/* Atomically read the parent head -- does not need a lock */
 		ch = qe->parent->head;
+
 		/* If we are now at the top of the head, break out */
-		if (qe->parent->head == qe)
+		if (qe == ch)
+			break;
+
+		/* If we have timed out, break out */
+		if ( qe->queuetimeout ) {
+			time(&now);
+			if ( (now - qe->start) >= qe->queuetimeout )
 			break;
+		}
+
+		/* Make a position announcement, if enabled */
+		if (qe->parent->announcefrequency)
+			say_position(qe);
+
+
 		/* Wait a second before checking again */
 		res = ast_waitfordigit(qe->chan, RECHECK * 1000);
 		if (res)
@@ -640,6 +789,7 @@
 		}
 		cur = cur->next;
 	}
+	q->callscompleted++;
 	ast_mutex_unlock(&q->lock);
 	return 0;
 }
@@ -793,6 +943,7 @@
 	if (!peer) {
 		if (to) {
 			/* Musta gotten hung up */
+			record_abandoned(qe);
 			res = -1;
 		} else {
 			if (digit && valid_exit(qe, digit))
@@ -817,6 +968,7 @@
 			ast_channel_setoption(peer,AST_OPTION_TONE_VERIFY,&zapx,sizeof(char),0);
 		}
 		/* Update parameters for the queue */
+		recalc_holdtime(qe);
 		update_queue(qe->parent, lpeer);
 		hanguptree(outgoing, peer);
 		/* Stop music on hold */
@@ -849,6 +1001,10 @@
 			ast_hangup(peer);
 			return -1;
 		}
+		/* Begin Monitoring */
+		if (qe->parent->monfmt && *qe->parent->monfmt) {
+			ast_monitor_start( peer, qe->parent->monfmt, qe->chan->cdr->uniqueid, 1 );
+		}
 		/* Drop out of the queue at this point, to prepare for next caller */
 		leave_queue(qe);			
  		/* JDG: sendurl */
@@ -1120,6 +1276,7 @@
 	char *options = NULL;
 	char *url = NULL;
 	char *announceoverride = NULL;
+	char *queuetimeoutstr = NULL;
 	/* whether to exit Queue application after the timeout hits */
 	int go_on = 0;
 
@@ -1134,6 +1291,9 @@
 	}
 	
 	LOCAL_USER_ADD(u);
+
+	/* Setup our queue entry */
+	memset(&qe, 0, sizeof(qe));
 	
 	/* Parse our arguments XXX Check for failure XXX */
 	strncpy(info, (char *)data, strlen((char *)data) + AST_MAX_EXTENSION-1);
@@ -1151,25 +1311,39 @@
 				if (announceoverride) {
 					*announceoverride = '\0';
 					announceoverride++;
+					queuetimeoutstr = strchr(announceoverride, '|');
+					if (queuetimeoutstr) {
+						*queuetimeoutstr = '\0';
+						queuetimeoutstr++;
+						qe.queuetimeout = atoi(queuetimeoutstr);
+					} else {
+						qe.queuetimeout = 0;
 				}
 			}
 		}
 	}
-	if (option_debug)
-		ast_log(LOG_DEBUG, "queue: %s, options: %s, url: %s, announce: %s\n",
-					queuename, options, url, announceoverride);
-	/* Setup our queue entry */
-	memset(&qe, 0, sizeof(qe));
+	}
+
+	printf("queue: %s, options: %s, url: %s, announce: %s, timeout: %d\n",
+		queuename, options, url, announceoverride, qe.queuetimeout);
+
+
 	qe.chan = chan;
 	qe.start = time(NULL);
+	qe.last_pos_said = 0;
+	qe.last_pos = 0;
 	if (!join_queue(queuename, &qe)) {
 		ast_queue_log(queuename, chan->uniqueid, "NONE", "ENTERQUEUE", "%s|%s", url ? url : "", chan->callerid ? chan->callerid : "");
 		/* Start music on hold */
 		ast_moh_start(chan, qe.moh);
 		for (;;) {
+			/* This is the wait loop for callers 2 through maxlen */
+
 			res = wait_our_turn(&qe);
 			/* If they hungup, return immediately */
 			if (res < 0) {
+				/* Record this abandoned call */
+				record_abandoned(&qe);
 				ast_queue_log(queuename, chan->uniqueid, "NONE", "ABANDON", "%d|%d|%ld", qe.pos, qe.opos, (long)time(NULL) - qe.start);
 				if (option_verbose > 2) {
 					ast_verbose(VERBOSE_PREFIX_3 "User disconnected while waiting their turn\n");
@@ -1186,6 +1360,22 @@
 		}
 		if (!res) {
 			for (;;) {
+				/* This is the wait loop for the head caller*/
+				/* To exit, they may get their call answered; */
+				/* they may dial a digit from the queue context; */
+				/* or, they may may timeout. */
+
+				/* Leave if we have exceeded our queuetimeout */
+				if (qe.queuetimeout && ( (time(NULL) - qe.start) >= qe.queuetimeout) ) {
+					res = 0;
+					break;
+				}
+
+				/* Make a position announcement, if enabled */
+				if (qe.parent->announcefrequency)
+					say_position(&qe);
+
+				/* Try calling all queue members for 'timeout' seconds */
 				res = try_calling(&qe, options, announceoverride, url, &go_on);
 				if (res) {
 					if (res < 0) {
@@ -1195,6 +1385,14 @@
 						ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%c|%d", res, qe.pos);
 					break;
 				}
+
+				/* Leave if we have exceeded our queuetimeout */
+				if (qe.queuetimeout && ( (time(NULL) - qe.start) >= qe.queuetimeout) ) {
+					res = 0;
+					break;
+				}
+
+				/* OK, we didn't get anybody; wait for 'retry' seconds; may get a digit to exit with */
 				res = wait_a_bit(&qe);
 				if (res < 0) {
 					ast_queue_log(queuename, chan->uniqueid, "NONE", "ABANDON", "%d|%d|%ld", qe.pos, qe.opos, (long)time(NULL) - qe.start);
@@ -1208,18 +1406,20 @@
 					ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%c|%d", res, qe.pos);
 					break;
 				}
-				/* exit after a timeout if 'n' option enabled */
+				/* exit after 'timeout' cycle if 'n' option enabled */
 				if (go_on) {
 					ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
 					res = 0;
 					break;
 				}
+
 			}
 		}
 		/* Don't allow return code > 0 */
 		if (res > 0 && res != AST_PBX_KEEPALIVE) {
 			res = 0;	
 			ast_moh_stop(chan);
+			ast_stopstream(chan);
 		}
 		leave_queue(&qe);
 	} else {
@@ -1281,10 +1481,24 @@
 				q->retry = 0;
 				q->timeout = -1;
 				q->maxlen = 0;
+				q->announcefrequency = 0;
+				q->announceholdtime = 0;
+				q->holdtime = 0;
+				q->callscompleted = 0;
+				q->callsabandoned = 0;
+				q->callscompletedinsl = 0;
+				q->servicelevel = 0;
 				free_members(q, 0);
 				strcpy(q->moh, "");
 				strcpy(q->announce, "");
 				strcpy(q->context, "");
+				strcpy(q->monfmt, "");
+				strcpy(q->sound_next, "queue-youarenext");
+				strcpy(q->sound_thereare, "queue-thereare");
+				strcpy(q->sound_calls, "queue-callswaiting");
+				strcpy(q->sound_holdtime, "queue-holdtime");
+				strcpy(q->sound_minutes, "queue-minutes");
+				strcpy(q->sound_thanks, "queue-thankyou");
 				prev = q->members;
 				if (prev) {
 					/* find the end of any dynamic members */
@@ -1329,10 +1543,30 @@
 						strncpy(q->context, var->value, sizeof(q->context) - 1);
 					} else if (!strcasecmp(var->name, "timeout")) {
 						q->timeout = atoi(var->value);
+					} else if (!strcasecmp(var->name, "monitor-format")) {
+						strncpy(q->monfmt, var->value, sizeof(q->monfmt) - 1);
+					} else if (!strcasecmp(var->name, "queue-youarenext")) {
+						strncpy(q->sound_next, var->value, sizeof(q->sound_next) - 1);
+					} else if (!strcasecmp(var->name, "queue-thereare")) {
+						strncpy(q->sound_thereare, var->value, sizeof(q->sound_thereare) - 1);
+					} else if (!strcasecmp(var->name, "queue-callswaiting")) {
+						strncpy(q->sound_calls, var->value, sizeof(q->sound_calls) - 1);
+					} else if (!strcasecmp(var->name, "queue-holdtime")) {
+						strncpy(q->sound_holdtime, var->value, sizeof(q->sound_holdtime) - 1);
+					} else if (!strcasecmp(var->name, "queue-minutes")) {
+						strncpy(q->sound_minutes, var->value, sizeof(q->sound_minutes) - 1);
+					} else if (!strcasecmp(var->name, "queue-thankyou")) {
+						strncpy(q->sound_thanks, var->value, sizeof(q->sound_thanks) - 1);
+					} else if (!strcasecmp(var->name, "announce-frequency")) {
+						q->announcefrequency = atoi(var->value);
+					} else if (!strcasecmp(var->name, "announce-holdtime")) {
+						q->announceholdtime = (!strcasecmp(var->value,"once")) ? 1 : ast_true(var->value);
 					} else if (!strcasecmp(var->name, "retry")) {
 						q->retry = atoi(var->value);
 					} else if (!strcasecmp(var->name, "maxlen")) {
 						q->maxlen = atoi(var->value);
+					} else if (!strcasecmp(var->name, "servicelevel")) {
+						q->servicelevel= atoi(var->value);
 					} else if (!strcasecmp(var->name, "strategy")) {
 						q->strategy = strat2int(var->value);
 						if (q->strategy < 0) {
@@ -1390,6 +1624,8 @@
 	time_t now;
 	char max[80];
 	char calls[80];
+	float sl = 0;
+
 	time(&now);
 	if ((!queue_show && argc != 2) || (queue_show && argc != 3))
 		return RESULT_SHOWUSAGE;
@@ -1420,7 +1656,11 @@
 			snprintf(max, sizeof(max), "%d", q->maxlen);
 		else
 			strcpy(max, "unlimited");
-		ast_cli(fd, "%-12.12s has %d calls (max %s) in '%s' strategy\n", q->name, q->count, max, int2strat(q->strategy));
+		sl = 0;
+		if(q->callscompleted > 0)
+			sl = 100*((float)q->callscompletedinsl/(float)q->callscompleted);
+		ast_cli(fd, "%-12.12s has %d calls (max %s) in '%s' strategy (%ds holdtime), C:%d, A:%d, SL:%2.1f%% within %ds\n",
+			q->name, q->count, max, int2strat(q->strategy), q->holdtime, q->callscompleted, q->callsabandoned,sl,q->servicelevel);
 		if (q->members) {
 			ast_cli(fd, "   Members: \n");
 			for (mem = q->members; mem; mem = mem->next) {
@@ -1502,6 +1742,8 @@
 	char idText[256] = "";
 	struct ast_call_queue *q;
 	struct queue_ent *qe;
+	float sl = 0;
+	struct member *mem;
 	astman_send_ack(s, m, "Queue status will follow");
 	time(&now);
 	ast_mutex_lock(&qlock);
@@ -1511,21 +1753,43 @@
 	}
 	while(q) {
 		ast_mutex_lock(&q->lock);
+
+		/* List queue properties */
+		if(q->callscompleted > 0)
+			sl = 100*((float)q->callscompletedinsl/(float)q->callscompleted);
 		ast_cli(s->fd, "Event: QueueParams\r\n"
 					"Queue: %s\r\n"
 					"Max: %d\r\n"
 					"Calls: %d\r\n"
+					"Holdtime: %d\r\n"
+					"Completed: %d\r\n"
+					"Abandoned: %d\r\n"
+					"ServiceLevel: %d\r\n"
+					"ServicelevelPerf: %2.1f\r\n"
 					"%s"
 					"\r\n",
-						q->name, q->maxlen, q->count,idText);
-#if 0
-		/* Do we care about queue members? */					
+						q->name, q->maxlen, q->count, q->holdtime, q->callscompleted,
+						q->callsabandoned, q->servicelevel, sl, idText);
+
+		/* List Queue Members */
 		for (mem = q->members; mem; mem = mem->next) 
-			ast_cli(fd, "      %s/%s\n", mem->tech, mem->loc);
-#endif			
+			ast_cli(s->fd, "Event: QueueMember\r\n"
+				"Queue: %s\r\n"
+				"Location: %s/%s\r\n"
+				"Membership: %s\r\n"
+				"Penalty: %d\r\n"
+				"CallsTaken: %d\r\n"
+				"LastCall: %ld\r\n"
+				"%s"
+				"\r\n",
+					q->name, mem->tech, mem->loc, mem->dynamic ? "dynamic" : "static",
+					mem->penalty, mem->calls, mem->lastcall, idText);
+
+		/* List Queue Entries */
+
 		pos = 1;
 		for (qe = q->head; qe; qe = qe->next) 
-			ast_cli(s->fd, "Event: QueueMember\r\n"
+			ast_cli(s->fd, "Event: QueueEntry\r\n"
 				"Queue: %s\r\n"
 				"Position: %d\r\n"
 				"Channel: %s\r\n"




More information about the svn-commits mailing list