Fix some bugs in CDRs; add some CLI commands to help debugging

This patch fixes a few minor bugs and one major one: the CDR by bridge
container was less than helpful. The mechanism previously used to try
and find all of the CDRs in a particular bridge ended up missing CDRs,
resulting in incorrect records.

When looking up CDRs in a bridge, we now just bite the bullet and do
a selection across all existing CDRs.

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@393599 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
Matthew Jordan 2013-07-03 22:04:08 +00:00
parent 569f5f2117
commit a981de9adb
1 changed files with 230 additions and 45 deletions

View File

@ -318,9 +318,6 @@ static ast_cond_t cdr_pending_cond;
/*! \brief A container of the active CDRs indexed by Party A channel name */
static struct ao2_container *active_cdrs_by_channel;
/*! \brief A container of the active CDRs indexed by the bridge ID */
static struct ao2_container *active_cdrs_by_bridge;
/*! \brief Message router for stasis messages regarding channel state */
static struct stasis_message_router *stasis_router;
@ -783,16 +780,6 @@ static int cdr_object_channel_cmp_fn(void *obj, void *arg, int flags)
return strcasecmp(left->name, match) ? 0 : (CMP_MATCH | CMP_STOP);
}
/*! \internal
* \brief Hash function for containers of CDRs indexing by bridge ID
*/
static int cdr_object_bridge_hash_fn(const void *obj, const int flags)
{
const struct cdr_object *cdr = obj;
const char *id = (flags & OBJ_KEY) ? obj : cdr->bridge;
return ast_str_case_hash(id);
}
/*! \internal
* \brief Comparison function for containers of CDRs indexing by bridge. Note
* that we expect there to be collisions, as a single bridge may have multiple
@ -801,9 +788,9 @@ static int cdr_object_bridge_hash_fn(const void *obj, const int flags)
static int cdr_object_bridge_cmp_fn(void *obj, void *arg, int flags)
{
struct cdr_object *left = obj;
struct cdr_object *right = arg;
struct cdr_object *it_cdr;
const char *match = (flags & OBJ_KEY) ? arg : right->bridge;
const char *match = arg;
for (it_cdr = left; it_cdr; it_cdr = it_cdr->next) {
if (!strcasecmp(it_cdr->bridge, match)) {
return CMP_MATCH;
@ -1405,6 +1392,11 @@ static int single_state_bridge_enter_comparison(struct cdr_object *cdr,
{
struct cdr_object_snapshot *party_a;
/* Don't match on ourselves */
if (!strcmp(cdr->party_a.snapshot->name, cand_cdr->party_a.snapshot->name)) {
return 1;
}
/* Try the candidate CDR's Party A first */
party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_a);
if (!strcmp(party_a->snapshot->name, cdr->party_a.snapshot->name)) {
@ -1419,8 +1411,8 @@ static int single_state_bridge_enter_comparison(struct cdr_object *cdr,
return 0;
}
/* Try their Party B */
if (!cand_cdr->party_b.snapshot) {
/* Try their Party B, unless it's us */
if (!cand_cdr->party_b.snapshot || !strcmp(cdr->party_a.snapshot->name, cand_cdr->party_b.snapshot->name)) {
return 1;
}
party_a = cdr_object_pick_party_a(&cdr->party_a, &cand_cdr->party_b);
@ -1442,7 +1434,7 @@ static int single_state_process_bridge_enter(struct cdr_object *cdr, struct ast_
ast_string_field_set(cdr, bridge, bridge->uniqueid);
/* Get parties in the bridge */
it_cdrs = ao2_callback(active_cdrs_by_bridge, OBJ_MULTIPLE | OBJ_KEY,
it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE,
cdr_object_bridge_cmp_fn, bridge_id);
if (!it_cdrs) {
/* No one in the bridge yet! */
@ -1581,7 +1573,7 @@ static int dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_br
ast_string_field_set(cdr, bridge, bridge->uniqueid);
/* Get parties in the bridge */
it_cdrs = ao2_callback(active_cdrs_by_bridge, OBJ_MULTIPLE | OBJ_KEY,
it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE,
cdr_object_bridge_cmp_fn, bridge_id);
if (!it_cdrs) {
/* No one in the bridge yet! */
@ -1610,7 +1602,6 @@ static int dial_state_process_bridge_enter(struct cdr_object *cdr, struct ast_br
if (strcmp(cdr->party_b.snapshot->name, cand_cdr->party_a.snapshot->name)) {
continue;
}
cdr_object_snapshot_copy(&cdr->party_b, &cand_cdr->party_a);
/* If they have a Party B, they joined up with someone else as their
* Party A. Don't finalize them as they're active. Otherwise, we
@ -2192,10 +2183,6 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription *
return;
}
if (strcmp(bridge->subclass, "parking")) {
ao2_unlink(active_cdrs_by_bridge, cdr);
}
/* Create a new pending record. If the channel decides to do something else,
* the pending record will handle it - otherwise, if gets dropped.
*/
@ -2207,7 +2194,7 @@ static void handle_bridge_leave_message(void *data, struct stasis_subscription *
if (strcmp(bridge->subclass, "parking")) {
/* Party B */
ao2_callback(active_cdrs_by_bridge, OBJ_NODATA,
ao2_callback(active_cdrs_by_channel, OBJ_NODATA,
cdr_object_party_b_left_bridge_cb,
&leave_data);
}
@ -2338,7 +2325,7 @@ static struct ao2_container *create_candidates_for_bridge(struct ast_bridge_snap
/* For each CDR that has a record in the bridge, get their Party A and
* make them a candidate. Note that we do this in two passes as opposed to one so
* that we give preference CDRs where the channel is Party A */
it_cdrs = ao2_callback(active_cdrs_by_bridge, OBJ_MULTIPLE | OBJ_KEY,
it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE,
cdr_object_bridge_cmp_fn, bridge_id);
if (!it_cdrs) {
/* No one in the bridge yet! */
@ -2350,10 +2337,9 @@ static struct ao2_container *create_candidates_for_bridge(struct ast_bridge_snap
add_candidate_for_bridge(bridge->uniqueid, candidates, cand_cdr_master, 1);
}
ao2_iterator_destroy(it_cdrs);
/* For each CDR that has a record in the bridge, get their Party B and
* make them a candidate. */
it_cdrs = ao2_callback(active_cdrs_by_bridge, OBJ_MULTIPLE | OBJ_KEY,
it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE,
cdr_object_bridge_cmp_fn, bridge_id);
if (!it_cdrs) {
/* Now it's just an error. */
@ -2412,7 +2398,6 @@ static int bridge_candidate_process(void *obj, void *arg, int flags)
|| (cdr->party_b.snapshot && !(strcmp(cdr->party_b.snapshot->name, bcand->candidate.snapshot->name)))) {
return 0;
}
party_a = cdr_object_pick_party_a(&cdr->party_a, &bcand->candidate);
/* We're party A - make a new CDR, append it to us, and set the candidate as
* Party B */
@ -2454,7 +2439,6 @@ static int bridge_candidate_process(void *obj, void *arg, int flags)
} else {
bridge_candidate_add_to_cdr(b_party, cdr->bridge, &cdr->party_a);
}
ao2_link(active_cdrs_by_bridge, b_party);
ao2_ref(b_party, -1);
}
@ -2476,7 +2460,6 @@ static void handle_bridge_pairings(struct cdr_object *cdr, struct ast_bridge_sna
if (!candidates) {
return;
}
ao2_callback(candidates, OBJ_NODATA,
bridge_candidate_process,
cdr);
@ -2577,8 +2560,6 @@ static void handle_standard_bridge_enter_message(struct cdr_object *cdr,
handled_cdr = cdr->last;
}
handle_bridge_pairings(handled_cdr, bridge);
ao2_link(active_cdrs_by_bridge, cdr);
ao2_unlock(cdr);
}
@ -2879,9 +2860,7 @@ static int cdr_object_select_all_by_channel_cb(void *obj, void *arg, int flags)
{
struct cdr_object *cdr = obj;
const char *name = arg;
if (!(flags & OBJ_KEY)) {
return 0;
}
if (!strcasecmp(cdr->party_a.snapshot->name, name) ||
(cdr->party_b.snapshot && !strcasecmp(cdr->party_b.snapshot->name, name))) {
return CMP_MATCH;
@ -2910,7 +2889,7 @@ int ast_cdr_setvar(const char *channel_name, const char *name, const char *value
}
}
it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE | OBJ_KEY, cdr_object_select_all_by_channel_cb, arg);
it_cdrs = ao2_callback(active_cdrs_by_channel, OBJ_MULTIPLE, cdr_object_select_all_by_channel_cb, arg);
if (!it_cdrs) {
ast_log(AST_LOG_ERROR, "Unable to find CDR for channel %s\n", channel_name);
return -1;
@ -3240,7 +3219,11 @@ int ast_cdr_set_property(const char *channel_name, enum ast_cdr_options option)
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);
@ -3584,7 +3567,10 @@ static char *handle_cli_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_a
switch (cmd) {
case CLI_INIT:
e->command = "cdr set debug [on|off]";
e->usage = "Enable or disable extra debugging in the CDR Engine";
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;
@ -3605,6 +3591,181 @@ static char *handle_cli_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_a
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_by_channel, 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] = "\0";
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_by_channel, 0);
while ((cdr = ao2_iterator_next(&it_cdrs))) {
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;
}
}
/* Only CDRs when this was dialed are available; skip */
if (ast_tvzero(start_time)) {
ao2_ref(cdr, -1);
continue;
}
it_cdr = cdr->last;
end_time = ast_tvzero(cdr->last->end) ? ast_tvnow() : cdr->last->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,
(long)ast_tvdiff_ms(end_time, answer_time) / 1000,
(long)ast_tvdiff_ms(end_time, start_time) / 1000);
ao2_ref(cdr, -1);
}
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] = "\0";
char end_time_buffer[64] = "\0";
const char *channel_name = a->argv[3];
RAII_VAR(struct cdr_object *, cdr, NULL, ao2_cleanup);
#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"
cdr = ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY);
if (!cdr) {
ast_cli(a->fd, "Unknown channel: %s\n", channel_name);
return;
}
ast_cli(a->fd, "\n");
ast_cli(a->fd, "Call Detail Record (CDR) Information for %s\n", channel_name);
ast_cli(a->fd, "--------------------------------------------------\n");
ast_cli(a->fd, TITLE_STRING, "AccountCode", "CallerID", "Dst. Channel", "LastApp", "Data", "Start", "Answer", "End", "Billsec", "Duration");
ao2_lock(cdr);
for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
struct timeval end;
if (snapshot_is_dialed(it_cdr->party_a.snapshot)) {
continue;
}
ast_callerid_merge(clid, sizeof(clid), it_cdr->party_a.snapshot->caller_name, it_cdr->party_a.snapshot->caller_number, "");
if (ast_tvzero(it_cdr->end)) {
end = ast_tvnow();
} else {
end = it_cdr->end;
}
cdr_get_tv(it_cdr->start, "%T", start_time_buffer, sizeof(start_time_buffer));
cdr_get_tv(it_cdr->answer, "%T", answer_time_buffer, sizeof(answer_time_buffer));
cdr_get_tv(end, "%T", end_time_buffer, sizeof(end_time_buffer));
ast_cli(a->fd, FORMAT_STRING,
it_cdr->party_a.snapshot->accountcode,
clid,
it_cdr->party_b.snapshot ? it_cdr->party_b.snapshot->name : "<none>",
it_cdr->appl,
it_cdr->data,
start_time_buffer,
answer_time_buffer,
end_time_buffer,
(long)ast_tvdiff_ms(end, it_cdr->answer) / 1000,
(long)ast_tvdiff_ms(end, it_cdr->start) / 1000);
}
ao2_unlock(cdr);
#undef FORMAT_STRING
#undef TITLE_STRING
}
static char *handle_cli_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
switch (cmd) {
case CLI_INIT:
e->command = "cdr show active";
e->usage =
"Usage: cdr show active [channel]\n"
" Displays a summary of all Call Detail Records when [channel]\n"
" is omitted; displays all of the Call Detail Records\n"
" currently in flight for a given [channel] when [channel] is\n"
" specified.\n\n"
" Note that this will not display Call Detail Records that\n"
" have already been dispatched to a backend storage, nor for\n"
" channels that are no longer active.\n";
return NULL;
case CLI_GENERATE:
return cli_complete_show(a);
}
if (a->argc > 4) {
return CLI_SHOWUSAGE;
} else if (a->argc < 4) {
cli_show_channels(a);
} else {
cli_show_channel(a);
}
return CLI_SUCCESS;
}
static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
struct cdr_beitem *beitem = NULL;
@ -3623,8 +3784,9 @@ static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_
return NULL;
}
if (a->argc > 3)
if (a->argc > 3) {
return CLI_SHOWUSAGE;
}
ast_cli(a->fd, "\n");
ast_cli(a->fd, "Call Detail Record (CDR) settings\n");
@ -3688,6 +3850,7 @@ static char *handle_cli_submit(struct ast_cli_entry *e, int cmd, struct ast_cli_
static struct ast_cli_entry cli_submit = AST_CLI_DEFINE(handle_cli_submit, "Posts all pending batched CDR data");
static struct ast_cli_entry cli_status = AST_CLI_DEFINE(handle_cli_status, "Display the CDR status");
static struct ast_cli_entry cli_show = AST_CLI_DEFINE(handle_cli_show, "Display CDRs");
static struct ast_cli_entry cli_debug = AST_CLI_DEFINE(handle_cli_debug, "Enable debugging");
@ -3773,6 +3936,7 @@ static void cdr_engine_shutdown(void)
finalize_batch_mode();
ast_cli_unregister(&cli_status);
ast_cli_unregister(&cli_debug);
ast_cli_unregister(&cli_show);
ast_sched_context_destroy(sched);
sched = NULL;
ast_free(batch);
@ -3785,7 +3949,6 @@ static void cdr_engine_shutdown(void)
ao2_global_obj_release(module_configs);
ao2_ref(active_cdrs_by_channel, -1);
ao2_ref(active_cdrs_by_bridge, -1);
}
static void cdr_enable_batch_mode(struct ast_cdr_config *config)
@ -3809,6 +3972,31 @@ static void cdr_enable_batch_mode(struct ast_cdr_config *config)
config->batch_settings.size, config->batch_settings.time);
}
/*!
* \internal
* \brief Print channel object key (name).
* \since 12.0.0
*
* \param v_obj A pointer to the object we want the key printed.
* \param where User data needed by prnt to determine where to put output.
* \param prnt Print output callback function to use.
*
* \return Nothing
*/
static void cdr_container_print_fn(void *v_obj, void *where, ao2_prnt_fn *prnt)
{
struct cdr_object *cdr = v_obj;
struct cdr_object *it_cdr;
if (!cdr) {
return;
}
for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
prnt(where, "Party A: %s; Party B: %s; Bridge %s\n", it_cdr->party_a.snapshot->name, it_cdr->party_b.snapshot ? it_cdr->party_b.snapshot->name : "<unknown>",
it_cdr->bridge);
}
}
int ast_cdr_engine_init(void)
{
RAII_VAR(struct module_config *, mod_cfg, NULL, ao2_cleanup);
@ -3822,11 +4010,7 @@ int ast_cdr_engine_init(void)
if (!active_cdrs_by_channel) {
return -1;
}
active_cdrs_by_bridge = ao2_container_alloc(51, cdr_object_bridge_hash_fn, cdr_object_bridge_cmp_fn);
if (!active_cdrs_by_bridge) {
return -1;
}
ao2_container_register("cdrs_by_channel", active_cdrs_by_channel, cdr_container_print_fn);
cdr_topic = stasis_topic_create("cdr_engine");
if (!cdr_topic) {
@ -3864,6 +4048,7 @@ int ast_cdr_engine_init(void)
ast_cli_register(&cli_status);
ast_cli_register(&cli_debug);
ast_cli_register(&cli_show);
ast_register_atexit(cdr_engine_shutdown);
mod_cfg = ao2_global_obj_ref(module_configs);