2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2006, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \brief Call Detail Record API
23 * \author Mark Spencer <markster@digium.com>
25 * \note Includes code and algorithms from the Zapata library.
27 * \note We do a lot of checking here in the CDR code to try to be sure we don't ever let a CDR slip
28 * through our fingers somehow. If someone allocates a CDR, it must be completely handled normally
29 * or a WARNING shall be logged, so that we can best keep track of any escape condition where the CDR
30 * isn't properly generated and posted.
42 ASTERISK_FILE_VERSION(__FILE__
, "$Revision$")
44 #include "asterisk/lock.h"
45 #include "asterisk/channel.h"
46 #include "asterisk/cdr.h"
47 #include "asterisk/logger.h"
48 #include "asterisk/callerid.h"
49 #include "asterisk/causes.h"
50 #include "asterisk/options.h"
51 #include "asterisk/linkedlists.h"
52 #include "asterisk/utils.h"
53 #include "asterisk/sched.h"
54 #include "asterisk/config.h"
55 #include "asterisk/cli.h"
56 // XXX #include "asterisk/module.h"
57 #include "asterisk/stringfields.h"
59 /*! Default AMA flag for billing records (CDR's) */
60 int ast_default_amaflags
= AST_CDR_DOCUMENTATION
;
61 char ast_default_accountcode
[AST_MAX_ACCOUNT_CODE
] = "";
63 struct ast_cdr_beitem
{
67 AST_LIST_ENTRY(ast_cdr_beitem
) list
;
70 static AST_LIST_HEAD_STATIC(be_list
, ast_cdr_beitem
);
72 struct ast_cdr_batch_item
{
74 struct ast_cdr_batch_item
*next
;
77 static struct ast_cdr_batch
{
79 struct ast_cdr_batch_item
*head
;
80 struct ast_cdr_batch_item
*tail
;
83 static struct sched_context
*sched
;
84 static int cdr_sched
= -1;
85 static pthread_t cdr_thread
= AST_PTHREADT_NULL
;
87 #define BATCH_SIZE_DEFAULT 100
88 #define BATCH_TIME_DEFAULT 300
89 #define BATCH_SCHEDULER_ONLY_DEFAULT 0
90 #define BATCH_SAFE_SHUTDOWN_DEFAULT 1
96 static int batchscheduleronly
;
97 static int batchsafeshutdown
;
99 AST_MUTEX_DEFINE_STATIC(cdr_batch_lock
);
101 /* these are used to wake up the CDR thread when there's work to do */
102 AST_MUTEX_DEFINE_STATIC(cdr_pending_lock
);
103 static ast_cond_t cdr_pending_cond
;
106 /*! Register a CDR driver. Each registered CDR driver generates a CDR
107 \return 0 on success, -1 on failure
109 int ast_cdr_register(char *name
, char *desc
, ast_cdrbe be
)
111 struct ast_cdr_beitem
*i
;
116 ast_log(LOG_WARNING
, "CDR engine '%s' lacks backend\n", name
);
120 AST_LIST_LOCK(&be_list
);
121 AST_LIST_TRAVERSE(&be_list
, i
, list
) {
122 if (!strcasecmp(name
, i
->name
))
125 AST_LIST_UNLOCK(&be_list
);
128 ast_log(LOG_WARNING
, "Already have a CDR backend called '%s'\n", name
);
132 if (!(i
= ast_calloc(1, sizeof(*i
))))
136 ast_copy_string(i
->name
, name
, sizeof(i
->name
));
137 ast_copy_string(i
->desc
, desc
, sizeof(i
->desc
));
139 AST_LIST_LOCK(&be_list
);
140 AST_LIST_INSERT_HEAD(&be_list
, i
, list
);
141 AST_LIST_UNLOCK(&be_list
);
146 /*! unregister a CDR driver */
147 void ast_cdr_unregister(char *name
)
149 struct ast_cdr_beitem
*i
= NULL
;
151 AST_LIST_LOCK(&be_list
);
152 AST_LIST_TRAVERSE_SAFE_BEGIN(&be_list
, i
, list
) {
153 if (!strcasecmp(name
, i
->name
)) {
154 AST_LIST_REMOVE_CURRENT(&be_list
, list
);
155 if (option_verbose
> 1)
156 ast_verbose(VERBOSE_PREFIX_2
"Unregistered '%s' CDR backend\n", name
);
161 AST_LIST_TRAVERSE_SAFE_END
;
162 AST_LIST_UNLOCK(&be_list
);
165 /*! Duplicate a CDR record
166 \returns Pointer to new CDR record
168 struct ast_cdr
*ast_cdr_dup(struct ast_cdr
*cdr
)
170 struct ast_cdr
*newcdr
= ast_cdr_alloc();
175 memcpy(newcdr
, cdr
, sizeof(*newcdr
));
176 /* The varshead is unusable, volatile even, after the memcpy so we take care of that here */
177 memset(&newcdr
->varshead
, 0, sizeof(newcdr
->varshead
));
178 ast_cdr_copy_vars(newcdr
, cdr
);
184 static const char *ast_cdr_getvar_internal(struct ast_cdr
*cdr
, const char *name
, int recur
)
186 if (ast_strlen_zero(name
))
189 for (; cdr
; cdr
= recur
? cdr
->next
: NULL
) {
190 struct ast_var_t
*variables
;
191 struct varshead
*headp
= &cdr
->varshead
;
192 AST_LIST_TRAVERSE(headp
, variables
, entries
) {
193 if (!strcasecmp(name
, ast_var_name(variables
)))
194 return ast_var_value(variables
);
201 static void cdr_get_tv(struct timeval tv
, const char *fmt
, char *buf
, int bufsize
)
203 if (fmt
== NULL
) { /* raw mode */
204 snprintf(buf
, bufsize
, "%ld.%06ld", (long)tv
.tv_sec
, (long)tv
.tv_usec
);
206 time_t t
= tv
.tv_sec
;
209 localtime_r(&t
, &tm
);
210 strftime(buf
, bufsize
, fmt
, &tm
);
215 /*! CDR channel variable retrieval */
216 void ast_cdr_getvar(struct ast_cdr
*cdr
, const char *name
, char **ret
, char *workspace
, int workspacelen
, int recur
, int raw
)
218 const char *fmt
= "%Y-%m-%d %T";
222 /* special vars (the ones from the struct ast_cdr when requested by name)
223 I'd almost say we should convert all the stringed vals to vars */
225 if (!strcasecmp(name
, "clid"))
226 ast_copy_string(workspace
, cdr
->clid
, workspacelen
);
227 else if (!strcasecmp(name
, "src"))
228 ast_copy_string(workspace
, cdr
->src
, workspacelen
);
229 else if (!strcasecmp(name
, "dst"))
230 ast_copy_string(workspace
, cdr
->dst
, workspacelen
);
231 else if (!strcasecmp(name
, "dcontext"))
232 ast_copy_string(workspace
, cdr
->dcontext
, workspacelen
);
233 else if (!strcasecmp(name
, "channel"))
234 ast_copy_string(workspace
, cdr
->channel
, workspacelen
);
235 else if (!strcasecmp(name
, "dstchannel"))
236 ast_copy_string(workspace
, cdr
->dstchannel
, workspacelen
);
237 else if (!strcasecmp(name
, "lastapp"))
238 ast_copy_string(workspace
, cdr
->lastapp
, workspacelen
);
239 else if (!strcasecmp(name
, "lastdata"))
240 ast_copy_string(workspace
, cdr
->lastdata
, workspacelen
);
241 else if (!strcasecmp(name
, "start"))
242 cdr_get_tv(cdr
->start
, raw
? NULL
: fmt
, workspace
, workspacelen
);
243 else if (!strcasecmp(name
, "answer"))
244 cdr_get_tv(cdr
->answer
, raw
? NULL
: fmt
, workspace
, workspacelen
);
245 else if (!strcasecmp(name
, "end"))
246 cdr_get_tv(cdr
->end
, raw
? NULL
: fmt
, workspace
, workspacelen
);
247 else if (!strcasecmp(name
, "duration"))
248 snprintf(workspace
, workspacelen
, "%ld", cdr
->duration
);
249 else if (!strcasecmp(name
, "billsec"))
250 snprintf(workspace
, workspacelen
, "%ld", cdr
->billsec
);
251 else if (!strcasecmp(name
, "disposition")) {
253 snprintf(workspace
, workspacelen
, "%ld", cdr
->disposition
);
255 ast_copy_string(workspace
, ast_cdr_disp2str(cdr
->disposition
), workspacelen
);
257 } else if (!strcasecmp(name
, "amaflags")) {
259 snprintf(workspace
, workspacelen
, "%ld", cdr
->amaflags
);
261 ast_copy_string(workspace
, ast_cdr_flags2str(cdr
->amaflags
), workspacelen
);
263 } else if (!strcasecmp(name
, "accountcode"))
264 ast_copy_string(workspace
, cdr
->accountcode
, workspacelen
);
265 else if (!strcasecmp(name
, "uniqueid"))
266 ast_copy_string(workspace
, cdr
->uniqueid
, workspacelen
);
267 else if (!strcasecmp(name
, "userfield"))
268 ast_copy_string(workspace
, cdr
->userfield
, workspacelen
);
269 else if ((varbuf
= ast_cdr_getvar_internal(cdr
, name
, recur
)))
270 ast_copy_string(workspace
, varbuf
, workspacelen
);
272 if (!ast_strlen_zero(workspace
))
276 /* readonly cdr variables */
277 static const char *cdr_readonly_vars
[] = { "clid", "src", "dst", "dcontext", "channel", "dstchannel",
278 "lastapp", "lastdata", "start", "answer", "end", "duration",
279 "billsec", "disposition", "amaflags", "accountcode", "uniqueid",
281 /*! Set a CDR channel variable
282 \note You can't set the CDR variables that belong to the actual CDR record, like "billsec".
284 int ast_cdr_setvar(struct ast_cdr
*cdr
, const char *name
, const char *value
, int recur
)
286 struct ast_var_t
*newvariable
;
287 struct varshead
*headp
;
290 for(x
= 0; cdr_readonly_vars
[x
]; x
++) {
291 if (!strcasecmp(name
, cdr_readonly_vars
[x
])) {
292 ast_log(LOG_ERROR
, "Attempt to set a read-only variable!.\n");
298 ast_log(LOG_ERROR
, "Attempt to set a variable on a nonexistent CDR record.\n");
302 for (; cdr
; cdr
= recur
? cdr
->next
: NULL
) {
303 headp
= &cdr
->varshead
;
304 AST_LIST_TRAVERSE_SAFE_BEGIN(headp
, newvariable
, entries
) {
305 if (!strcasecmp(ast_var_name(newvariable
), name
)) {
306 /* there is already such a variable, delete it */
307 AST_LIST_REMOVE_CURRENT(headp
, entries
);
308 ast_var_delete(newvariable
);
312 AST_LIST_TRAVERSE_SAFE_END
;
315 newvariable
= ast_var_assign(name
, value
);
316 AST_LIST_INSERT_HEAD(headp
, newvariable
, entries
);
323 int ast_cdr_copy_vars(struct ast_cdr
*to_cdr
, struct ast_cdr
*from_cdr
)
325 struct ast_var_t
*variables
, *newvariable
= NULL
;
326 struct varshead
*headpa
, *headpb
;
327 const char *var
, *val
;
330 headpa
= &from_cdr
->varshead
;
331 headpb
= &to_cdr
->varshead
;
333 AST_LIST_TRAVERSE(headpa
,variables
,entries
) {
335 (var
= ast_var_name(variables
)) && (val
= ast_var_value(variables
)) &&
336 !ast_strlen_zero(var
) && !ast_strlen_zero(val
)) {
337 newvariable
= ast_var_assign(var
, val
);
338 AST_LIST_INSERT_HEAD(headpb
, newvariable
, entries
);
346 int ast_cdr_serialize_variables(struct ast_cdr
*cdr
, char *buf
, size_t size
, char delim
, char sep
, int recur
)
348 struct ast_var_t
*variables
;
349 const char *var
, *val
;
352 int total
= 0, x
= 0, i
;
354 memset(buf
, 0, size
);
356 for (; cdr
; cdr
= recur
? cdr
->next
: NULL
) {
358 ast_build_string(&buf
, &size
, "\n");
360 AST_LIST_TRAVERSE(&cdr
->varshead
, variables
, entries
) {
362 (var
= ast_var_name(variables
)) && (val
= ast_var_value(variables
)) &&
363 !ast_strlen_zero(var
) && !ast_strlen_zero(val
)) {
364 if (ast_build_string(&buf
, &size
, "level %d: %s%c%s%c", x
, var
, delim
, val
, sep
)) {
365 ast_log(LOG_ERROR
, "Data Buffer Size Exceeded!\n");
373 for (i
= 0; cdr_readonly_vars
[i
]; i
++) {
374 ast_cdr_getvar(cdr
, cdr_readonly_vars
[i
], &tmp
, workspace
, sizeof(workspace
), 0, 0);
378 if (ast_build_string(&buf
, &size
, "level %d: %s%c%s%c", x
, cdr_readonly_vars
[i
], delim
, tmp
, sep
)) {
379 ast_log(LOG_ERROR
, "Data Buffer Size Exceeded!\n");
390 void ast_cdr_free_vars(struct ast_cdr
*cdr
, int recur
)
393 /* clear variables */
394 for (; cdr
; cdr
= recur
? cdr
->next
: NULL
) {
395 struct ast_var_t
*vardata
;
396 struct varshead
*headp
= &cdr
->varshead
;
397 while ((vardata
= AST_LIST_REMOVE_HEAD(headp
, entries
)))
398 ast_var_delete(vardata
);
402 /*! \brief print a warning if cdr already posted */
403 static void check_post(struct ast_cdr
*cdr
)
405 if (ast_test_flag(cdr
, AST_CDR_FLAG_POSTED
))
406 ast_log(LOG_WARNING
, "CDR on channel '%s' already posted\n", S_OR(cdr
->channel
, "<unknown>"));
409 /*! \brief print a warning if cdr already started */
410 static void check_start(struct ast_cdr
*cdr
)
412 if (!ast_tvzero(cdr
->start
))
413 ast_log(LOG_WARNING
, "CDR on channel '%s' already started\n", S_OR(cdr
->channel
, "<unknown>"));
416 void ast_cdr_free(struct ast_cdr
*cdr
)
420 struct ast_cdr
*next
= cdr
->next
;
421 char *chan
= S_OR(cdr
->channel
, "<unknown>");
422 if (!ast_test_flag(cdr
, AST_CDR_FLAG_POSTED
) && !ast_test_flag(cdr
, AST_CDR_FLAG_POST_DISABLED
))
423 ast_log(LOG_WARNING
, "CDR on channel '%s' not posted\n", chan
);
424 if (ast_tvzero(cdr
->end
))
425 ast_log(LOG_WARNING
, "CDR on channel '%s' lacks end\n", chan
);
426 if (ast_tvzero(cdr
->start
))
427 ast_log(LOG_WARNING
, "CDR on channel '%s' lacks start\n", chan
);
429 ast_cdr_free_vars(cdr
, 0);
435 struct ast_cdr
*ast_cdr_alloc(void)
437 return ast_calloc(1, sizeof(struct ast_cdr
));
440 void ast_cdr_start(struct ast_cdr
*cdr
)
444 for (; cdr
; cdr
= cdr
->next
) {
445 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
)) {
446 chan
= S_OR(cdr
->channel
, "<unknown>");
449 cdr
->start
= ast_tvnow();
454 void ast_cdr_answer(struct ast_cdr
*cdr
)
457 for (; cdr
; cdr
= cdr
->next
) {
459 if (cdr
->disposition
< AST_CDR_ANSWERED
)
460 cdr
->disposition
= AST_CDR_ANSWERED
;
461 if (ast_tvzero(cdr
->answer
))
462 cdr
->answer
= ast_tvnow();
466 void ast_cdr_busy(struct ast_cdr
*cdr
)
469 for (; cdr
; cdr
= cdr
->next
) {
470 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
)) {
472 if (cdr
->disposition
< AST_CDR_BUSY
)
473 cdr
->disposition
= AST_CDR_BUSY
;
478 void ast_cdr_failed(struct ast_cdr
*cdr
)
480 for (; cdr
; cdr
= cdr
->next
) {
482 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
)) {
483 if (cdr
->disposition
< AST_CDR_FAILED
)
484 cdr
->disposition
= AST_CDR_FAILED
;
489 int ast_cdr_disposition(struct ast_cdr
*cdr
, int cause
)
493 for (; cdr
; cdr
= cdr
->next
) {
498 case AST_CAUSE_FAILURE
:
501 case AST_CAUSE_NORMAL
:
503 case AST_CAUSE_NOTDEFINED
:
508 ast_log(LOG_WARNING
, "Cause not handled\n");
514 void ast_cdr_setdestchan(struct ast_cdr
*cdr
, const char *chann
)
516 for (; cdr
; cdr
= cdr
->next
) {
518 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
))
519 ast_copy_string(cdr
->dstchannel
, chann
, sizeof(cdr
->dstchannel
));
523 void ast_cdr_setapp(struct ast_cdr
*cdr
, char *app
, char *data
)
526 for (; cdr
; cdr
= cdr
->next
) {
527 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
)) {
531 ast_copy_string(cdr
->lastapp
, app
, sizeof(cdr
->lastapp
));
534 ast_copy_string(cdr
->lastdata
, data
, sizeof(cdr
->lastdata
));
539 /* set cid info for one record */
540 static void set_one_cid(struct ast_cdr
*cdr
, struct ast_channel
*c
)
542 /* Grab source from ANI or normal Caller*ID */
543 const char *num
= S_OR(c
->cid
.cid_ani
, c
->cid
.cid_num
);
545 if (!ast_strlen_zero(c
->cid
.cid_name
)) {
546 if (!ast_strlen_zero(num
)) /* both name and number */
547 snprintf(cdr
->clid
, sizeof(cdr
->clid
), "\"%s\" <%s>", c
->cid
.cid_name
, num
);
549 ast_copy_string(cdr
->clid
, c
->cid
.cid_name
, sizeof(cdr
->clid
));
550 } else if (!ast_strlen_zero(num
)) { /* only number */
551 ast_copy_string(cdr
->clid
, num
, sizeof(cdr
->clid
));
552 } else { /* nothing known */
555 ast_copy_string(cdr
->src
, S_OR(num
, ""), sizeof(cdr
->src
));
558 int ast_cdr_setcid(struct ast_cdr
*cdr
, struct ast_channel
*c
)
560 for (; cdr
; cdr
= cdr
->next
) {
561 if (ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
))
567 int ast_cdr_init(struct ast_cdr
*cdr
, struct ast_channel
*c
)
571 for ( ; cdr
; cdr
= cdr
->next
) {
572 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
)) {
573 chan
= S_OR(cdr
->channel
, "<unknown>");
574 if (!ast_strlen_zero(cdr
->channel
))
575 ast_log(LOG_WARNING
, "CDR already initialized on '%s'\n", chan
);
576 ast_copy_string(cdr
->channel
, c
->name
, sizeof(cdr
->channel
));
579 cdr
->disposition
= (c
->_state
== AST_STATE_UP
) ? AST_CDR_ANSWERED
: AST_CDR_NOANSWER
;
580 cdr
->amaflags
= c
->amaflags
? c
->amaflags
: ast_default_amaflags
;
581 ast_copy_string(cdr
->accountcode
, c
->accountcode
, sizeof(cdr
->accountcode
));
582 /* Destination information */
583 ast_copy_string(cdr
->dst
, c
->exten
, sizeof(cdr
->dst
));
584 ast_copy_string(cdr
->dcontext
, c
->context
, sizeof(cdr
->dcontext
));
585 /* Unique call identifier */
586 ast_copy_string(cdr
->uniqueid
, c
->uniqueid
, sizeof(cdr
->uniqueid
));
592 void ast_cdr_end(struct ast_cdr
*cdr
)
594 for ( ; cdr
; cdr
= cdr
->next
) {
596 if (ast_tvzero(cdr
->end
))
597 cdr
->end
= ast_tvnow();
598 if (ast_tvzero(cdr
->start
)) {
599 ast_log(LOG_WARNING
, "CDR on channel '%s' has not started\n", S_OR(cdr
->channel
, "<unknown>"));
600 cdr
->disposition
= AST_CDR_FAILED
;
602 cdr
->duration
= cdr
->end
.tv_sec
- cdr
->start
.tv_sec
;
603 cdr
->billsec
= ast_tvzero(cdr
->answer
) ? 0 : cdr
->end
.tv_sec
- cdr
->answer
.tv_sec
;
607 char *ast_cdr_disp2str(int disposition
)
609 switch (disposition
) {
610 case AST_CDR_NOANSWER
:
616 case AST_CDR_ANSWERED
:
622 /*! Converts AMA flag to printable string */
623 char *ast_cdr_flags2str(int flag
)
628 case AST_CDR_BILLING
:
630 case AST_CDR_DOCUMENTATION
:
631 return "DOCUMENTATION";
636 int ast_cdr_setaccount(struct ast_channel
*chan
, const char *account
)
638 struct ast_cdr
*cdr
= chan
->cdr
;
640 ast_string_field_set(chan
, accountcode
, account
);
641 for ( ; cdr
; cdr
= cdr
->next
) {
642 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
))
643 ast_copy_string(cdr
->accountcode
, chan
->accountcode
, sizeof(cdr
->accountcode
));
648 int ast_cdr_setamaflags(struct ast_channel
*chan
, const char *flag
)
651 int newflag
= ast_cdr_amaflags2int(flag
);
653 for (cdr
= chan
->cdr
; cdr
; cdr
= cdr
->next
)
654 cdr
->amaflags
= newflag
;
660 int ast_cdr_setuserfield(struct ast_channel
*chan
, const char *userfield
)
662 struct ast_cdr
*cdr
= chan
->cdr
;
664 for ( ; cdr
; cdr
= cdr
->next
) {
665 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
))
666 ast_copy_string(cdr
->userfield
, userfield
, sizeof(cdr
->userfield
));
672 int ast_cdr_appenduserfield(struct ast_channel
*chan
, const char *userfield
)
674 struct ast_cdr
*cdr
= chan
->cdr
;
676 for ( ; cdr
; cdr
= cdr
->next
) {
677 int len
= strlen(cdr
->userfield
);
679 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
))
680 strncpy(cdr
->userfield
+len
, userfield
, sizeof(cdr
->userfield
) - len
- 1);
686 int ast_cdr_update(struct ast_channel
*c
)
688 struct ast_cdr
*cdr
= c
->cdr
;
690 for ( ; cdr
; cdr
= cdr
->next
) {
691 if (!ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
)) {
694 /* Copy account code et-al */
695 ast_copy_string(cdr
->accountcode
, c
->accountcode
, sizeof(cdr
->accountcode
));
696 /* Destination information */ /* XXX privilege macro* ? */
697 ast_copy_string(cdr
->dst
, S_OR(c
->macroexten
, c
->exten
), sizeof(cdr
->dst
));
698 ast_copy_string(cdr
->dcontext
, S_OR(c
->macrocontext
, c
->context
), sizeof(cdr
->dcontext
));
705 int ast_cdr_amaflags2int(const char *flag
)
707 if (!strcasecmp(flag
, "default"))
709 if (!strcasecmp(flag
, "omit"))
711 if (!strcasecmp(flag
, "billing"))
712 return AST_CDR_BILLING
;
713 if (!strcasecmp(flag
, "documentation"))
714 return AST_CDR_DOCUMENTATION
;
718 static void post_cdr(struct ast_cdr
*cdr
)
721 struct ast_cdr_beitem
*i
;
723 for ( ; cdr
; cdr
= cdr
->next
) {
724 chan
= S_OR(cdr
->channel
, "<unknown>");
726 if (ast_tvzero(cdr
->end
))
727 ast_log(LOG_WARNING
, "CDR on channel '%s' lacks end\n", chan
);
728 if (ast_tvzero(cdr
->start
))
729 ast_log(LOG_WARNING
, "CDR on channel '%s' lacks start\n", chan
);
730 ast_set_flag(cdr
, AST_CDR_FLAG_POSTED
);
731 AST_LIST_LOCK(&be_list
);
732 AST_LIST_TRAVERSE(&be_list
, i
, list
) {
735 AST_LIST_UNLOCK(&be_list
);
739 void ast_cdr_reset(struct ast_cdr
*cdr
, struct ast_flags
*_flags
)
742 struct ast_flags flags
= { 0 };
745 ast_copy_flags(&flags
, _flags
, AST_FLAGS_ALL
);
747 for ( ; cdr
; cdr
= cdr
->next
) {
748 /* Detach if post is requested */
749 if (ast_test_flag(&flags
, AST_CDR_FLAG_LOCKED
) || !ast_test_flag(cdr
, AST_CDR_FLAG_LOCKED
)) {
750 if (ast_test_flag(&flags
, AST_CDR_FLAG_POSTED
)) {
752 if ((dup
= ast_cdr_dup(cdr
))) {
755 ast_set_flag(cdr
, AST_CDR_FLAG_POSTED
);
758 /* clear variables */
759 if (!ast_test_flag(&flags
, AST_CDR_FLAG_KEEP_VARS
)) {
760 ast_cdr_free_vars(cdr
, 0);
763 /* Reset to initial state */
764 ast_clear_flag(cdr
, AST_FLAGS_ALL
);
765 memset(&cdr
->start
, 0, sizeof(cdr
->start
));
766 memset(&cdr
->end
, 0, sizeof(cdr
->end
));
767 memset(&cdr
->answer
, 0, sizeof(cdr
->answer
));
771 cdr
->disposition
= AST_CDR_NOANSWER
;
776 struct ast_cdr
*ast_cdr_append(struct ast_cdr
*cdr
, struct ast_cdr
*newcdr
)
793 /*! \note Don't call without cdr_batch_lock */
794 static void reset_batch(void)
801 /*! \note Don't call without cdr_batch_lock */
802 static int init_batch(void)
804 /* This is the single meta-batch used to keep track of all CDRs during the entire life of the program */
805 if (!(batch
= ast_malloc(sizeof(*batch
))))
813 static void *do_batch_backend_process(void *data
)
815 struct ast_cdr_batch_item
*processeditem
;
816 struct ast_cdr_batch_item
*batchitem
= data
;
818 /* Push each CDR into storage mechanism(s) and free all the memory */
820 post_cdr(batchitem
->cdr
);
821 ast_cdr_free(batchitem
->cdr
);
822 processeditem
= batchitem
;
823 batchitem
= batchitem
->next
;
830 void ast_cdr_submit_batch(int shutdown
)
832 struct ast_cdr_batch_item
*oldbatchitems
= NULL
;
834 pthread_t batch_post_thread
= AST_PTHREADT_NULL
;
836 /* if there's no batch, or no CDRs in the batch, then there's nothing to do */
837 if (!batch
|| !batch
->head
)
840 /* move the old CDRs aside, and prepare a new CDR batch */
841 ast_mutex_lock(&cdr_batch_lock
);
842 oldbatchitems
= batch
->head
;
844 ast_mutex_unlock(&cdr_batch_lock
);
846 /* if configured, spawn a new thread to post these CDRs,
847 also try to save as much as possible if we are shutting down safely */
848 if (batchscheduleronly
|| shutdown
) {
850 ast_log(LOG_DEBUG
, "CDR single-threaded batch processing begins now\n");
851 do_batch_backend_process(oldbatchitems
);
853 pthread_attr_init(&attr
);
854 pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
855 if (ast_pthread_create(&batch_post_thread
, &attr
, do_batch_backend_process
, oldbatchitems
)) {
856 ast_log(LOG_WARNING
, "CDR processing thread could not detach, now trying in this thread\n");
857 do_batch_backend_process(oldbatchitems
);
860 ast_log(LOG_DEBUG
, "CDR multi-threaded batch processing begins now\n");
865 static int submit_scheduled_batch(void *data
)
867 ast_cdr_submit_batch(0);
868 /* manually reschedule from this point in time */
869 cdr_sched
= ast_sched_add(sched
, batchtime
* 1000, submit_scheduled_batch
, NULL
);
870 /* returning zero so the scheduler does not automatically reschedule */
874 static void submit_unscheduled_batch(void)
876 /* this is okay since we are not being called from within the scheduler */
878 ast_sched_del(sched
, cdr_sched
);
879 /* schedule the submission to occur ASAP (1 ms) */
880 cdr_sched
= ast_sched_add(sched
, 1, submit_scheduled_batch
, NULL
);
881 /* signal the do_cdr thread to wakeup early and do some work (that lazy thread ;) */
882 ast_mutex_lock(&cdr_pending_lock
);
883 ast_cond_signal(&cdr_pending_cond
);
884 ast_mutex_unlock(&cdr_pending_lock
);
887 void ast_cdr_detach(struct ast_cdr
*cdr
)
889 struct ast_cdr_batch_item
*newtail
;
892 /* maybe they disabled CDR stuff completely, so just drop it */
895 ast_log(LOG_DEBUG
, "Dropping CDR !\n");
896 ast_set_flag(cdr
, AST_CDR_FLAG_POST_DISABLED
);
901 /* post stuff immediately if we are not in batch mode, this is legacy behaviour */
908 /* otherwise, each CDR gets put into a batch list (at the end) */
910 ast_log(LOG_DEBUG
, "CDR detaching from this thread\n");
912 /* we'll need a new tail for every CDR */
913 if (!(newtail
= ast_calloc(1, sizeof(*newtail
)))) {
919 /* don't traverse a whole list (just keep track of the tail) */
920 ast_mutex_lock(&cdr_batch_lock
);
924 /* new batch is empty, so point the head at the new tail */
925 batch
->head
= newtail
;
927 /* already got a batch with something in it, so just append a new tail */
928 batch
->tail
->next
= newtail
;
931 batch
->tail
= newtail
;
932 curr
= batch
->size
++;
933 ast_mutex_unlock(&cdr_batch_lock
);
935 /* if we have enough stuff to post, then do it */
936 if (curr
>= (batchsize
- 1))
937 submit_unscheduled_batch();
940 static void *do_cdr(void *data
)
942 struct timespec timeout
;
947 struct timeval now
= ast_tvnow();
948 schedms
= ast_sched_wait(sched
);
949 /* this shouldn't happen, but provide a 1 second default just in case */
952 timeout
.tv_sec
= now
.tv_sec
+ (schedms
/ 1000);
953 timeout
.tv_nsec
= (now
.tv_usec
* 1000) + ((schedms
% 1000) * 1000);
954 /* prevent stuff from clobbering cdr_pending_cond, then wait on signals sent to it until the timeout expires */
955 ast_mutex_lock(&cdr_pending_lock
);
956 ast_cond_timedwait(&cdr_pending_cond
, &cdr_pending_lock
, &timeout
);
957 numevents
= ast_sched_runq(sched
);
958 ast_mutex_unlock(&cdr_pending_lock
);
959 if (option_debug
> 1)
960 ast_log(LOG_DEBUG
, "Processed %d scheduled CDR batches from the run queue\n", numevents
);
966 static int handle_cli_status(int fd
, int argc
, char *argv
[])
968 struct ast_cdr_beitem
*beitem
=NULL
;
970 long nextbatchtime
=0;
973 return RESULT_SHOWUSAGE
;
975 ast_cli(fd
, "CDR logging: %s\n", enabled
? "enabled" : "disabled");
976 ast_cli(fd
, "CDR mode: %s\n", batchmode
? "batch" : "simple");
982 nextbatchtime
= ast_sched_when(sched
, cdr_sched
);
983 ast_cli(fd
, "CDR safe shut down: %s\n", batchsafeshutdown
? "enabled" : "disabled");
984 ast_cli(fd
, "CDR batch threading model: %s\n", batchscheduleronly
? "scheduler only" : "scheduler plus separate threads");
985 ast_cli(fd
, "CDR current batch size: %d record%s\n", cnt
, (cnt
!= 1) ? "s" : "");
986 ast_cli(fd
, "CDR maximum batch size: %d record%s\n", batchsize
, (batchsize
!= 1) ? "s" : "");
987 ast_cli(fd
, "CDR maximum batch time: %d second%s\n", batchtime
, (batchtime
!= 1) ? "s" : "");
988 ast_cli(fd
, "CDR next scheduled batch processing time: %ld second%s\n", nextbatchtime
, (nextbatchtime
!= 1) ? "s" : "");
990 AST_LIST_LOCK(&be_list
);
991 AST_LIST_TRAVERSE(&be_list
, beitem
, list
) {
992 ast_cli(fd
, "CDR registered backend: %s\n", beitem
->name
);
994 AST_LIST_UNLOCK(&be_list
);
1000 static int handle_cli_submit(int fd
, int argc
, char *argv
[])
1003 return RESULT_SHOWUSAGE
;
1005 submit_unscheduled_batch();
1006 ast_cli(fd
, "Submitted CDRs to backend engines for processing. This may take a while.\n");
1011 static struct ast_cli_entry cli_submit
= {
1012 .cmda
= { "cdr", "submit", NULL
},
1013 .handler
= handle_cli_submit
,
1014 .summary
= "Posts all pending batched CDR data",
1016 "Usage: cdr submit\n"
1017 " Posts all pending batched CDR data to the configured CDR backend engine modules.\n"
1020 static struct ast_cli_entry cli_status
= {
1021 .cmda
= { "cdr", "status", NULL
},
1022 .handler
= handle_cli_status
,
1023 .summary
= "Display the CDR status",
1025 "Usage: cdr status\n"
1026 " Displays the Call Detail Record engine system status.\n"
1029 static int do_reload(void)
1031 struct ast_config
*config
;
1032 const char *enabled_value
;
1033 const char *batched_value
;
1034 const char *scheduleronly_value
;
1035 const char *batchsafeshutdown_value
;
1036 const char *size_value
;
1037 const char *time_value
;
1038 const char *end_before_h_value
;
1045 ast_mutex_lock(&cdr_batch_lock
);
1047 batchsize
= BATCH_SIZE_DEFAULT
;
1048 batchtime
= BATCH_TIME_DEFAULT
;
1049 batchscheduleronly
= BATCH_SCHEDULER_ONLY_DEFAULT
;
1050 batchsafeshutdown
= BATCH_SAFE_SHUTDOWN_DEFAULT
;
1051 was_enabled
= enabled
;
1052 was_batchmode
= batchmode
;
1056 /* don't run the next scheduled CDR posting while reloading */
1058 ast_sched_del(sched
, cdr_sched
);
1060 if ((config
= ast_config_load("cdr.conf"))) {
1061 if ((enabled_value
= ast_variable_retrieve(config
, "general", "enable"))) {
1062 enabled
= ast_true(enabled_value
);
1064 if ((batched_value
= ast_variable_retrieve(config
, "general", "batch"))) {
1065 batchmode
= ast_true(batched_value
);
1067 if ((scheduleronly_value
= ast_variable_retrieve(config
, "general", "scheduleronly"))) {
1068 batchscheduleronly
= ast_true(scheduleronly_value
);
1070 if ((batchsafeshutdown_value
= ast_variable_retrieve(config
, "general", "safeshutdown"))) {
1071 batchsafeshutdown
= ast_true(batchsafeshutdown_value
);
1073 if ((size_value
= ast_variable_retrieve(config
, "general", "size"))) {
1074 if (sscanf(size_value
, "%d", &cfg_size
) < 1)
1075 ast_log(LOG_WARNING
, "Unable to convert '%s' to a numeric value.\n", size_value
);
1076 else if (size_value
< 0)
1077 ast_log(LOG_WARNING
, "Invalid maximum batch size '%d' specified, using default\n", cfg_size
);
1079 batchsize
= cfg_size
;
1081 if ((time_value
= ast_variable_retrieve(config
, "general", "time"))) {
1082 if (sscanf(time_value
, "%d", &cfg_time
) < 1)
1083 ast_log(LOG_WARNING
, "Unable to convert '%s' to a numeric value.\n", time_value
);
1084 else if (time_value
< 0)
1085 ast_log(LOG_WARNING
, "Invalid maximum batch time '%d' specified, using default\n", cfg_time
);
1087 batchtime
= cfg_time
;
1089 if ((end_before_h_value
= ast_variable_retrieve(config
, "general", "endbeforehexten")))
1090 ast_set2_flag(&ast_options
, ast_true(end_before_h_value
), AST_OPT_FLAG_END_CDR_BEFORE_H_EXTEN
);
1093 if (enabled
&& !batchmode
) {
1094 ast_log(LOG_NOTICE
, "CDR simple logging enabled.\n");
1095 } else if (enabled
&& batchmode
) {
1096 cdr_sched
= ast_sched_add(sched
, batchtime
* 1000, submit_scheduled_batch
, NULL
);
1097 ast_log(LOG_NOTICE
, "CDR batch mode logging enabled, first of either size %d or time %d seconds.\n", batchsize
, batchtime
);
1099 ast_log(LOG_NOTICE
, "CDR logging disabled, data will be lost.\n");
1102 /* if this reload enabled the CDR batch mode, create the background thread
1103 if it does not exist */
1104 if (enabled
&& batchmode
&& (!was_enabled
|| !was_batchmode
) && (cdr_thread
== AST_PTHREADT_NULL
)) {
1105 ast_cond_init(&cdr_pending_cond
, NULL
);
1106 if (ast_pthread_create(&cdr_thread
, NULL
, do_cdr
, NULL
) < 0) {
1107 ast_log(LOG_ERROR
, "Unable to start CDR thread.\n");
1108 ast_sched_del(sched
, cdr_sched
);
1110 ast_cli_register(&cli_submit
);
1111 ast_register_atexit(ast_cdr_engine_term
);
1114 /* if this reload disabled the CDR and/or batch mode and there is a background thread,
1116 } else if (((!enabled
&& was_enabled
) || (!batchmode
&& was_batchmode
)) && (cdr_thread
!= AST_PTHREADT_NULL
)) {
1117 /* wake up the thread so it will exit */
1118 pthread_cancel(cdr_thread
);
1119 pthread_kill(cdr_thread
, SIGURG
);
1120 pthread_join(cdr_thread
, NULL
);
1121 cdr_thread
= AST_PTHREADT_NULL
;
1122 ast_cond_destroy(&cdr_pending_cond
);
1123 ast_cli_unregister(&cli_submit
);
1124 ast_unregister_atexit(ast_cdr_engine_term
);
1126 /* if leaving batch mode, then post the CDRs in the batch,
1127 and don't reschedule, since we are stopping CDR logging */
1128 if (!batchmode
&& was_batchmode
) {
1129 ast_cdr_engine_term();
1135 ast_mutex_unlock(&cdr_batch_lock
);
1136 ast_config_destroy(config
);
1141 int ast_cdr_engine_init(void)
1145 sched
= sched_context_create();
1147 ast_log(LOG_ERROR
, "Unable to create schedule context.\n");
1151 ast_cli_register(&cli_status
);
1155 ast_mutex_lock(&cdr_batch_lock
);
1157 ast_mutex_unlock(&cdr_batch_lock
);
1163 /* \note This actually gets called a couple of times at shutdown. Once, before we start
1164 hanging up channels, and then again, after the channel hangup timeout expires */
1165 void ast_cdr_engine_term(void)
1167 ast_cdr_submit_batch(batchsafeshutdown
);
1170 int ast_cdr_engine_reload(void)