[asterisk-commits] mjordan: trunk r393599 - /trunk/main/cdr.c

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Wed Jul 3 17:04:10 CDT 2013


Author: mjordan
Date: Wed Jul  3 17:04:08 2013
New Revision: 393599

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=393599
Log:
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.

Modified:
    trunk/main/cdr.c

Modified: trunk/main/cdr.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/cdr.c?view=diff&rev=393599&r1=393598&r2=393599
==============================================================================
--- trunk/main/cdr.c (original)
+++ trunk/main/cdr.c Wed Jul  3 17:04:08 2013
@@ -318,9 +318,6 @@
 /*! \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;
 
@@ -784,16 +781,6 @@
 }
 
 /*! \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
  * CDRs active at one point in time
@@ -801,9 +788,9 @@
 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 @@
 {
 	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 @@
 		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 @@
 	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 @@
 	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 @@
 			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 @@
 		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 @@
 
 	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 @@
 	/* 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 @@
 		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 @@
 		|| (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 @@
 		} 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 @@
 	if (!candidates) {
 		return;
 	}
-
 	ao2_callback(candidates, OBJ_NODATA,
 			bridge_candidate_process,
 			cdr);
@@ -2577,8 +2560,6 @@
 		handled_cdr = cdr->last;
 	}
 	handle_bridge_pairings(handled_cdr, bridge);
-
-	ao2_link(active_cdrs_by_bridge, cdr);
 	ao2_unlock(cdr);
 }
 
@@ -2879,9 +2860,7 @@
 {
 	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 @@
 		}
 	}
 
-	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 @@
 		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 @@
 	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;
@@ -3600,6 +3586,181 @@
 	} else if (!strcmp(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");
+	}
+
+	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;
@@ -3623,8 +3784,9 @@
 		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 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 @@
 	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 @@
 	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 @@
 			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 @@
 	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 @@
 
 	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);




More information about the asterisk-commits mailing list