Newer
Older
if (!strcasecmp(name, "clid")) {
} else if (!strcasecmp(name, "src")) {
} else if (!strcasecmp(name, "dst")) {
} else if (!strcasecmp(name, "dcontext")) {
ast_copy_string(workspace, cdr->dcontext, workspacelen);
} else if (!strcasecmp(name, "channel")) {
ast_copy_string(workspace, cdr->channel, workspacelen);
} else if (!strcasecmp(name, "dstchannel")) {
ast_copy_string(workspace, cdr->dstchannel, workspacelen);
} else if (!strcasecmp(name, "lastapp")) {
ast_copy_string(workspace, cdr->lastapp, workspacelen);
} else if (!strcasecmp(name, "lastdata")) {
ast_copy_string(workspace, cdr->lastdata, workspacelen);
} else if (!strcasecmp(name, "start")) {
cdr_get_tv(cdr->start, raw ? NULL : fmt, workspace, workspacelen);
} else if (!strcasecmp(name, "answer")) {
cdr_get_tv(cdr->answer, raw ? NULL : fmt, workspace, workspacelen);
} else if (!strcasecmp(name, "end")) {
cdr_get_tv(cdr->end, raw ? NULL : fmt, workspace, workspacelen);
} else if (!strcasecmp(name, "duration")) {
snprintf(workspace, workspacelen, "%ld", cdr->end.tv_sec != 0 ? cdr->duration : (long)ast_tvdiff_ms(ast_tvnow(), cdr->start) / 1000);
} else if (!strcasecmp(name, "billsec")) {
snprintf(workspace, workspacelen, "%ld", (cdr->billsec || !ast_tvzero(cdr->end) || ast_tvzero(cdr->answer)) ? cdr->billsec : (long)ast_tvdiff_ms(ast_tvnow(), cdr->answer) / 1000);
} else if (!strcasecmp(name, "disposition")) {
snprintf(workspace, workspacelen, "%ld", cdr->disposition);
} else {
ast_copy_string(workspace, ast_cdr_disp2str(cdr->disposition), workspacelen);
}
} else if (!strcasecmp(name, "amaflags")) {
if (raw) {
snprintf(workspace, workspacelen, "%ld", cdr->amaflags);
ast_copy_string(workspace, ast_channel_amaflags2string(cdr->amaflags), workspacelen);
} else if (!strcasecmp(name, "accountcode")) {
ast_copy_string(workspace, cdr->accountcode, workspacelen);
} else if (!strcasecmp(name, "peeraccount")) {
ast_copy_string(workspace, cdr->peeraccount, workspacelen);
} else if (!strcasecmp(name, "uniqueid")) {
ast_copy_string(workspace, cdr->uniqueid, workspacelen);
} else if (!strcasecmp(name, "linkedid")) {
ast_copy_string(workspace, cdr->linkedid, workspacelen);
} else if (!strcasecmp(name, "userfield")) {
ast_copy_string(workspace, cdr->userfield, workspacelen);
} else if (!strcasecmp(name, "sequence")) {
Matthew Nicholson
committed
snprintf(workspace, workspacelen, "%d", cdr->sequence);
} else if ((varbuf = cdr_format_var_internal(cdr, name))) {
workspace[0] = '\0';
if (!ast_strlen_zero(workspace)) {
* \brief Callback that finds all CDRs that reference a particular channel by name
static int cdr_object_select_all_by_name_cb(void *obj, void *arg, int flags)
{
struct cdr_object *cdr = obj;
const char *name = arg;
if (!strcasecmp(cdr->party_a.snapshot->name, name) ||
(cdr->party_b.snapshot && !strcasecmp(cdr->party_b.snapshot->name, name))) {
return CMP_MATCH;
}
return 0;
* \internal
* \brief Callback that finds a CDR by channel name
*/
static int cdr_object_get_by_name_cb(void *obj, void *arg, int flags)
{
struct cdr_object *cdr = obj;
const char *name = arg;
if (!strcasecmp(cdr->party_a.snapshot->name, name)) {
return CMP_MATCH;
}
return 0;
}
/* Read Only CDR variables */
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
static const char * const cdr_readonly_vars[] = {
"clid",
"src",
"dst",
"dcontext",
"channel",
"dstchannel",
"lastapp",
"lastdata",
"start",
"answer",
"end",
"duration",
"billsec",
"disposition",
"amaflags",
"accountcode",
"uniqueid",
"linkedid",
"userfield",
"sequence",
NULL
};
int ast_cdr_setvar(const char *channel_name, const char *name, const char *value)
struct cdr_object *cdr;
struct cdr_object *it_cdr;
struct ao2_iterator *it_cdrs;
char *arg = ast_strdupa(channel_name);
Anthony Minessale II
committed
int x;
Joshua Colp
committed
for (x = 0; cdr_readonly_vars[x]; x++) {
if (!strcasecmp(name, cdr_readonly_vars[x])) {
ast_log(LOG_ERROR, "Attempt to set the '%s' read-only variable!\n", name);
Anthony Minessale II
committed
return -1;
}
}
it_cdrs = ao2_callback(active_cdrs_master, OBJ_MULTIPLE, cdr_object_select_all_by_name_cb, arg);
if (!it_cdrs) {
ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
for (; (cdr = ao2_iterator_next(it_cdrs)); ao2_unlock(cdr), ao2_cleanup(cdr)) {
ao2_lock(cdr);
for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
struct varshead *headp = NULL;
if (it_cdr->fn_table == &finalized_state_fn_table && it_cdr->next != NULL) {
if (!strcasecmp(channel_name, it_cdr->party_a.snapshot->name)) {
headp = &it_cdr->party_a.variables;
} else if (it_cdr->party_b.snapshot
&& !strcasecmp(channel_name, it_cdr->party_b.snapshot->name)) {
headp = &it_cdr->party_b.variables;
if (headp) {
set_variable(headp, name, value);
ao2_iterator_destroy(it_cdrs);
return 0;
/*!
* \brief Format a variable on a \ref cdr_object
*/
static void cdr_object_format_var_internal(struct cdr_object *cdr, const char *name, char *value, size_t length)
struct ast_var_t *variable;
AST_LIST_TRAVERSE(&cdr->party_a.variables, variable, entries) {
if (!strcasecmp(name, ast_var_name(variable))) {
ast_copy_string(value, ast_var_value(variable), length);
return;
*value = '\0';
}
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
/*!
* \brief Format one of the standard properties on a \ref cdr_object
*/
static int cdr_object_format_property(struct cdr_object *cdr_obj, const char *name, char *value, size_t length)
{
struct ast_channel_snapshot *party_a = cdr_obj->party_a.snapshot;
struct ast_channel_snapshot *party_b = cdr_obj->party_b.snapshot;
if (!strcasecmp(name, "clid")) {
ast_callerid_merge(value, length, party_a->caller_name, party_a->caller_number, "");
} else if (!strcasecmp(name, "src")) {
ast_copy_string(value, party_a->caller_number, length);
} else if (!strcasecmp(name, "dst")) {
ast_copy_string(value, party_a->exten, length);
} else if (!strcasecmp(name, "dcontext")) {
ast_copy_string(value, party_a->context, length);
} else if (!strcasecmp(name, "channel")) {
ast_copy_string(value, party_a->name, length);
} else if (!strcasecmp(name, "dstchannel")) {
if (party_b) {
ast_copy_string(value, party_b->name, length);
ast_copy_string(value, "", length);
} else if (!strcasecmp(name, "lastapp")) {
ast_copy_string(value, party_a->appl, length);
} else if (!strcasecmp(name, "lastdata")) {
ast_copy_string(value, party_a->data, length);
} else if (!strcasecmp(name, "start")) {
cdr_get_tv(cdr_obj->start, NULL, value, length);
} else if (!strcasecmp(name, "answer")) {
cdr_get_tv(cdr_obj->answer, NULL, value, length);
} else if (!strcasecmp(name, "end")) {
cdr_get_tv(cdr_obj->end, NULL, value, length);
} else if (!strcasecmp(name, "duration")) {
snprintf(value, length, "%ld", cdr_object_get_duration(cdr_obj));
} else if (!strcasecmp(name, "billsec")) {
snprintf(value, length, "%ld", cdr_object_get_billsec(cdr_obj));
} else if (!strcasecmp(name, "disposition")) {
snprintf(value, length, "%u", cdr_obj->disposition);
} else if (!strcasecmp(name, "amaflags")) {
snprintf(value, length, "%d", party_a->amaflags);
} else if (!strcasecmp(name, "accountcode")) {
ast_copy_string(value, party_a->accountcode, length);
} else if (!strcasecmp(name, "peeraccount")) {
if (party_b) {
ast_copy_string(value, party_b->accountcode, length);
ast_copy_string(value, "", length);
} else if (!strcasecmp(name, "uniqueid")) {
ast_copy_string(value, party_a->uniqueid, length);
} else if (!strcasecmp(name, "linkedid")) {
ast_copy_string(value, cdr_obj->linkedid, length);
} else if (!strcasecmp(name, "userfield")) {
ast_copy_string(value, cdr_obj->party_a.userfield, length);
} else if (!strcasecmp(name, "sequence")) {
snprintf(value, length, "%u", cdr_obj->sequence);
} else {
return 1;
/*! \internal
* \brief Look up and retrieve a CDR object by channel name
* \param name The name of the channel
* \retval NULL on error
* \retval The \ref cdr_object for the channel on success, with the reference
* count bumped by one.
*/
static struct cdr_object *cdr_object_get_by_name(const char *name)
{
char *param;
if (ast_strlen_zero(name)) {
return NULL;
}
param = ast_strdupa(name);
return ao2_callback(active_cdrs_master, 0, cdr_object_get_by_name_cb, param);
int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size_t length)
Jonathan Rose
committed
{
struct cdr_object *cdr_obj;
Jonathan Rose
committed
if (ast_strlen_zero(name)) {
Jonathan Rose
committed
}
cdr = cdr_object_get_by_name(channel_name);
if (!cdr) {
ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
Jonathan Rose
committed
}
cdr_obj = cdr->last;
if (cdr_object_format_property(cdr_obj, name, value, length)) {
/* Property failed; attempt variable */
cdr_object_format_var_internal(cdr_obj, name, value, length);
Matthew Jordan
committed
ao2_unlock(cdr);
int ast_cdr_serialize_variables(const char *channel_name, struct ast_str **buf, char delim, char sep)
struct cdr_object *it_cdr;
struct ast_var_t *variable;
const char *var;
int total = 0, x = 0, i;
cdr = cdr_object_get_by_name(channel_name);
if (is_cdr_flag_set(CDR_ENABLED)) {
Joshua Colp
committed
ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
}
ast_str_reset(*buf);
ao2_lock(cdr);
for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
ast_str_append(buf, 0, "\n");
AST_LIST_TRAVERSE(&it_cdr->party_a.variables, variable, entries) {
if (!(var = ast_var_name(variable))) {
continue;
}
if (ast_str_append(buf, 0, "level %d: %s%c%s%c", x, var, delim, S_OR(ast_var_value(variable), ""), sep) < 0) {
ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
break;
}
for (i = 0; cdr_readonly_vars[i]; i++) {
if (cdr_object_format_property(it_cdr, cdr_readonly_vars[i], workspace, sizeof(workspace))) {
/* Unhandled read-only CDR variable. */
ast_assert(0);
continue;
}
if (!ast_strlen_zero(workspace)
&& ast_str_append(buf, 0, "level %d: %s%c%s%c", x, cdr_readonly_vars[i], delim, workspace, sep) < 0) {
ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
break;
}
total++;
}
Matthew Nicholson
committed
}
void ast_cdr_free(struct ast_cdr *cdr)
while (cdr) {
struct ast_cdr *next = cdr->next;
free_variables(&cdr->varshead);
ast_free(cdr);
cdr = next;
struct ast_cdr *ast_cdr_alloc(void)
struct ast_cdr *x;
x = ast_calloc(1, sizeof(*x));
return x;
const char *ast_cdr_disp2str(int disposition)
case AST_CDR_NULL:
return "NO ANSWER"; /* by default, for backward compatibility */
return "FAILED";
return "BUSY";
Jonathan Rose
committed
case AST_CDR_CONGESTION:
return "CONGESTION";
struct party_b_userfield_update {
const char *channel_name;
const char *userfield;
};
/*! \brief Callback used to update the userfield on Party B on all CDRs */
static int cdr_object_update_party_b_userfield_cb(void *obj, void *arg, void *data, int flags)
struct cdr_object *cdr = obj;
if ((cdr->fn_table != &finalized_state_fn_table || !cdr->next)
&& !strcasecmp(cdr->party_b_name, arg)) {
struct party_b_userfield_update *info = data;
/*
* For sanity's sake we also assert the party_b snapshot
* is consistent with the key.
*/
ast_assert(cdr->party_b.snapshot
&& !strcasecmp(cdr->party_b.snapshot->name, info->channel_name));
ast_copy_string(cdr->party_b.userfield, info->userfield,
sizeof(cdr->party_b.userfield));
return 0;
}
void ast_cdr_setuserfield(const char *channel_name, const char *userfield)
struct party_b_userfield_update party_b_info = {
.channel_name = channel_name,
.userfield = userfield,
};
struct cdr_object *it_cdr;
/* Handle Party A */
cdr = cdr_object_get_by_name(channel_name);
if (cdr) {
ao2_lock(cdr);
for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
if (it_cdr->fn_table == &finalized_state_fn_table && it_cdr->next != NULL) {
continue;
}
ast_copy_string(it_cdr->party_a.userfield, userfield,
sizeof(it_cdr->party_a.userfield));
ao2_unlock(cdr);
/* Handle Party B */
ao2_callback_data(active_cdrs_all, OBJ_NODATA | OBJ_MULTIPLE | OBJ_SEARCH_KEY,
cdr_object_update_party_b_userfield_cb, (char *) party_b_info.channel_name,
&party_b_info);
static void post_cdr(struct ast_cdr *cdr)
struct module_config *mod_cfg;
struct cdr_beitem *i;
mod_cfg = ao2_global_obj_ref(module_configs);
if (!mod_cfg) {
return;
}
for (; cdr ; cdr = cdr->next) {
/* For people, who don't want to see unanswered single-channel events */
if (!ast_test_flag(&mod_cfg->general->settings, CDR_UNANSWERED) &&
cdr->disposition < AST_CDR_ANSWERED &&
(ast_strlen_zero(cdr->channel) || ast_strlen_zero(cdr->dstchannel))) {
ast_debug(1, "Skipping CDR for %s since we weren't answered\n", cdr->channel);
if (ast_test_flag(cdr, AST_CDR_FLAG_DISABLE)) {
continue;
}
AST_RWLIST_RDLOCK(&be_list);
AST_RWLIST_TRAVERSE(&be_list, i, list) {
if (!i->suspended) {
i->be(cdr);
}
}
AST_RWLIST_UNLOCK(&be_list);
}
ao2_cleanup(mod_cfg);
int ast_cdr_set_property(const char *channel_name, enum ast_cdr_options option)
struct cdr_object *it_cdr;
cdr = cdr_object_get_by_name(channel_name);
if (!cdr) {
return -1;
}
ao2_lock(cdr);
for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
if (it_cdr->fn_table == &finalized_state_fn_table) {
continue;
}
/* Note: in general, set the flags on both the CDR record as well as the
* Party A. Sometimes all we have is the Party A to look at.
*/
ast_set_flag(&it_cdr->flags, option);
ast_set_flag(&it_cdr->party_a, option);
ao2_unlock(cdr);
int ast_cdr_clear_property(const char *channel_name, enum ast_cdr_options option)
struct cdr_object *it_cdr;
cdr = cdr_object_get_by_name(channel_name);
if (!cdr) {
return -1;
}
ao2_lock(cdr);
for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
if (it_cdr->fn_table == &finalized_state_fn_table) {
continue;
}
ast_clear_flag(&it_cdr->flags, option);
ao2_unlock(cdr);
Matthew Jordan
committed
int ast_cdr_reset(const char *channel_name, int keep_variables)
struct ast_var_t *vardata;
struct cdr_object *it_cdr;
cdr = cdr_object_get_by_name(channel_name);
if (!cdr) {
return -1;
}
ao2_lock(cdr);
for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
/* clear variables */
Matthew Jordan
committed
if (!keep_variables) {
while ((vardata = AST_LIST_REMOVE_HEAD(&it_cdr->party_a.variables, entries))) {
ast_var_delete(vardata);
}
if (cdr->party_b.snapshot) {
while ((vardata = AST_LIST_REMOVE_HEAD(&it_cdr->party_b.variables, entries))) {
ast_var_delete(vardata);
}
}
Martin Pycko
committed
}
/* Reset to initial state */
memset(&it_cdr->start, 0, sizeof(it_cdr->start));
memset(&it_cdr->end, 0, sizeof(it_cdr->end));
memset(&it_cdr->answer, 0, sizeof(it_cdr->answer));
it_cdr->start = ast_tvnow();
cdr_object_check_party_a_answer(it_cdr);
ao2_unlock(cdr);
int ast_cdr_fork(const char *channel_name, struct ast_flags *options)
RAII_VAR(struct cdr_object *, cdr, cdr_object_get_by_name(channel_name), ao2_cleanup);
struct cdr_object *new_cdr;
struct cdr_object *it_cdr;
struct cdr_object *cdr_obj;
if (!cdr) {
return -1;
}
{
SCOPED_AO2LOCK(lock, cdr);
Richard Mudgett
committed
cdr_obj = cdr->last;
if (cdr_obj->fn_table == &finalized_state_fn_table) {
/* If the last CDR in the chain is finalized, don't allow a fork -
* things are already dying at this point
*/
return -1;
/* Copy over the basic CDR information. The Party A information is
* copied over automatically as part of the append
*/
ast_debug(1, "Forking CDR for channel %s\n", cdr->party_a.snapshot->name);
new_cdr = cdr_object_create_and_append(cdr);
if (!new_cdr) {
return -1;
new_cdr->fn_table = cdr_obj->fn_table;
ast_string_field_set(new_cdr, bridge, cdr->bridge);
ast_string_field_set(new_cdr, appl, cdr->appl);
ast_string_field_set(new_cdr, data, cdr->data);
ast_string_field_set(new_cdr, context, cdr->context);
ast_string_field_set(new_cdr, exten, cdr->exten);
new_cdr->flags = cdr->flags;
Matthew Jordan
committed
/* Explicitly clear the AST_CDR_LOCK_APP flag - we want
* the application to be changed on the new CDR if the
* dialplan demands it
*/
ast_clear_flag(&new_cdr->flags, AST_CDR_LOCK_APP);
/* If there's a Party B, copy it over as well */
if (cdr_obj->party_b.snapshot) {
new_cdr->party_b.snapshot = cdr_obj->party_b.snapshot;
ao2_ref(new_cdr->party_b.snapshot, +1);
cdr_all_relink(new_cdr);
strcpy(new_cdr->party_b.userfield, cdr_obj->party_b.userfield);
new_cdr->party_b.flags = cdr_obj->party_b.flags;
if (ast_test_flag(options, AST_CDR_FLAG_KEEP_VARS)) {
copy_variables(&new_cdr->party_b.variables, &cdr_obj->party_b.variables);
}
new_cdr->start = cdr_obj->start;
new_cdr->answer = cdr_obj->answer;
/* Modify the times based on the flags passed in */
if (ast_test_flag(options, AST_CDR_FLAG_SET_ANSWER)
&& new_cdr->party_a.snapshot->state == AST_STATE_UP) {
new_cdr->answer = ast_tvnow();
}
if (ast_test_flag(options, AST_CDR_FLAG_RESET)) {
new_cdr->answer = ast_tvnow();
new_cdr->start = ast_tvnow();
}
/* Create and append, by default, copies over the variables */
if (!ast_test_flag(options, AST_CDR_FLAG_KEEP_VARS)) {
free_variables(&new_cdr->party_a.variables);
}
/* Finalize any current CDRs */
if (ast_test_flag(options, AST_CDR_FLAG_FINALIZE)) {
for (it_cdr = cdr; it_cdr != new_cdr; it_cdr = it_cdr->next) {
if (it_cdr->fn_table == &finalized_state_fn_table) {
continue;
/* Force finalization on the CDR. This will bypass any checks for
* end before 'h' extension.
*/
cdr_object_finalize(it_cdr);
cdr_object_transition_state(it_cdr, &finalized_state_fn_table);
Martin Pycko
committed
}
/*! \note Don't call without cdr_batch_lock */
static void reset_batch(void)
{
batch->size = 0;
batch->head = NULL;
batch->tail = NULL;
}
/*! \note Don't call without cdr_batch_lock */
static int init_batch(void)
{
/* This is the single meta-batch used to keep track of all CDRs during the entire life of the program */
if (!(batch = ast_malloc(sizeof(*batch))))
return -1;
reset_batch();
return 0;
}
static void *do_batch_backend_process(void *data)
{
struct cdr_batch_item *processeditem;
struct cdr_batch_item *batchitem = data;
/* Push each CDR into storage mechanism(s) and free all the memory */
while (batchitem) {
post_cdr(batchitem->cdr);
ast_cdr_free(batchitem->cdr);
processeditem = batchitem;
batchitem = batchitem->next;
Tilghman Lesher
committed
ast_free(processeditem);
}
return NULL;
}
static void cdr_submit_batch(int do_shutdown)
{
struct module_config *mod_cfg;
struct cdr_batch_item *oldbatchitems = NULL;
pthread_t batch_post_thread = AST_PTHREADT_NULL;
/* if there's no batch, or no CDRs in the batch, then there's nothing to do */
if (!batch || !batch->head) {
return;
/* move the old CDRs aside, and prepare a new CDR batch */
ast_mutex_lock(&cdr_batch_lock);
oldbatchitems = batch->head;
reset_batch();
ast_mutex_unlock(&cdr_batch_lock);
mod_cfg = ao2_global_obj_ref(module_configs);
/* if configured, spawn a new thread to post these CDRs,
also try to save as much as possible if we are shutting down safely */
if (!mod_cfg
|| ast_test_flag(&mod_cfg->general->batch_settings.settings, BATCH_MODE_SCHEDULER_ONLY)
|| do_shutdown) {
ast_debug(1, "CDR single-threaded batch processing begins now\n");
do_batch_backend_process(oldbatchitems);
} else {
Russell Bryant
committed
if (ast_pthread_create_detached_background(&batch_post_thread, NULL, do_batch_backend_process, oldbatchitems)) {
ast_log(LOG_WARNING, "CDR processing thread could not detach, now trying in this thread\n");
do_batch_backend_process(oldbatchitems);
} else {
ast_debug(1, "CDR multi-threaded batch processing begins now\n");
}
}
ao2_cleanup(mod_cfg);
}
static int submit_scheduled_batch(const void *data)
{
struct module_config *mod_cfg;
cdr_submit_batch(0);
mod_cfg = ao2_global_obj_ref(module_configs);
if (!mod_cfg) {
return 0;
}
/* manually reschedule from this point in time */
ast_mutex_lock(&cdr_sched_lock);
cdr_sched = ast_sched_add(sched, mod_cfg->general->batch_settings.time * 1000, submit_scheduled_batch, NULL);
ast_mutex_unlock(&cdr_sched_lock);
ao2_cleanup(mod_cfg);
/* returning zero so the scheduler does not automatically reschedule */
return 0;
}
/*! Do not hold the batch lock while calling this function */
static void submit_unscheduled_batch(void)
{
/* Prevent two deletes from happening at the same time */
ast_mutex_lock(&cdr_sched_lock);
/* this is okay since we are not being called from within the scheduler */
AST_SCHED_DEL(sched, cdr_sched);
/* schedule the submission to occur ASAP (1 ms) */
cdr_sched = ast_sched_add(sched, 1, submit_scheduled_batch, NULL);
ast_mutex_unlock(&cdr_sched_lock);
/* signal the do_cdr thread to wakeup early and do some work (that lazy thread ;) */
Kevin P. Fleming
committed
ast_cond_signal(&cdr_pending_cond);
}
static void cdr_detach(struct ast_cdr *cdr)
{
struct cdr_batch_item *newtail;
int curr;
RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
int submit_batch = 0;
/* maybe they disabled CDR stuff completely, so just drop it */
if (!mod_cfg || !ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
ast_debug(1, "Dropping CDR !\n");
ast_cdr_free(cdr);
return;
}
/* post stuff immediately if we are not in batch mode, this is legacy behaviour */
if (!ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
post_cdr(cdr);
ast_cdr_free(cdr);
return;
}
/* otherwise, each CDR gets put into a batch list (at the end) */
ast_debug(1, "CDR detaching from this thread\n");
/* we'll need a new tail for every CDR */
if (!(newtail = ast_calloc(1, sizeof(*newtail)))) {
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
post_cdr(cdr);
ast_cdr_free(cdr);
return;
}
/* don't traverse a whole list (just keep track of the tail) */
ast_mutex_lock(&cdr_batch_lock);
if (!batch)
init_batch();
if (!batch->head) {
/* new batch is empty, so point the head at the new tail */
batch->head = newtail;
} else {
/* already got a batch with something in it, so just append a new tail */
batch->tail->next = newtail;
}
newtail->cdr = cdr;
batch->tail = newtail;
curr = batch->size++;
/* if we have enough stuff to post, then do it */
if (curr >= (mod_cfg->general->batch_settings.size - 1)) {
submit_batch = 1;
Matthew Jordan
committed
}
ast_mutex_unlock(&cdr_batch_lock);
/* Don't call submit_unscheduled_batch with the cdr_batch_lock held */
if (submit_batch) {
submit_unscheduled_batch();
}
}
static void *do_cdr(void *data)
{
struct timespec timeout;
int schedms;
int numevents = 0;
Joshua Colp
committed
for (;;) {
schedms = ast_sched_wait(sched);
/* this shouldn't happen, but provide a 1 second default just in case */
if (schedms <= 0)
schedms = 1000;
now = ast_tvadd(ast_tvnow(), ast_samp2tv(schedms, 1000));
timeout.tv_sec = now.tv_sec;
timeout.tv_nsec = now.tv_usec * 1000;
/* prevent stuff from clobbering cdr_pending_cond, then wait on signals sent to it until the timeout expires */
Kevin P. Fleming
committed
ast_cond_timedwait(&cdr_pending_cond, &cdr_pending_lock, &timeout);
numevents = ast_sched_runq(sched);
ast_debug(2, "Processed %d scheduled CDR batches from the run queue\n", numevents);
}
return NULL;
}
static char *handle_cli_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
struct module_config *mod_cfg;
switch (cmd) {
case CLI_INIT:
e->command = "cdr set debug [on|off]";
e->usage = "Enable or disable extra debugging in the CDR Engine. Note\n"
"that this will dump debug information to the VERBOSE setting\n"
"and should only be used when debugging information from the\n"
"CDR engine is needed.\n";
return NULL;
case CLI_GENERATE:
return NULL;
}
if (a->argc != 4) {
return CLI_SHOWUSAGE;
}
mod_cfg = ao2_global_obj_ref(module_configs);
if (!mod_cfg) {
ast_cli(a->fd, "Could not set CDR debugging mode\n");
return CLI_SUCCESS;
}
if (!strcasecmp(a->argv[3], "on")
&& !ast_test_flag(&mod_cfg->general->settings, CDR_DEBUG)) {
ast_set_flag(&mod_cfg->general->settings, CDR_DEBUG);
ast_cli(a->fd, "CDR debugging enabled\n");
} else if (!strcasecmp(a->argv[3], "off")
&& ast_test_flag(&mod_cfg->general->settings, CDR_DEBUG)) {
ast_clear_flag(&mod_cfg->general->settings, CDR_DEBUG);
ast_cli(a->fd, "CDR debugging disabled\n");
}
cdr_set_debug_mode(mod_cfg);
ao2_cleanup(mod_cfg);
return CLI_SUCCESS;
}
/*! \brief Complete user input for 'cdr show' */
static char *cli_complete_show(struct ast_cli_args *a)
{
char *result = NULL;
int wordlen = strlen(a->word);
int which = 0;
struct ao2_iterator it_cdrs;
struct cdr_object *cdr;
it_cdrs = ao2_iterator_init(active_cdrs_master, 0);
while ((cdr = ao2_iterator_next(&it_cdrs))) {
if (!strncasecmp(a->word, cdr->party_a.snapshot->name, wordlen) &&
(++which > a->n)) {
result = ast_strdup(cdr->party_a.snapshot->name);
if (result) {
ao2_ref(cdr, -1);
break;
}
}
ao2_ref(cdr, -1);
}
ao2_iterator_destroy(&it_cdrs);
return result;
}
static void cli_show_channels(struct ast_cli_args *a)
{
struct ao2_iterator it_cdrs;
struct cdr_object *cdr;
char start_time_buffer[64];
char answer_time_buffer[64];
char end_time_buffer[64];
#define TITLE_STRING "%-25.25s %-25.25s %-15.15s %-8.8s %-8.8s %-8.8s %-8.8s %-8.8s\n"
#define FORMAT_STRING "%-25.25s %-25.25s %-15.15s %-8.8s %-8.8s %-8.8s %-8.8ld %-8.8ld\n"
ast_cli(a->fd, "\n");
ast_cli(a->fd, "Channels with Call Detail Record (CDR) Information\n");
ast_cli(a->fd, "--------------------------------------------------\n");
ast_cli(a->fd, TITLE_STRING, "Channel", "Dst. Channel", "LastApp", "Start", "Answer", "End", "Billsec", "Duration");
it_cdrs = ao2_iterator_init(active_cdrs_master, 0);
for (; (cdr = ao2_iterator_next(&it_cdrs)); ao2_cleanup(cdr)) {
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
struct cdr_object *it_cdr;
struct timeval start_time = { 0, };
struct timeval answer_time = { 0, };
struct timeval end_time = { 0, };
SCOPED_AO2LOCK(lock, cdr);
/* Calculate the start, end, answer, billsec, and duration over the
* life of all of the CDR entries
*/
for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
if (snapshot_is_dialed(it_cdr->party_a.snapshot)) {
continue;
}
if (ast_tvzero(start_time)) {
start_time = it_cdr->start;
}
if (!ast_tvzero(it_cdr->answer) && ast_tvzero(answer_time)) {
answer_time = it_cdr->answer;
}
}
Matthew Jordan
committed
/* If there was no start time, then all CDRs were for a dialed channel; skip */
if (ast_tvzero(start_time)) {
continue;
}
it_cdr = cdr->last;
Matthew Jordan
committed
end_time = ast_tvzero(it_cdr->end) ? ast_tvnow() : it_cdr->end;
cdr_get_tv(start_time, "%T", start_time_buffer, sizeof(start_time_buffer));
cdr_get_tv(answer_time, "%T", answer_time_buffer, sizeof(answer_time_buffer));
cdr_get_tv(end_time, "%T", end_time_buffer, sizeof(end_time_buffer));
ast_cli(a->fd, FORMAT_STRING, it_cdr->party_a.snapshot->name,
it_cdr->party_b.snapshot ? it_cdr->party_b.snapshot->name : "<none>",
it_cdr->appl,
start_time_buffer,
answer_time_buffer,
end_time_buffer,
Matthew Jordan
committed
ast_tvzero(answer_time) ? 0 : (long)ast_tvdiff_ms(end_time, answer_time) / 1000,
(long)ast_tvdiff_ms(end_time, start_time) / 1000);
}
ao2_iterator_destroy(&it_cdrs);
#undef FORMAT_STRING
#undef TITLE_STRING
}
static void cli_show_channel(struct ast_cli_args *a)
{
struct cdr_object *it_cdr;
char clid[64];
char start_time_buffer[64];
char answer_time_buffer[64];
char end_time_buffer[64];
const char *channel_name = a->argv[3];
#define TITLE_STRING "%-10.10s %-20.20s %-25.25s %-15.15s %-15.15s %-8.8s %-8.8s %-8.8s %-8.8s %-8.8s\n"
#define FORMAT_STRING "%-10.10s %-20.20s %-25.25s %-15.15s %-15.15s %-8.8s %-8.8s %-8.8s %-8.8ld %-8.8ld\n"