Added the option to disable call cdr updates.

Added the option to send call cdr status.
Added the option to format call time.


git-svn-id: http://yate.null.ro/svn/yate/trunk@5262 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
andrei 2012-09-14 15:12:16 +00:00
parent 31155b34db
commit 5adc97d095
2 changed files with 381 additions and 3 deletions

View File

@ -6,6 +6,19 @@
; guardtime: int: Time in ms to remember hungup channels to avoid race conditions
;guardtime=5000
; updates: bool: Emit call cdr message when call status has changed
;updates=true
; status: bool: Emit call cdr status messages for the call duration
;status=false
; status_interval: int: The time interval in seconds to emit call cdr status messages
; Note: If status is set to false this interval will be ignored
; Default 60s
; Min 60s
; Max 600s
;status_interval=60
[parameters]
; Each line consists of name=bool where name is the name of the parameter being
@ -26,3 +39,48 @@
; The following parameters are handled internally and cannot be changed:
; time, chan, operation, cdrwrite, cdrtrack, cdrcreate, cdrid, runid,
; direction, status, duration, billtime, ringtime
; Special section for time formatting
; Formatting rules
; YYYY | %Y -> Year (2012)
; YY | %y -> Year, last two digits (00-99)
; MM | %m -> Month as a decimal number (01-12)
; DD | %d -> Day of the month (01-31)
; HH | %H -> Hour in 24h format (00-23)
; mm | %M -> Hour in 24h format (00-23)
; SS | %S -> Second (00-60)
; Note the value is rounded
; UTC -> If present the time will represent the UTC time. If is missing
; the time will represent local time zone
; N*u -> Milliseconds and Microseconds.
; Note! The value is rounded
; EG: time: 1.123456
; s.u -> 1.1
; s.uu -> 1.12
; s.uuu -> 1.123
; s.uuuu -> 1.1234
; s.uuuuu -> 1.12346
; s.uuuuuu -> 1.123456
; Other formats may me used but they must correspond with C strftime method formats
; Important! If duplicate formats are ignored!
; Eg: date : 09 14 2012
; YYYY YY DD DD mm DD-> 2012 YY 14 DD 09 DD
;[formatted-timers]
; Append the call start time in call cdr message with the given format
; Eg: call_start_time=YY/MM/DD HH:mm:SS.uuuuuu UTC
;call_start_time=
; Append the call answer time in call cdr message with the given format
; Eg: call_answer_time=YY/MM/DD HH:mm:SS.uuuuuu UTC
;call_answer_time=
; Append the call hangup time in call cdr message with the given format
; Eg: call_hangup_time=YY/MM/DD HH:mm:SS.uuuuuu UTC
;call_hangup_time=
; Append the call duration in call cdr message with the given format
; Allowed formats: HH mm SS N*u
; Eg: HH:mm:SS.uuu
;duration_call=

View File

@ -26,6 +26,8 @@
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <math.h>
using namespace TelEngine;
namespace { // anonymous
@ -96,6 +98,7 @@ public:
void emit(const char *operation = 0);
String getStatus() const;
static CdrBuilder* find(String &id);
u_int64_t m_statusTime;
private:
u_int64_t
m_start,
@ -143,9 +146,49 @@ private:
u_int64_t m_expires;
};
class StatusThread : public Thread
{
public:
inline StatusThread()
:m_exit(false),m_maxSleep(25)
{ }
inline ~StatusThread()
{ }
virtual void run();
inline void exit()
{ m_exit = true; }
private:
bool m_exit;
int m_maxSleep;
};
class CustomTimer : public String {
public:
inline CustomTimer(bool relative = false)
:m_enabled(false), m_gmt(false), m_relative(relative),m_usecCount(0),
m_usecIndex(-1)
{}
virtual ~CustomTimer()
{}
void process(const String& value);
void getTime(String& ret, u_int64_t time);
void getRelativeTime(String& ret, u_int64_t time);
bool m_enabled;
private:
void extractUsec(const String& param);
bool m_gmt;
bool m_relative;
int m_usecCount;
int m_usecIndex;
};
static ObjList s_cdrs;
static ObjList s_hungup;
CustomTimer m_startTime;
CustomTimer m_answerTime;
CustomTimer m_hangupTime;
CustomTimer m_durationTime(true);
u_int64_t Hungup::s_exp = 5000000;
// This mutex protects both the CDR list and the params list
@ -154,6 +197,10 @@ static ObjList s_params;
static int s_res = 1;
static int s_seq = 0;
static String s_runId;
static bool s_cdrUpdates = true;
static bool s_cdrStatus = false;
static unsigned int s_statusUpdate = 60000;
static StatusThread* s_updaterThread = 0;
// Time resolutions
static TokenDict const s_timeRes[] = {
@ -217,6 +264,35 @@ static const char* printTime(char* buf,u_int64_t usec)
return buf;
}
static const char* printTime(char* buf, u_int64_t usec, int count)
{
switch (count) {
case 1:
usec = (usec + 50000) / 100000;
sprintf(buf,"%01u",(unsigned int)(usec % 10));
break;
case 2:
usec = (usec + 5000) / 10000;
sprintf(buf,"%02u",(unsigned int)(usec % 100));
break;
case 3:
usec = (usec + 500) / 1000;
sprintf(buf,"%03u",(unsigned int)(usec % 1000));
break;
case 4:
usec = (usec + 50) / 100;
sprintf(buf,"%04u",(unsigned int)(usec % 10000));
break;
case 5:
usec = (usec + 5) / 10;
sprintf(buf,"%05u",(unsigned int)(usec % 100000));
break;
default:
sprintf(buf,"%06u",(unsigned int)(usec % 1000000));
}
return buf;
}
// Expire hungup guard records
static void expireHungup()
{
@ -234,7 +310,7 @@ CdrBuilder::CdrBuilder(const char *name)
: NamedList(name), m_dir("unknown"), m_status("unknown"),
m_first(true), m_write(true)
{
m_start = m_call = m_ringing = m_answer = m_hangup = 0;
m_statusTime = m_start = m_call = m_ringing = m_answer = m_hangup = 0;
m_cdrId = ++s_seq;
}
@ -277,6 +353,10 @@ void CdrBuilder::emit(const char *operation)
if (!operation)
operation = m_first ? "initialize" : "update";
if (String(operation) == YSTRING("update") && !s_cdrUpdates)
return;
m_first = false;
DDebug("cdrbuild",DebugAll,"Emit '%s' for '%s' status '%s'",
@ -293,6 +373,26 @@ void CdrBuilder::emit(const char *operation)
m->addParam("billtime",printTime(buf,t_hangup - t_answer));
m->addParam("ringtime",printTime(buf,t_answer - t_ringing));
m->addParam("status",m_status);
String tmp;
if (m_startTime.m_enabled) {
m_startTime.getTime(tmp,m_start);
m->addParam("call_start_time",tmp);
}
if (m_answerTime.m_enabled) {
m_answerTime.getTime(tmp,t_answer);
m->addParam("call_answer_time",tmp);
}
if (m_hangupTime.m_enabled) {
m_hangupTime.getTime(tmp,t_hangup);
m->addParam("call_hangup_time",tmp);
}
if (m_durationTime.m_enabled) {
m_durationTime.getTime(tmp,t_hangup - m_start);
m->addParam("call_duration",tmp);
}
if (!getValue("external")) {
const char* ext = 0;
if (m_dir == YSTRING("incoming"))
@ -430,6 +530,8 @@ bool CdrHandler::received(Message &msg)
if (n)
Debug("cdrbuild",DebugWarn,"Forcibly finalizing %u CDR records.",n);
s_cdrs.clear();
if (s_updaterThread)
s_updaterThread->exit();
return false;
}
if ((m_type == CdrProgress) && !msg.getBoolValue(YSTRING("earlymedia"),false))
@ -487,9 +589,11 @@ bool CdrHandler::received(Message &msg)
break;
}
}
if (b)
if (b) {
rval = b->update(msg,type,msg.msgTime().usec());
else
if (type == CdrAnswer)
b->m_statusTime = Time::msecNow() + s_statusUpdate;
} else
Debug("cdrbuild",level,"Got message '%s' for untracked id '%s'",
msg.c_str(),id.c_str());
if ((type == CdrRinging) || (type == CdrProgress) || (type == CdrAnswer)) {
@ -547,6 +651,174 @@ bool CommandHandler::received(Message &msg)
return false;
}
void StatusThread::run()
{
// Check if we should emit cdr status
Time now;
s_mutex.lock();
for (ObjList* o = s_cdrs.skipNull();o;o = o->skipNext()) {
CdrBuilder* cdr = static_cast<CdrBuilder*>(o->get());
if (cdr->getStatus() != YSTRING("answered"))
continue;
if (cdr->m_statusTime < now.msec()) {
cdr->emit("status");
cdr->m_statusTime = now.msec() + s_statusUpdate;
}
}
s_mutex.unlock();
// Check cdrs for timeout and emit cdr status
while (!m_exit) {
Thread::msleep(m_maxSleep);
Lock lock(s_mutex);
Time t;
for (ObjList* o = s_cdrs.skipNull();o;o = o->skipNext()) {
CdrBuilder* cdr = static_cast<CdrBuilder*>(o->get());
if (cdr->m_statusTime && cdr->m_statusTime < t.msec()) {
cdr->emit("status");
cdr->m_statusTime = t.msec() + s_statusUpdate;
}
}
}
}
void CustomTimer::process(const String& value)
{
if (value.length() == 0) {
m_enabled = false;
return;
}
if (m_relative) {
extractUsec(value);
return;
}
Debug(DebugWarn,"Requested to process: %s",value.c_str());
String tmp = value;
int count = 4;
// YYYY or YY the year
int i = tmp.find(YSTRING("YYYY"));
if (i < 0) {
i = tmp.find(YSTRING("YY"));
if (i >= 0)
count = 2;
}
if (i >= 0) {
Debug(DebugNote," i = %d count = %d",i,count);
tmp = tmp.substr(0,i) + ((count == 2) ? YSTRING("%y") : YSTRING("%Y")) +
tmp.substr(i + count);
}
// MM month
i = tmp.find(YSTRING("MM"));
if (i >= 0)
tmp = tmp.substr(0,i) + YSTRING("%m") + tmp.substr(i + 2);
// DD day
i = tmp.find(YSTRING("DD"));
if (i >= 0)
tmp = tmp.substr(0,i) + YSTRING("%d") + tmp.substr(i + 2);
// HH hour
i = tmp.find(YSTRING("HH"));
if (i >= 0)
tmp = tmp.substr(0,i) + YSTRING("%H") + tmp.substr(i + 2);
// mm minutes
i = tmp.find(YSTRING("mm"));
if (i >= 0)
tmp = tmp.substr(0,i) + YSTRING("%M") + tmp.substr(i + 2);
// SS seconds
i = tmp.find(YSTRING("SS"));
if (i >= 0)
tmp = tmp.substr(0,i) + YSTRING("%S") + tmp.substr(i + 2);
// UTC time zone
i = tmp.find(YSTRING("UTC"));
m_gmt = i >= 0;
if (i >= 0)
tmp = tmp.substr(0,i) + YSTRING("%Z") + tmp.substr(i + 3);
else
tmp += " %Z";
m_enabled = true;
extractUsec(tmp);
}
void CustomTimer::extractUsec(const String& param)
{
String tmp = param;
int i = tmp.find('u');
if (i < 0) {
assign(tmp);
return;
}
int count = 1;
char c;
while ((c = tmp.at(i + count)) == 'u')
count++;
m_usecCount = (count > 6) ? 6 : count;
//m_usecCount = pow(10,6 - count);
m_usecIndex = i;
if (i >= 0)
tmp = tmp.substr(0,i) + tmp.substr(i + count);
assign(tmp);
}
void CustomTimer::getTime(String& ret, u_int64_t time)
{
char buf[length() + 100];
time_t rawtime = time / 1000000;
String tmp = c_str();
if (m_usecIndex >= 0) {
char buf[10];
printTime(buf,time,m_usecCount);
String usec(buf);
tmp = tmp.substr(0,m_usecIndex) + usec + tmp.substr(m_usecIndex);
} else if (time % 1000000 > 500000){
rawtime ++;
}
if (m_relative) {
getRelativeTime(tmp,time);
ret.assign(tmp);
return;
}
struct tm * timeinfo;
if (!m_gmt)
timeinfo = localtime(&rawtime);
else
timeinfo = gmtime(&rawtime);
int len = strftime (buf, length() + 100, tmp.c_str(), timeinfo);
ret.assign(buf,len);
}
void CustomTimer::getRelativeTime(String& ret, u_int64_t time)
{
u_int64_t timeLeft = time / 1000000;
String tmp = ret;
int index = tmp.find(YSTRING("HH"));
if (index >= 0) {
int h = timeLeft / 3600;
timeLeft = timeLeft % 3600;
String aux = "";
if (h <= 9)
aux = "0";
tmp = tmp.substr(0,index) + aux + String(h) + tmp.substr(index + 2);
}
index = tmp.find(YSTRING("mm"));
if (index >= 0) {
int m = timeLeft / 60;
timeLeft = timeLeft % 60;
String aux = "";
if (m <= 9)
aux = "0";
tmp = tmp.substr(0,index) + aux + String(m) + tmp.substr(index + 2);
}
index = tmp.find(YSTRING("SS"));
if (index >= 0) {
String aux = "";
if (timeLeft <= 9)
aux = "0";
tmp = tmp.substr(0,index) + aux + String((int)timeLeft) + tmp.substr(index + 2);
}
ret.assign(tmp);
}
CdrBuildPlugin::CdrBuildPlugin()
: Plugin("cdrbuild"),
@ -598,6 +870,54 @@ void CdrBuildPlugin::initialize()
s_params.append(new Param(p->name(),p->toBoolean(false)));
}
}
s_cdrUpdates = cfg.getBoolValue("general","updates",true);
s_cdrStatus = cfg.getBoolValue("general","status",false);
int sUpdate = cfg.getIntValue("general","status_interval",60);
if (sUpdate < 60)
s_statusUpdate = 60000;
else if (sUpdate > 600)
s_statusUpdate = 600000;
else
s_statusUpdate = sUpdate * 1000;
if (s_cdrStatus && !s_updaterThread) {
s_updaterThread = new StatusThread();
s_updaterThread->startup();
} else if (s_updaterThread && !s_cdrStatus) {
s_updaterThread->exit();
s_updaterThread = 0;
}
while (true) {
NamedList* timers = cfg.getSection("formatted-timers");
if (!timers) {
m_startTime.m_enabled = false;
m_answerTime.m_enabled = false;
m_hangupTime.m_enabled = false;
break;
}
NamedString* param = timers->getParam("call_start_time");
if (param)
m_startTime.process(*param);
m_startTime.m_enabled = param != 0;
param = timers->getParam("call_answer_time");
if (param)
m_answerTime.process(*param);
m_answerTime.m_enabled = param != 0;
param = timers->getParam("call_hangup_time");
if (param)
m_hangupTime.process(*param);
m_hangupTime.m_enabled = param != 0;
param = timers->getParam("call_duration");
if (param)
m_durationTime.process(*param);
m_durationTime.m_enabled = param != 0;
break;
}
s_mutex.unlock();
if (m_first) {
m_first = false;