New universal CDR module

CDR module push data in realtime engines.
Is it good idea or not?

Example module code:

/*!
 * \file
 * \brief Custom Realtime CDR records.
 *
 * \author me
 *
 *
 * \arg See also \ref AstCDR
 *
 *
 * \ingroup cdr_drivers
 */

/*** MODULEINFO
	<depend></depend>
	<support_level>extended</support_level>
	<defaultenabled>no</defaultenabled>
 ***/

#include "asterisk.h"

#include "asterisk/paths.h"	/* use ast_config_AST_LOG_DIR */
#include "asterisk/channel.h"
#include "asterisk/cdr.h"
#include "asterisk/module.h"
#include "asterisk/config.h"
#include "asterisk/pbx.h"
#include "asterisk/utils.h"
#include "asterisk/cli.h"
#include "asterisk/app.h"

AST_MUTEX_DEFINE_STATIC(lock);

static const char config_file[] = "cdr_realtime.conf";

static const char desc[] = "Customizable Realtime CDR Backend";
static const char name[] = "cdr_realtime";

static char RT_Engine[80];
struct ast_variable *fields = NULL;

static void free_config(int reload);

static int load_config(int reload)
{
	struct ast_config *cfg;
	struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
	struct ast_variable *var, *first=NULL;

	if ((cfg = ast_config_load(config_file, config_flags)) == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEINVALID) {
		ast_log(LOG_WARNING, "Failed to %sload configuration file. %s\n", reload ? "re" : "", reload ? "" : "Module not activated.");
		return -1;
	} else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
		return 0;
	}

	if (reload) {
		free_config(1);
	}

	// Load main params
	ast_copy_string(RT_Engine, ast_variable_retrieve(cfg, "general", "engine"), sizeof(RT_Engine));
	ast_verb(3, "CDR Realtime  [Engine] start CDR flow to engine %s\n",RT_Engine);
	
	if (var=ast_variable_browse(cfg, "columns")) {
		for (; var; var = var->next) {
			if(!fields){
				fields=ast_variable_new(var->name, var->value,"");
				first=fields;
			} else {
				first->next=ast_variable_new(var->name, var->value,"");
				first=first->next;
			}
			ast_verb(3, "CDR Realtime [add column] %s -> %s\n",var->name, var->value);
		}
	}
	
	ast_config_destroy(cfg);
	return 0;
}

static void free_config(int reload)
{
	if(fields){
		ast_variables_destroy(fields);
		fields=NULL;
	}
}

static int write_cdr(struct ast_cdr *cdr)
{
	struct ast_variable *var, *values=NULL, *first=NULL;
	struct ast_channel *dummy;
	char subst_buf[2048];
	//RAII_VAR(struct ast_variable *, fields, NULL, ast_variables_destroy);
	if(!fields){
		ast_verbose(1, "CDR Realtime - no fields for write in engine, skip CDR update");
		return 0;
	}
	ast_mutex_lock(&lock);
	dummy = ast_dummy_channel_alloc();
	if (!dummy) {
		ast_log(LOG_ERROR, "Unable to allocate channel for variable subsitution.\n");
		ast_mutex_unlock(&lock);
		return 0;
	}
	ast_channel_cdr_set(dummy, ast_cdr_dup(cdr));
	var=fields;
	for (; var; var = var->next) {
		pbx_substitute_variables_helper(dummy, var->value, subst_buf, sizeof(subst_buf) - 1);
		if(!values){
			values=ast_variable_new(var->name,subst_buf,"");
			first=values;
		} else {
			values->next=ast_variable_new(var->name,subst_buf,"");
			values=values->next;
		}
		ast_debug(3, "CDR Realtime [write] %s -> %s\n",var->name, subst_buf);
	}
	if(first){
		ast_store_realtime_fields(RT_Engine,first);
		ast_variables_destroy(first);
	}
	ast_channel_unref(dummy);
	ast_mutex_unlock(&lock);

	return 0;
}

static int unload_module(void)
{
	if (ast_cdr_unregister(name)) {
		return -1;
	} else {
		free_config(0);
		return 0;
	}
}

static int load_module(void)
{
	if (load_config(0)) {
		return AST_MODULE_LOAD_DECLINE;
	}

	if (ast_cdr_register(name, desc, write_cdr)) {
		ast_log(LOG_ERROR, "Unable to register custom Realtime CDR handling\n");
		free_config(0);
		return AST_MODULE_LOAD_DECLINE;
	}

	return AST_MODULE_LOAD_SUCCESS;
}

static int reload(void)
{
	int res = 0;

	ast_mutex_lock(&lock);
	res = load_config(1);
	ast_mutex_unlock(&lock);

	return res;
}

AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Realtime CDR Module",
	.support_level = AST_MODULE_SUPPORT_EXTENDED,
	.load = load_module,
	.unload = unload_module,
	.reload = reload,
	.load_pri = AST_MODPRI_CDR_DRIVER,
	.requires = "cdr",
);

I’am create github repo

It sounds interesting but my understanding is that realtime is for dynamic configuration and not so much historical logging. EDIT: I improved my understanding – queue_log can send output to realtime.

One testing issue you might run into under load is that CDR writes start blocking reads from the same realtime backend.

Yes, tested, backend speed mariadb - 50 query/sec. (2 CPU 1.4 GHz)

We have many phones, AVG CDR/realtime data query speed - about 10 query/sec.
For small installation (<1000 phones) its work fine. (eq remote office - filial)

My profit - failover for CDR data (CDR is important for us) and realtime insert in main office database.

extconfig.conf:

realtimeData => sqlite3, PhonesDB,MyPhones ; some info
CDR => mysql,MySqlServer1,CDR,1 ; DB in main office
CDR => sqlite3,CDRStorage,CDR,2 ; Local cache DB - resync by script in main office

ODBC (cdr_adaptive_odbc) work not good, maybe lost CDR data silent (no error in asterisk log), plus minimum module fo embeded installations

I like the cut of your jib!

Do you see differences now under load between what your new module stores and the other CDR backends store, for the same calls ?

Hi bulatov,

I wanted to write the same kind of module but storing cdr into kafka.
How is your module going? Is it working fine any flaws?

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.