From: phoneben <3232963@gmail.com> Date: Sun, 18 Jan 2026 18:34:01 +0000 (+0200) Subject: app_queue: Queue Timing Parity with Dial() and Accurate Wait Metrics X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=43b69949ec650e8c05e1ed0e5d37be31f860b912;p=thirdparty%2Fasterisk.git app_queue: Queue Timing Parity with Dial() and Accurate Wait Metrics app_queue: Set Dial-compatible timing variables Extends Queue() to set Dial-compatible timing variables (ANSWEREDTIME, DIALEDTIME) and introduces a precise QUEUEWAIT metric calculated at agent connect time, with proper initialization to prevent stale or misleading values. --- diff --git a/apps/app_queue.c b/apps/app_queue.c index 3233f384f1..fdb426aa1b 100644 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -71,6 +71,7 @@ #include #include #include +#include #include "asterisk/lock.h" #include "asterisk/file.h" @@ -303,6 +304,24 @@ If the call was successfully withdrawn from the queue, and the withdraw request was provided with optional withdraw info, the withdraw info will be stored in this variable. + + The total amount of time, in seconds, that the caller spent waiting in the queue before being connected to an agent. + + + The total amount of time, in milliseconds, that the caller spent waiting in the queue before being connected to an agent. + + + The amount of time, in seconds, that the caller spent connected to an agent. If the call was never answered, this will be set to 0. + + + The amount of time, in milliseconds, that the caller spent connected to an agent. + + + The total amount of time, in seconds, from the start of the call until it ends. This matches the behavior of Dial(). + + + The total amount of time, in milliseconds, from the start of the call until it ends. + @@ -6987,8 +7006,26 @@ static int setup_stasis_subs(struct queue_ent *qe, struct ast_channel *peer, str struct queue_end_bridge { struct call_queue *q; struct ast_channel *chan; + struct timeval start_time; }; +/*! + * \internal + * \brief Helper to set the standard Dial duration variables + */ +static void set_duration_var(struct ast_channel *chan, const char *var_base, int64_t duration) +{ + char buf[32]; + char full_var_name[128]; + + snprintf(buf, sizeof(buf), "%" PRId64, duration / 1000); + pbx_builtin_setvar_helper(chan, var_base, buf); + + snprintf(full_var_name, sizeof(full_var_name), "%s_MS", var_base); + snprintf(buf, sizeof(buf), "%" PRId64, duration); + pbx_builtin_setvar_helper(chan, full_var_name, buf); +} + static void end_bridge_callback_data_fixup(struct ast_bridge_config *bconfig, struct ast_channel *originator, struct ast_channel *terminator) { struct queue_end_bridge *qeb = bconfig->end_bridge_callback_data; @@ -7001,9 +7038,20 @@ static void end_bridge_callback(void *data) struct queue_end_bridge *qeb = data; struct call_queue *q = qeb->q; struct ast_channel *chan = qeb->chan; + int64_t answered_time_ms; if (ao2_ref(qeb, -1) == 1) { set_queue_variables(q, chan); + + /* Match Dial() timing variables */ + ast_channel_lock(chan); + ast_channel_stage_snapshot(chan); + answered_time_ms = ast_tvdiff_ms(ast_tvnow(), qeb->start_time); + set_duration_var(chan, "ANSWEREDTIME", answered_time_ms); + set_duration_var(chan, "DIALEDTIME", ast_channel_get_duration_ms(chan)); + ast_channel_stage_snapshot_done(chan); + ast_channel_unlock(chan); + /* This unrefs the reference we made in try_calling when we allocated qeb */ queue_t_unref(q, "Expire bridge_config reference"); } @@ -7560,6 +7608,8 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a ast_queue_log(queuename, ast_channel_uniqueid(qe->chan), member->membername, "CONNECT", "%ld|%s|%ld", (long) (time(NULL) - qe->start), ast_channel_uniqueid(peer), (long)(orig - to > 0 ? (orig - to) / 1000 : 0)); + /* Queue hold time until agent answered */ + set_duration_var(qe->chan, "QUEUEWAIT", (int64_t)(time(NULL) - qe->start) * 1000); blob = ast_json_pack("{s: s, s: s, s: s, s: I, s: I}", "Queue", queuename, @@ -7575,6 +7625,7 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a if ((queue_end_bridge = ao2_alloc(sizeof(*queue_end_bridge), NULL))) { queue_end_bridge->q = qe->parent; queue_end_bridge->chan = qe->chan; + queue_end_bridge->start_time = ast_tvnow(); bridge_config.end_bridge_callback = end_bridge_callback; bridge_config.end_bridge_callback_data = queue_end_bridge; bridge_config.end_bridge_callback_data_fixup = end_bridge_callback_data_fixup; @@ -8727,6 +8778,13 @@ static int queue_exec(struct ast_channel *chan, const char *data) char *opt_args[OPT_ARG_ARRAY_SIZE]; int max_forwards; int cid_allow; + /* Reset variables to avoid stale data */ + pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", ""); + pbx_builtin_setvar_helper(chan, "ANSWEREDTIME_MS", ""); + pbx_builtin_setvar_helper(chan, "DIALEDTIME", ""); + pbx_builtin_setvar_helper(chan, "DIALEDTIME_MS", ""); + pbx_builtin_setvar_helper(chan, "QUEUEWAIT", ""); + pbx_builtin_setvar_helper(chan, "QUEUEWAIT_MS", ""); if (ast_strlen_zero(data)) { ast_log(LOG_WARNING, "Queue requires an argument: queuename[,options[,URL[,announceoverride[,timeout[,agi[,gosub[,rule[,position]]]]]]]]\n"); @@ -9063,6 +9121,27 @@ check_turns: } stop: + if (qe.chan) { + ast_channel_lock(qe.chan); + ast_channel_stage_snapshot(qe.chan); + /* 1. Handle QUEUEWAIT (Total time spent waiting in queue) */ + if (ast_strlen_zero(pbx_builtin_getvar_helper(qe.chan, "QUEUEWAIT"))) { + set_duration_var(qe.chan, "QUEUEWAIT", (int64_t)(time(NULL) - qe.start) * 1000); + } + + /* 2. Handle DIALEDTIME (Total time spent from beginning of the call) */ + if (ast_strlen_zero(pbx_builtin_getvar_helper(qe.chan, "DIALEDTIME"))) { + set_duration_var(qe.chan, "DIALEDTIME", ast_channel_get_duration_ms(qe.chan)); + } + + /* 3. Handle ANSWEREDTIME (Time spent talking to an agent) */ + if (ast_strlen_zero(pbx_builtin_getvar_helper(qe.chan, "ANSWEREDTIME"))) { + /* If we are here and it's still empty, the call was never answered */ + set_duration_var(qe.chan, "ANSWEREDTIME", 0); + } + ast_channel_stage_snapshot_done(qe.chan); + ast_channel_unlock(qe.chan); + } if (res) { if (reason == QUEUE_WITHDRAW) { record_abandoned(&qe);