From 4405813297fa41d441ab2cb28387df09b90aea68 Mon Sep 17 00:00:00 2001 From: Bradley Latus <brad.latus@gmail.com> Date: Tue, 8 Jun 2010 23:48:17 +0000 Subject: [PATCH] Add High Resolution Times to CDRs for Asterisk People expressed an interest in having access to the exact length of calls to a finer degree than seconds. See the CHANGES and UPGRADE.txt for usage also updated the sample configs to note the change. Patch by snuffy. (closes issue #16559) Reported by: cianmaher Tested by: cianmaher, snuffy Review: https://reviewboard.asterisk.org/r/461/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@269153 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- CHANGES | 5 ++ UPGRADE.txt | 4 ++ addons/cdr_mysql.c | 34 ++++++++++++ cdr/cdr_adaptive_odbc.c | 34 ++++++++++++ cdr/cdr_odbc.c | 28 +++++++++- cdr/cdr_pgsql.c | 4 +- cdr/cdr_sqlite.c | 29 ++++++++++- cdr/cdr_tds.c | 71 +++++++++++++++++++++++++- configs/cdr_custom.conf.sample | 4 +- configs/cdr_odbc.conf.sample | 1 + configs/cdr_sqlite3_custom.conf.sample | 3 ++ configs/cdr_syslog.conf.sample | 2 + configs/cdr_tds.conf.sample | 11 ++++ funcs/func_cdr.c | 40 +++++++++++++-- 14 files changed, 258 insertions(+), 12 deletions(-) diff --git a/CHANGES b/CHANGES index 3aac8c698b..ebc722a4c5 100644 --- a/CHANGES +++ b/CHANGES @@ -223,6 +223,8 @@ Dialplan Functions Returns "0" if there is a B channel associated with the call. Returns "1" if no B channel is associated with the call. The call is either on hold or is a call waiting call. + * Added option to dialplan function CDR(), the 'f' option + allows for high resolution times for billsec and duration fields. Dialplan Variables ------------------ @@ -414,6 +416,9 @@ CDR See configs/cdr_syslog.conf.sample for more information. * A 'sequence' field has been added to CDRs which can be combined with linkedid or uniqueid to uniquely identify a CDR. + * Handling of billsec and duration field has changed. If your table definition + specifies those fields as float,double or similar they will now be logged with + microsecond accuracy instead of a whole integer. Calendaring for Asterisk ------------------------ diff --git a/UPGRADE.txt b/UPGRADE.txt index b02c1f493e..c3139b9a52 100644 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -88,6 +88,10 @@ From 1.6.2 to 1.8: * When a call is redirected inside of a Dial, the app and appdata fields of the CDR will now be set to "AppDial" and "(Outgoing Line)" instead of being blank. +* The CDR handling of billsec and duration field has changed. If your table + definition specifies those fields as float,double or similar they will now + be logged with microsecond accuracy instead of a whole integer. + From 1.6.1 to 1.6.2: * SIP no longer sends the 183 progress message for early media by diff --git a/addons/cdr_mysql.c b/addons/cdr_mysql.c index a6a527b24d..504163a21f 100644 --- a/addons/cdr_mysql.c +++ b/addons/cdr_mysql.c @@ -288,6 +288,40 @@ db_reconnect: ast_str_append(&sql2, 0, ","); } + if (!strcasecmp(cdrname, "billsec") && + (strstr(entry->type, "float") || + strstr(entry->type, "double") || + strstr(entry->type, "decimal") || + strstr(entry->type, "numeric") || + strstr(entry->type, "real"))) { + + if (!ast_tvzero(cdr->answer)) { + snprintf(workspace, sizeof(workspace), "%lf", + (double) (ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0)); + } else { + ast_copy_string(workspace, "0", sizeof(workspace)); + } + + if (!ast_strlen_zero(workspace)) { + value = workspace; + } + } + + if (!strcasecmp(cdrname, "duration") && + (strstr(entry->type, "float") || + strstr(entry->type, "double") || + strstr(entry->type, "decimal") || + strstr(entry->type, "numeric") || + strstr(entry->type, "real"))) { + + snprintf(workspace, sizeof(workspace), "%lf", + (double) (ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0)); + + if (!ast_strlen_zero(workspace)) { + value = workspace; + } + } + ast_str_make_space(&escape, (valsz = strlen(value)) * 2 + 1); mysql_real_escape_string(&mysql, ast_str_buffer(escape), value, valsz); diff --git a/cdr/cdr_adaptive_odbc.c b/cdr/cdr_adaptive_odbc.c index 3c52658d4f..50577db7ef 100644 --- a/cdr/cdr_adaptive_odbc.c +++ b/cdr/cdr_adaptive_odbc.c @@ -611,6 +611,23 @@ static int odbc_log(struct ast_cdr *cdr) continue; } else { double number = 0.0; + + if (!strcasecmp(entry->cdrname, "billsec")) { + if (!ast_tvzero(cdr->answer)) { + snprintf(colbuf, sizeof(colbuf), "%lf", + (double) (ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0)); + } else { + ast_copy_string(colbuf, "0", sizeof(colbuf)); + } + } else if (!strcasecmp(entry->cdrname, "duration")) { + snprintf(colbuf, sizeof(colbuf), "%lf", + (double) (ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0)); + + if (!ast_strlen_zero(colbuf)) { + colptr = colbuf; + } + } + if (sscanf(colptr, "%30lf", &number) != 1) { ast_log(LOG_WARNING, "CDR variable %s is not an numeric type.\n", entry->name); continue; @@ -628,6 +645,23 @@ static int odbc_log(struct ast_cdr *cdr) continue; } else { double number = 0.0; + + if (!strcasecmp(entry->cdrname, "billsec")) { + if (!ast_tvzero(cdr->answer)) { + snprintf(colbuf, sizeof(colbuf), "%lf", + (double) (ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0)); + } else { + ast_copy_string(colbuf, "0", sizeof(colbuf)); + } + } else if (!strcasecmp(entry->cdrname, "duration")) { + snprintf(colbuf, sizeof(colbuf), "%lf", + (double) (ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0)); + + if (!ast_strlen_zero(colbuf)) { + colptr = colbuf; + } + } + if (sscanf(colptr, "%30lf", &number) != 1) { ast_log(LOG_WARNING, "CDR variable %s is not an numeric type.\n", entry->name); continue; diff --git a/cdr/cdr_odbc.c b/cdr/cdr_odbc.c index e0f830c2a9..fde134fa8e 100644 --- a/cdr/cdr_odbc.c +++ b/cdr/cdr_odbc.c @@ -53,6 +53,7 @@ enum { CONFIG_LOGUNIQUEID = 1 << 0, CONFIG_USEGMTIME = 1 << 1, CONFIG_DISPOSITIONSTRING = 1 << 2, + CONFIG_HRTIME = 1 << 3, }; static struct ast_flags config = { 0 }; @@ -96,8 +97,23 @@ static SQLHSTMT execute_cb(struct odbc_obj *obj, void *data) SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->dstchannel), 0, cdr->dstchannel, 0, NULL); SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->lastapp), 0, cdr->lastapp, 0, NULL); SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->lastdata), 0, cdr->lastdata, 0, NULL); - SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->duration, 0, NULL); - SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->billsec, 0, NULL); + + if (ast_test_flag(&config, CONFIG_HRTIME)) { + double hrbillsec = 0.0; + double hrduration; + + if (!ast_tvzero(cdr->answer)) { + hrbillsec = (double) ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0; + } + hrduration = (double) ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0; + + SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_DOUBLE, SQL_FLOAT, 0, 0, &hrbillsec, 0, NULL); + SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_DOUBLE, SQL_FLOAT, 0, 0, &hrduration, 0, NULL); + } else { + SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->duration, 0, NULL); + SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->billsec, 0, NULL); + } + if (ast_test_flag(&config, CONFIG_DISPOSITIONSTRING)) SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(ast_cdr_disp2str(cdr->disposition)) + 1, 0, ast_cdr_disp2str(cdr->disposition), 0, NULL); else @@ -203,6 +219,14 @@ static int odbc_load_module(int reload) ast_debug(1, "cdr_odbc: Logging in local time\n"); } + if (((tmp = ast_variable_retrieve(cfg, "global", "hrtime"))) && ast_true(tmp)) { + ast_set_flag(&config, CONFIG_HRTIME); + ast_debug(1, "cdr_odbc: Logging billsec and duration fields as floats\n"); + } else { + ast_clear_flag(&config, CONFIG_HRTIME); + ast_debug(1, "cdr_odbc: Logging billsec and duration fields as integers\n"); + } + if ((tmp = ast_variable_retrieve(cfg, "global", "table")) == NULL) { ast_log(LOG_WARNING, "cdr_odbc: table not specified. Assuming cdr\n"); tmp = "cdr"; diff --git a/cdr/cdr_pgsql.c b/cdr/cdr_pgsql.c index eae092642a..53e957c266 100644 --- a/cdr/cdr_pgsql.c +++ b/cdr/cdr_pgsql.c @@ -211,12 +211,12 @@ static int pgsql_log(struct ast_cdr *cdr) } else if (strncmp(cur->type, "float", 5) == 0) { struct timeval *when = cur->name[0] == 'd' ? &cdr->start : &cdr->answer; LENGTHEN_BUF2(31); - ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double)cdr->end.tv_sec - when->tv_sec + cdr->end.tv_usec / 1000000.0 - when->tv_usec / 1000000.0); + ast_str_append(&sql2, 0, "%s%f", first ? "" : ",", (double) (ast_tvdiff_us(cdr->end, *when) / 1000000.0)); } else { /* Char field, probably */ struct timeval *when = cur->name[0] == 'd' ? &cdr->start : &cdr->answer; LENGTHEN_BUF2(31); - ast_str_append(&sql2, 0, "%s'%f'", first ? "" : ",", (double)cdr->end.tv_sec - when->tv_sec + cdr->end.tv_usec / 1000000.0 - when->tv_usec / 1000000.0); + ast_str_append(&sql2, 0, "%s'%f'", first ? "" : ",", (double) (ast_tvdiff_us(cdr->end, *when) / 1000000.0)); } } else if (strcmp(cur->name, "disposition") == 0 || strcmp(cur->name, "amaflags") == 0) { if (strncmp(cur->type, "int", 3) == 0) { diff --git a/cdr/cdr_sqlite.c b/cdr/cdr_sqlite.c index 5a3a4b2ba4..d871e8a8a8 100644 --- a/cdr/cdr_sqlite.c +++ b/cdr/cdr_sqlite.c @@ -49,8 +49,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/utils.h" #include "asterisk/paths.h" -#define LOG_UNIQUEID 0 -#define LOG_USERFIELD 0 +#define LOG_UNIQUEID 0 +#define LOG_USERFIELD 0 +#define LOG_HRTIME 0 /* When you change the DATE_FORMAT, be sure to change the CHAR(19) below to something else */ #define DATE_FORMAT "%Y-%m-%d %T" @@ -74,8 +75,13 @@ static const char sql_create_table[] = "CREATE TABLE cdr (" " start CHAR(19)," " answer CHAR(19)," " end CHAR(19)," +#if LOG_HRTIME +" duration FLOAT," +" billsec FLOAT," +#else " duration INTEGER," " billsec INTEGER," +#endif " disposition INTEGER," " amaflags INTEGER," " accountcode VARCHAR(20)" @@ -101,6 +107,10 @@ static int sqlite_log(struct ast_cdr *cdr) char *zErr = 0; char startstr[80], answerstr[80], endstr[80]; int count; +#if LOG_HRTIME + double hrbillsec = 0.0; + double hrduration; +#endif ast_mutex_lock(&sqlite_lock); @@ -108,6 +118,13 @@ static int sqlite_log(struct ast_cdr *cdr) format_date(answerstr, sizeof(answerstr), &cdr->answer); format_date(endstr, sizeof(endstr), &cdr->end); +#if LOG_HRTIME + if (!ast_tvzero(cdr->answer)) { + hrbillsec = (double) ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0; + } + hrduration = (double) ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0; +#endif + for(count=0; count<5; count++) { res = sqlite_exec_printf(db, "INSERT INTO cdr (" @@ -126,7 +143,11 @@ static int sqlite_log(struct ast_cdr *cdr) "'%q', '%q', '%q', '%q', " "'%q', '%q', '%q', '%q', " "'%q', '%q', '%q', " +#if LOG_HRTIME + "%f, %f, %d, %d, " +#else "%d, %d, %d, %d, " +#endif "'%q'" # if LOG_UNIQUEID ",'%q'" @@ -138,7 +159,11 @@ static int sqlite_log(struct ast_cdr *cdr) cdr->clid, cdr->src, cdr->dst, cdr->dcontext, cdr->channel, cdr->dstchannel, cdr->lastapp, cdr->lastdata, startstr, answerstr, endstr, +#if LOG_HRTIME + hrduration, hrbillsec, cdr->disposition, cdr->amaflags, +#else cdr->duration, cdr->billsec, cdr->disposition, cdr->amaflags, +#endif cdr->accountcode # if LOG_UNIQUEID ,cdr->uniqueid diff --git a/cdr/cdr_tds.c b/cdr/cdr_tds.c index c615400d3c..ab0f0659cd 100644 --- a/cdr/cdr_tds.c +++ b/cdr/cdr_tds.c @@ -87,6 +87,7 @@ struct cdr_tds_config { AST_STRING_FIELD(table); AST_STRING_FIELD(charset); AST_STRING_FIELD(language); + AST_STRING_FIELD(hrtime); ); DBPROCESS *dbproc; unsigned int connected:1; @@ -149,7 +150,36 @@ retry: } if (settings->has_userfield) { - erc = dbfcmd(settings->dbproc, + if (settings->hrtime) { + double hrbillsec = 0.0; + double hrduration; + + if (!ast_tvzero(cdr->answer)) { + hrbillsec = (double)(ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0); + } + hrduration = (double)(ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0); + + erc = dbfcmd(settings->dbproc, + "INSERT INTO %s " + "(" + "accountcode, src, dst, dcontext, clid, channel, " + "dstchannel, lastapp, lastdata, start, answer, [end], duration, " + "billsec, disposition, amaflags, uniqueid, userfield" + ") " + "VALUES " + "(" + "'%s', '%s', '%s', '%s', '%s', '%s', " + "'%s', '%s', '%s', %s, %s, %s, %lf, " + "%lf, '%s', '%s', '%s', '%s'" + ")", + settings->table, + accountcode, src, dst, dcontext, clid, channel, + dstchannel, lastapp, lastdata, start, answer, end, hrduration, + hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid, + userfield + ); + } else { + erc = dbfcmd(settings->dbproc, "INSERT INTO %s " "(" "accountcode, src, dst, dcontext, clid, channel, " @@ -168,8 +198,37 @@ retry: cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid, userfield ); + } } else { - erc = dbfcmd(settings->dbproc, + if (settings->hrtime) { + double hrbillsec = 0.0; + double hrduration; + + if (!ast_tvzero(cdr->answer)) { + hrbillsec = (double)(ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0); + } + hrduration = (double)(ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0); + + erc = dbfcmd(settings->dbproc, + "INSERT INTO %s " + "(" + "accountcode, src, dst, dcontext, clid, channel, " + "dstchannel, lastapp, lastdata, start, answer, [end], duration, " + "billsec, disposition, amaflags, uniqueid" + ") " + "VALUES " + "(" + "'%s', '%s', '%s', '%s', '%s', '%s', " + "'%s', '%s', '%s', %s, %s, %s, %lf, " + "%lf, '%s', '%s', '%s'" + ")", + settings->table, + accountcode, src, dst, dcontext, clid, channel, + dstchannel, lastapp, lastdata, start, answer, end, hrduration, + hrbillsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid + ); + } else { + erc = dbfcmd(settings->dbproc, "INSERT INTO %s " "(" "accountcode, src, dst, dcontext, clid, channel, " @@ -187,6 +246,7 @@ retry: dstchannel, lastapp, lastdata, start, answer, end, cdr->duration, cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_cdr_flags2str(cdr->amaflags), uniqueid ); + } } if (erc == FAIL) { @@ -502,6 +562,13 @@ static int tds_load_module(int reload) ast_string_field_set(settings, table, "cdr"); } + ptr = ast_variable_retrieve(cfg, "global", "hrtime"); + if (ptr && ast_true(ptr)) { + ast_string_field_set(settings, hrtime, ptr); + } else { + ast_log(LOG_NOTICE, "High Resolution Time not found, using integers for billsec and duration fields by default.\n"); + } + mssql_disconnect(); if (mssql_connect()) { diff --git a/configs/cdr_custom.conf.sample b/configs/cdr_custom.conf.sample index ce3248b5dc..bec9e3377f 100644 --- a/configs/cdr_custom.conf.sample +++ b/configs/cdr_custom.conf.sample @@ -8,5 +8,7 @@ ; ;[mappings] ;Master.csv => ${CSV_QUOTE(${CDR(clid)})},${CSV_QUOTE(${CDR(src)})},${CSV_QUOTE(${CDR(dst)})},${CSV_QUOTE(${CDR(dcontext)})},${CSV_QUOTE(${CDR(channel)})},${CSV_QUOTE(${CDR(dstchannel)})},${CSV_QUOTE(${CDR(lastapp)})},${CSV_QUOTE(${CDR(lastdata)})},${CSV_QUOTE(${CDR(start)})},${CSV_QUOTE(${CDR(answer)})},${CSV_QUOTE(${CDR(end)})},${CSV_QUOTE(${CDR(duration)})},${CSV_QUOTE(${CDR(billsec)})},${CSV_QUOTE(${CDR(disposition)})},${CSV_QUOTE(${CDR(amaflags)})},${CSV_QUOTE(${CDR(accountcode)})},${CSV_QUOTE(${CDR(uniqueid)})},${CSV_QUOTE(${CDR(userfield)})},${CDR(sequence)} +; +; High Resolution Time for billsec and duration fields +;Master.csv => ${CSV_QUOTE(${CDR(clid)})},${CSV_QUOTE(${CDR(src)})},${CSV_QUOTE(${CDR(dst)})},${CSV_QUOTE(${CDR(dcontext)})},${CSV_QUOTE(${CDR(channel)})},${CSV_QUOTE(${CDR(dstchannel)})},${CSV_QUOTE(${CDR(lastapp)})},${CSV_QUOTE(${CDR(lastdata)})},${CSV_QUOTE(${CDR(start)})},${CSV_QUOTE(${CDR(answer)})},${CSV_QUOTE(${CDR(end)})},${CSV_QUOTE(${CDR(duration,f)})},${CSV_QUOTE(${CDR(billsec,f)})},${CSV_QUOTE(${CDR(disposition)})},${CSV_QUOTE(${CDR(amaflags)})},${CSV_QUOTE(${CDR(accountcode)})},${CSV_QUOTE(${CDR(uniqueid)})},${CSV_QUOTE(${CDR(userfield)})},${CDR(sequence)} ;Simple.csv => ${CSV_QUOTE(${EPOCH})},${CSV_QUOTE(${CDR(src)})},${CSV_QUOTE(${CDR(dst)})} - diff --git a/configs/cdr_odbc.conf.sample b/configs/cdr_odbc.conf.sample index e9d21d63fe..93bd6fff87 100644 --- a/configs/cdr_odbc.conf.sample +++ b/configs/cdr_odbc.conf.sample @@ -8,3 +8,4 @@ ;dispositionstring=yes ;table=cdr ;"cdr" is default table name ;usegmtime=no ; set to "yes" to log in GMT +;hrtime=yes ;Enables microsecond accuracy with the billsec and duration fields diff --git a/configs/cdr_sqlite3_custom.conf.sample b/configs/cdr_sqlite3_custom.conf.sample index 55872b3830..db1ad79442 100644 --- a/configs/cdr_sqlite3_custom.conf.sample +++ b/configs/cdr_sqlite3_custom.conf.sample @@ -5,3 +5,6 @@ table => cdr columns => calldate, clid, dcontext, channel, dstchannel, lastapp, lastdata, duration, billsec, disposition, amaflags, accountcode, uniqueid, userfield, test values => '${CDR(start)}','${CDR(clid)}','${CDR(dcontext)}','${CDR(channel)}','${CDR(dstchannel)}','${CDR(lastapp)}','${CDR(lastdata)}','${CDR(duration)}','${CDR(billsec)}','${CDR(disposition)}','${CDR(amaflags)}','${CDR(accountcode)}','${CDR(uniqueid)}','${CDR(userfield)}','${CDR(test)}' + +;Enable High Resolution Times for billsec and duration fields +;values => '${CDR(start)}','${CDR(clid)}','${CDR(dcontext)}','${CDR(channel)}','${CDR(dstchannel)}','${CDR(lastapp)}','${CDR(lastdata)}','${CDR(duration,f)}','${CDR(billsec,f)}','${CDR(disposition)}','${CDR(amaflags)}','${CDR(accountcode)}','${CDR(uniqueid)}','${CDR(userfield)}','${CDR(test)}' diff --git a/configs/cdr_syslog.conf.sample b/configs/cdr_syslog.conf.sample index 70c95680ab..3a619be9f8 100644 --- a/configs/cdr_syslog.conf.sample +++ b/configs/cdr_syslog.conf.sample @@ -73,6 +73,8 @@ ;template = "${CDR(clid)}","${CDR(src)}","${CDR(dst)}","${CDR(dcontext)}","${CDR(channel)}","${CDR(dstchannel)}","${CDR(lastapp)}","${CDR(lastdata)}","${CDR(start)}","${CDR(answer)}","${CDR(end)}","${CDR(duration)}","${CDR(billsec)}","${CDR(disposition)}","${CDR(amaflags)}","${CDR(accountcode)}","${CDR(uniqueid)}","${CDR(userfield)}" +; High Resolution Time for billsec and duration fields +;template = "${CDR(clid)}","${CDR(src)}","${CDR(dst)}","${CDR(dcontext)}","${CDR(channel)}","${CDR(dstchannel)}","${CDR(lastapp)}","${CDR(lastdata)}","${CDR(start)}","${CDR(answer)}","${CDR(end)}","${CDR(duration,f)}","${CDR(billsec,f)}","${CDR(disposition)}","${CDR(amaflags)}","${CDR(accountcode)}","${CDR(uniqueid)}","${CDR(userfield)}" ;[cdr-simple] ; Since we don't specify a facility or priority for this logging location, the diff --git a/configs/cdr_tds.conf.sample b/configs/cdr_tds.conf.sample index a2a0adc5a0..309599e5fc 100644 --- a/configs/cdr_tds.conf.sample +++ b/configs/cdr_tds.conf.sample @@ -65,3 +65,14 @@ ; Default value: iso_1 ;charset=BIG5 + +; High Resolution Times +; +; The 'hrtime' setting is used to store high resolution (sub second) times for +; billsec and duration fields. +; +; Accepted value: true or false +; Default value: false + +;hrtime=false + diff --git a/funcs/func_cdr.c b/funcs/func_cdr.c index 211187278b..3d0c846f96 100644 --- a/funcs/func_cdr.c +++ b/funcs/func_cdr.c @@ -106,6 +106,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") </parameter> <parameter name="options" required="false"> <optionlist> + <option name="f"> + <para>Returns billsec or duration fields as floating point values.</para> + </option> <option name="l"> <para>Uses the most recent CDR on a channel with multiple records</para> </option> @@ -174,9 +177,11 @@ enum cdr_option_flags { OPT_UNPARSED = (1 << 1), OPT_LAST = (1 << 2), OPT_SKIPLOCKED = (1 << 3), + OPT_FLOAT = (1 << 4), }; AST_APP_OPTIONS(cdr_func_options, { + AST_APP_OPTION('f', OPT_FLOAT), AST_APP_OPTION('l', OPT_LAST), AST_APP_OPTION('r', OPT_RECURSIVE), AST_APP_OPTION('s', OPT_SKIPLOCKED), @@ -213,9 +218,38 @@ static int cdr_read(struct ast_channel *chan, const char *cmd, char *parse, while (ast_test_flag(cdr, AST_CDR_FLAG_LOCKED) && cdr->next) cdr = cdr->next; - ast_cdr_getvar(cdr, args.variable, &ret, buf, len, - ast_test_flag(&flags, OPT_RECURSIVE), - ast_test_flag(&flags, OPT_UNPARSED)); + if (!strcasecmp("billsec", args.variable) && ast_test_flag(&flags, OPT_FLOAT)) { + if (!ast_tvzero(cdr->answer)) { + double hrtime; + + if(!ast_tvzero(cdr->end)) + hrtime = (double)(ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0); + else + hrtime = (double)(ast_tvdiff_us(ast_tvnow(), cdr->answer) / 1000000.0); + + snprintf(buf, len, "%lf", hrtime); + } else { + snprintf(buf, len, "%lf", 0.0); + } + ret = buf; + } else if (!strcasecmp("duration", args.variable) && ast_test_flag(&flags, OPT_FLOAT)) { + double hrtime; + + if(!ast_tvzero(cdr->end)) + hrtime = (double)(ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0); + else + hrtime = (double)(ast_tvdiff_us(ast_tvnow(), cdr->start) / 1000000.0); + + snprintf(buf, len, "%lf", hrtime); + + if (!ast_strlen_zero(buf)) { + ret = buf; + } + } else { + ast_cdr_getvar(cdr, args.variable, &ret, buf, len, + ast_test_flag(&flags, OPT_RECURSIVE), + ast_test_flag(&flags, OPT_UNPARSED)); + } return ret ? 0 : -1; } -- GitLab