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:
parent
31155b34db
commit
5adc97d095
|
@ -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=
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue