1 /**
2  * monitoring.cpp
3  * This file is part of the YATE Project http://YATE.null.ro
4  *
5  * Module for monitoring and gathering information about YATE.
6  *
7  * Yet Another Telephony Engine - a fully featured software PBX and IVR
8  * Copyright (C) 2004-2014 Null Team
9  *
10  * This software is distributed under multiple licenses;
11  * see the COPYING file in the main directory for licensing
12  * information for this specific distribution.
13  *
14  * This use of this software may be subject to additional restrictions.
15  * See the LEGAL file in the main directory for details.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
20  */
21 
22 #include <yatephone.h>
23 
24 #define SIP_PORT	5060
25 
26 using namespace TelEngine;
27 
28 namespace {
29 
30 class Monitor;
31 class MsgUpdateHandler;
32 class CdrHandler;
33 class HangupHandler;
34 class CallMonitor;
35 
36 // structure to hold a counter, a threshold for the counter
37 // and an alarm for when the threshold had been surpassed
38 typedef struct {
39     unsigned int counter;
40     unsigned int threshold;
41     bool alarm;
42 } BaseInfo;
43 
44 // container for MGCP transaction information
45 typedef struct {
46     BaseInfo transactions;  // MGCP transactions information
47     BaseInfo deletes;       // MGCP delete connection transactions that have timed out
48     u_int64_t reset;	    // interval after which the data of this structure should be reset
49     u_int64_t resetTime;    // time at which the data should be reset
50     bool gw_monitor;
51 } MGCPInfo;
52 
53 // container for SIP transaction information
54 typedef struct {
55     BaseInfo auths;	    // SIP authentication requests information
56     BaseInfo transactions;  // SIP timed out transactions information
57     BaseInfo byes;	    // SIP timed out BYE transactions information
58     u_int64_t reset;
59     u_int64_t resetTime;
60 } SIPInfo;
61 
62 /**
63  * Class Cache
64  * BaseClass for retaining and expiring different type of data
65  */
66 class Cache : public Mutex, public GenObject
67 {
68 public:
69     enum Info {
70 	COUNT   = 1,
71 	INDEX   = 2,
72     };
Cache(const char * name)73     inline Cache(const char* name)
74     	: Mutex(false,name),
75 	  m_reload(true), m_expireTime(0), m_retainInfoTime(0)
76 	{ }
77     virtual ~Cache();
78 
79     // set the time which is used for increasing the expire time for the cache at each access
setRetainInfoTime(u_int64_t time)80     inline void setRetainInfoTime(u_int64_t time)
81     {
82 	m_retainInfoTime = time;
83 	m_expireTime = 0; //expire data
84     }
85 
86     // get information from the cached data
87     virtual String getInfo(const String& query, unsigned int& index, TokenDict* dict);
88     // check if the information has expired
isExpired()89     inline bool isExpired()
90 	{ return Time::secNow() > m_expireTime; }
91     // update the time at which the data will expire
updateExpire()92     inline void updateExpire()
93     {
94 	m_expireTime = Time::secNow() + m_retainInfoTime;
95 	m_reload = false;
96     }
97 
98 protected:
99     // load data into this object from a engine.status message
100     virtual bool load();
101     // discard the cached data
102     virtual void discard();
103     // table containing information about modules obtained from an engine.status message
104     ObjList m_table;
105     // flag for reloading
106     bool m_reload;
107 private:
108     // time at which the cached data will expire (in seconds)
109     u_int64_t m_expireTime;
110     // value with which increase the expire time at each access
111     u_int64_t m_retainInfoTime;
112 };
113 
114 /**
115  * Class ActiveCallInfo
116  * Hold data about current calls
117  */
118 class ActiveCallsInfo : public Cache
119 {
120 public:
121     enum InfoType {
122 	COUNT       = 1,    // count of current call
123 	INDEX       = 2,    // index of a call
124 	ID	    = 3,    // id of a call
125 	STATUS      = 4,    // status of a call
126 	CALLER      = 5,    // caller party
127 	CALLED      = 6,    // called party
128 	PEER	    = 7,    // peer(s) channel of a call
129 	DURATION    = 8,     // call duration
130     };
131     // Constructor
ActiveCallsInfo()132     inline ActiveCallsInfo()
133 	: Cache("Monitor::activeCallsInfo")
134 	{ }
135     // Destructor
~ActiveCallsInfo()136     inline ~ActiveCallsInfo()
137 	{ }
138     // add information about peers by checking the billing ID
139     String checkPeers(const String& billID, const String& callID);
140 
141 protected:
142     // load data into this object from a engine.status message
143     bool load();
144 };
145 
146 /**
147  * Class SigInfo
148  * Base class for handling information from the signalling channel module about signalling components
149  */
150 class SigInfo : public Cache
151 {
152 public:
153     // enum for types of information
154     enum InfoType {
155 	COUNT       = 1,    // number of components
156 	INDEX       = 2,    // index of a component
157 	ID	    = 3,    // id of a component
158 	STATUS      = 4,    // status of a component
159 	TYPE	    = 5,    // the type of the component
160 	ALARMS_COUNT = 6,   // alarm counter for the component
161 	SKIP	    = 7,     // helper value to skip unnecessary information when parsing the status string
162     };
163     // Constructor
SigInfo(const char * name,const TokenDict * dict)164     inline SigInfo(const char* name, const TokenDict* dict)
165 	: Cache(name), m_dictionary(dict)
166 	{ }
167     // Destructor
~SigInfo()168     inline ~SigInfo()
169 	{ m_table.clear(); }
170     // update the alarm counter for the component with the given name
171     void updateAlarmCounter(const String& name);
172 protected:
173     // load data into this object from a engine.status message
174     virtual bool load();
175     virtual void discard();
176     // dictionary for
177     const TokenDict* m_dictionary;
178 };
179 
180 /**
181  * Class InterfaceInfo
182  */
183 class InterfaceInfo : public SigInfo
184 {
185 public:
186     // Constructor
InterfaceInfo()187     inline InterfaceInfo()
188 	: SigInfo("Monitor::ifaceInfo",s_ifacesInfo)
189 	{ }
190     // Destructor
~InterfaceInfo()191     inline ~InterfaceInfo()
192 	{ }
193     // Dictionary for mapping Monitor queries about signalling interfaces
194     static TokenDict s_ifacesInfo[];
195 protected:
196     // load data into this object from a engine.status message
197     bool load();
198 };
199 
200 /**
201  * Class LinkInfo
202  */
203 class LinkInfo : public SigInfo
204 {
205 public:
206     enum LinkExtraInfo {
207     	UPTIME		= 8,
208     };
209     // Constructor
LinkInfo()210     inline LinkInfo()
211 	: SigInfo("Monitor::linkInfo",s_linkInfo)
212 	{ }
213     // Destructor
~LinkInfo()214     inline ~LinkInfo()
215 	{ }
216     // dictionary for mapping Monitor queries
217     static TokenDict s_linkInfo[];
218 protected:
219     // load data into this object from a engine.status message
220     bool load();
221 };
222 
223 /**
224  * Class LinksetInfo
225  * Hold status data about signalling linksets
226  */
227 class LinksetInfo : public SigInfo
228 {
229 public:
LinksetInfo()230     inline LinksetInfo()
231 	: SigInfo("Monitor::linksetInfo",s_linksetInfo)
232 	{ }
~LinksetInfo()233     inline ~LinksetInfo()
234 	{ }
235     // dictionary for mapping Monitor queries
236     static TokenDict s_linksetInfo[];
237 protected:
238     // load data into this object from a engine.status message
239     bool load();
240     // parse individual link entries
241     NamedList* parseLinksetInfo(String& info, const String& link, NamedList* infoFill = 0);
242     // dictonary for mapping status parameters
243     static TokenDict s_linksetStatus[];
244 };
245 
246 /**
247  * Class TrunkInfo
248  * Hold status data about signalling trunks
249  */
250  class TrunkInfo : public SigInfo
251  {
252  public:
253     enum TrunkExtraInfo {
254     	CIRCUITS	=  7,
255 	CALLS		=  8,
256 	LOCKED		=  9,
257 	IDLE		= 10,
258     };
259     // Constructor
TrunkInfo()260     inline TrunkInfo()
261 	: SigInfo("Monitor::trunkInfo",s_trunkInfo)
262 	{ }
263     // Destructor
~TrunkInfo()264     inline ~TrunkInfo()
265 	{ }
266     // dictionary for mapping Monitor queries
267     static TokenDict s_trunkInfo[];
268 protected:
269     // load data into this object from a engine.status message
270     bool load();
271     virtual void discard();
272     // parse individual trunk entries
273     NamedList* parseTrunkInfo(String& info,const String& trunkName, NamedList* infoFill = 0);
274     // dictonary for mapping status parameters
275     static TokenDict s_trunkStatus[];
276  };
277 
278 /**
279  * Accounts Info
280  * Cache for account status information
281  */
282 class AccountsInfo : public Cache
283 {
284 public:
285     // account information type
286     enum AccountInfoType {
287 	COUNT       = 1,
288 	INDEX       = 2,
289 	ID	    = 3,
290 	STATUS      = 4,
291 	PROTO	    = 5,
292 	USERNAME    = 6,
293     };
294     // Constructor
AccountsInfo()295     inline AccountsInfo()
296 	: Cache("Monitor::accountsInfo")
297 	{ }
298     // Destructor
~AccountsInfo()299     inline ~AccountsInfo()
300 	{ }
301 private:
302     // load data into this object from a engine.status message
303     bool load();
304 };
305 
306 /**
307  * EngineInfo - engine status information cache
308  */
309 class EngineInfo : public Cache
310 {
311 public:
312     enum EngineInfoType {
313 	ENGINE_TYPE	    = 1,
314 	ENGINE_PLUGINS      = 2,
315 	ENGINE_HANDLERS     = 3,
316 	ENGINE_MESSAGES     = 4,
317 	ENGINE_THREADS      = 5,
318 	ENGINE_WORKERS      = 6,
319 	ENGINE_MUTEXES      = 7,
320 	ENGINE_LOCKS        = 8,
321 	ENGINE_SEMAPHORES   = 9,
322 	ENGINE_WAITING      = 10,
323 	ENGINE_RUNATTEMPT   = 11,
324 	ENGINE_NODENAME     = 12,
325 	ENGINE_STATE	    = 13,
326 	ENGINE_CALL_ACCEPT  = 14,
327 	ENGINE_UNEX_RESTART = 15,
328 	ENGINE_MAXQUEUED    = 16,
329 	ENGINE_MSGRATE      = 17,
330 	ENGINE_MAXMSGRATE   = 18,
331 	ENGINE_MSGENQUEUED  = 19,
332 	ENGINE_MSGDEQUEUED  = 20,
333 	ENGINE_MSGDISPATCHED = 21,
334     };
335     // Constructor
EngineInfo()336     inline EngineInfo()
337 	: Cache("Monitor::engineInfo")
338 	{ }
339     // Destructor
~EngineInfo()340     inline ~EngineInfo()
341 	{ }
342     // get information from the cached data. Reimplemented from Cache
343     String getInfo(const String query, unsigned int index, TokenDict* dict);
344 private:
345     // load data into this object from a engine.status message
346     bool load();
347     // dictionary for mapping engine status parameters
348     static TokenDict s_engineInfo[];
349 };
350 
351 /**
352   * ModuleInfo - module information cache
353   */
354 class ModuleInfo : public Cache
355 {
356 public:
357     enum ModuleInfoType {
358     	COUNT		    = 1,
359 	INDEX		    = 2,
360 	MODULE_NAME	    = 3,
361 	MODULE_TYPE	    = 4,
362 	MODULE_INFO	    = 5,
363 	MODULE_FORMAT       = 6,
364     };
365     // Constructor
ModuleInfo()366     inline ModuleInfo()
367 	: Cache("Monitor::moduleInfo")
368 	{ }
369     // Destructor
~ModuleInfo()370     inline ~ModuleInfo()
371 	{ }
372 private:
373     // load data into this object from a engine.status message
374     bool load();
375     // dictionary for mapping engine status parameters
376     static TokenDict s_moduleInfo[];
377 };
378 
379 /**
380  * DatabaseAccount
381  * A container which holds status information about a single database account
382  */
383 class DatabaseAccount : public GenObject
384 {
385 public:
386     enum DbIndex {
387 	TOTAL_IDX	= 0,	// index for total number of queries
388 	FAILED_IDX	= 1,	// index for number of failed queries
389 	ERROR_IDX       = 2,     // index for number of queries returning with an error status
390 	TIME_IDX	= 3,	// index for time spent executing queries
391 	CONN_IDX	= 4,	// index for number of active connections
392     };
393     enum DbAlarms {
394 	TOTAL_ALARM 	= 0x1,
395 	FAILED_ALARM	= 0x2,
396 	ERROR_ALARM     = 0x4,
397 	EXEC_ALARM	= 0x8,
398 	CONNS_ALARM	= 0x10,
399     };
400     // enum for information type
401     enum DbData {
402 	QueriesCount		= 1,
403 	FailedCount		= 2,
404 	ErrorsCount		= 3,
405 	ExecTime		= 4,
406 	TooManyAlrm		= 5,
407 	TooManyFailedAlrm       = 6,
408 	TooManyErrorAlrm	= 7,
409 	ExecTooLongAlrm		= 8,
410 	NoConnAlrm		= 9,
411 	TooManyAlrmCount	= 10,
412 	TooManyFailedAlrmCount  = 11,
413 	TooManyErrorAlrmCount   = 12,
414 	ExecTooLongAlrmCount    = 13,
415 	NoConnAlrmCount		= 14,
416 	MaxQueries		= 15,
417 	MaxFailedQueries	= 16,
418 	MaxErrorQueries		= 17,
419 	MaxExecTime		= 18,
420 	AccountName		= 19,
421 	AccountIndex		= 20,
422     };
423     // Constructor
424     DatabaseAccount(const NamedList* cfg);
425     // Destructor
~DatabaseAccount()426     inline ~DatabaseAccount()
427 	{ }
428     // reimplemented toString() method to make object searcheble in lists
toString() const429     inline const String& toString() const
430 	{ return m_name; }
431     // set the database entry index in the database account table
setIndex(unsigned int index)432     inline void setIndex(unsigned int index)
433 	{ m_index = index; }
434     // get this account's index
index()435     inline unsigned int index()
436 	{ return m_index; }
437     // update the internal data from the list received
438     void update(const NamedList& info);
439     // obtain data
440     const String getInfo(unsigned int query);
441     // reset internal data
442     void reset();
isCurrent()443     inline bool isCurrent()
444 	{ return m_isCurrent; }
setIsCurrent(bool current=true)445     inline void setIsCurrent(bool current = true)
446 	{ m_isCurrent = current; }
447     // update configuration for this direction
448     void updateConfig(const NamedList* cfg);
449 private:
450     // account name
451     String m_name;
452     // index
453     unsigned int m_index;
454     // counters for number of queries
455     unsigned int m_dbCounters[ExecTime];
456     // counters for previous interval counter values
457     unsigned int m_prevDbCounters[ExecTime];
458     // alarms set
459     u_int16_t m_alarms;
460     // alarm counters
461     unsigned int m_alarmCounters[CONN_IDX + 1];
462     // thresholds for triggering alarms
463     unsigned int m_thresholds[CONN_IDX];
464     // time at which internal data should be reset
465     unsigned int m_resetTime;
466     // time to hold on on current data
467     unsigned int m_resetInterval;
468     // flag if monitored account is current (i.e. false means this direction was removed from monitoring)
469     bool m_isCurrent;
470 };
471 
472 /**
473  * DatabaseInfo - database information
474  * A list of DatabaseAccounts
475  */
476 class DatabaseInfo : public Cache
477 {
478 public:
479     enum DbInfoType {
480 	Connections     = 1,
481 	FailedConns     = 2,
482 	Accounts	= 3,
483     };
484     // Constructor
DatabaseInfo(bool monitored=false)485     inline DatabaseInfo(bool monitored = false)
486 	: Cache("Monitor::dbInfo"), m_monitor(monitored)
487 	{ }
488     // Destructor
~DatabaseInfo()489     inline ~DatabaseInfo()
490 	{ m_table.clear(); }
491     // create and add a new DatabaseAccount created from the received configuration
492     void addDatabase(NamedList* cfg);
493     // update the internal data from the received message
494     void update(const Message& msg);
495     // get the requested information
496     String getInfo(const String& query, unsigned int& index, TokenDict* dict);
497     // try to reset internal data of the table entries
498     void reset();
499 
setMonitorEnabled(bool enable=false)500     inline void setMonitorEnabled(bool enable = false)
501 	{ m_monitor = enable; }
monitorEnable()502     inline bool monitorEnable()
503 	{ return m_monitor; }
504     // reconfigure
505     void setConfigure(const NamedList* sect);
506     // update database account after reinitialization
507     void updateDatabaseAccounts();
508 private:
509     static TokenDict s_databaseInfo[];
510     bool load();
511     // number of successful and failed connections to databases
512     unsigned int m_connData[FailedConns];
513     bool m_monitor;
514 
515     static String s_dbParam;
516     static String s_totalParam;
517     static String s_failedParam;
518     static String s_errorParam;
519     static String s_hasConnParam;
520     static String s_timeParam;
521 };
522 
523 /**
524  *  RTPEntry. Container holding data about a single monitored RTP direction
525  */
526 class RTPEntry : public GenObject
527 {
528 public:
529     enum RTPInfoType {
530 	Count       = 1,
531 	Index       = 2,
532 	Direction   = 3,
533 	NoAudio     = 4,
534 	LostAudio   = 5,
535 	PktsLost    = 6,
536 	SyncLost    = 7,
537 	SeqLost     = 8,
538 	WrongSRC    = 9,
539 	WrongSSRC   = 10,
540     };
541     RTPEntry(String rtpDirection);
542     ~RTPEntry();
toString() const543     inline const String& toString() const
544 	{ return m_rtpDir; }
545     // update the entry from the received information
546     void update(const NamedList& nl);
547     // reset internal data
548     void reset();
549     // set the index for this entry
setIndex(unsigned int index)550     inline void setIndex(unsigned int index)
551 	{ m_index = index; }
552     // get info from thins entry
553     String getInfo(unsigned int query);
554     // mapping dictionary for Monitor queries
555     static TokenDict s_rtpInfo[];
isCurrent()556     inline bool isCurrent()
557 	{ return m_isCurrent; }
setIsCurrent(bool current=true)558     inline void setIsCurrent(bool current = true)
559 	{ m_isCurrent = current; }
560 private:
561     // the RTP direction
562     String m_rtpDir;
563     // counters
564     unsigned int m_counters[WrongSSRC - Direction];
565     unsigned int m_index;
566     // flag if monitored direction is current (i.e. false means this direction was removed from monitoring)
567     bool m_isCurrent;
568 };
569 
570 /**
571  *  RTPTable. A list of RTPEntry
572  */
573 class RTPTable : public GenObject
574 {
575 public:
576     // Constructor
577     inline RTPTable(const NamedList* cfg);
578     // Destructor
~RTPTable()579     inline ~RTPTable()
580 	{ m_rtpEntries.clear(); }
581     // update internal data
582     void update(Message& msg);
583     // get the answer to a query
584     String getInfo(const String& query, const unsigned int& index);
585     // reset internal data
586     void reset();
587     // check if the internal data should be reset
shouldReset()588     inline bool shouldReset()
589 	{ return Time::secNow() >= m_resetTime; }
590     void reconfigure(const NamedList* cfg);
591 private:
592     // list of RTPEntry
593     ObjList m_rtpEntries;
594     Mutex m_rtpMtx;
595     // interval for how long the data should be kept
596     u_int64_t m_resetInterval;
597     // time at which the data should be reset
598     u_int64_t m_resetTime;
599     // RTP monitored?
600     bool m_monitor;
601 };
602 
603 // A route entry which is monitored for quality of service values
604 class CallRouteQoS : public GenObject
605 {
606 public:
607     enum CallStatus {
608 	ANSWERED    = 1,
609 	DELIVERED   = 2,
610     };
611     enum Indexes {
612 	CURRENT_IDX     = 0,
613 	PREVIOUS_IDX    = 1,
614 	TOTAL_IDX       = 2,
615     };
616     enum ALARMS {
617 	LOW_ASR     = 1,
618 	HIGH_ASR    = 2,
619 	LOW_NER     = 4,
620     };
621     enum QoSNotifs {
622 	ASR_LOW		= 1,
623 	ASR_HIGH	= 2,
624 	ASR_LOW_ALL	= 3,
625 	ASR_HIGH_ALL	= 4,
626 	NER_LOW		= 5,
627 	NER_LOW_ALL	= 6,
628 	ASR		= 7,
629 	NER		= 8,
630 	ASR_ALL		= 9,
631 	NER_ALL		= 10,
632 	MIN_ASR		= 11,
633 	MAX_ASR		= 12,
634 	MIN_NER		= 13,
635 	LOW_ASR_COUNT   = 14,
636 	HIGH_ASR_COUNT  = 15,
637 	LOW_ASR_ALL_COUNT   = 16,
638 	HIGH_ASR_ALL_COUNT  = 17,
639 	LOW_NER_COUNT       = 18,
640 	LOW_NER_ALL_COUNT   = 19,
641 	HANGUP		= 40,
642 	REJECT		= 41,
643 	BUSY		= 42,
644 	CANCELLED	= 43,
645 	NO_ANSWER	= 44,
646 	NO_ROUTE	= 45,
647 	NO_CONN		= 46,
648 	NO_AUTH		= 47,
649 	CONGESTION      = 48,
650 	NO_MEDIA	= 49,
651 	NO_CAUSE	= 50,
652 	HANGUP_ALL	= 60,
653 	REJECT_ALL	= 61,
654 	BUSY_ALL	= 62,
655 	CANCELLED_ALL	= 63,
656 	NO_ANSWER_ALL	= 64,
657 	NO_ROUTE_ALL	= 65,
658 	NO_CONN_ALL	= 66,
659 	NO_AUTH_ALL	= 67,
660 	CONGESTION_ALL  = 68,
661 	NO_MEDIA_ALL	= 69,
662 	NAME		= 80,
663 	INDEX		= 81,
664     };
665     // Constructor
666     CallRouteQoS(const String direction, const NamedList* cfg = 0);
667     // Destructor
668     ~CallRouteQoS();
669     // update the call counters and call end reason counters
670     void update(int type = -1, int endReason = -1);
671     // update the ASR and NER values
672     void updateQoS();
673     // reset the internal data
674     void reset();
675     // check if the given value has surpassed the given threshold and set the appropriate alarm
676     void checkForAlarm(int& value, float hysteresis, u_int8_t& alarm, const int min, const int max, u_int8_t minAlarm, u_int8_t maxAlarm = 0xff);
677     // is this route in a state of alarm
678     bool alarm();
679     // get the alarm
680     const String alarmText();
681     // send periodic notifications
682     void sendNotifs(unsigned int index, bool reset = false);
683     // get the response to the given query
684     bool get(int query, String& result);
685     // reimplemented toString() method to make the object searcheable in lists
toString() const686     inline const String& toString() const
687 	{ return m_routeName; }
688     // set the index of this object
setIndex(unsigned int index)689     inline void setIndex(unsigned int index)
690 	{ m_index = index; }
index()691     inline unsigned int index()
692 	{ return m_index; }
isCurrent()693     inline bool isCurrent()
694 	{ return m_isCurrent; }
setIsCurrent(bool current=true)695     inline void setIsCurrent(bool current = true)
696 	{ m_isCurrent = current; }
697     // update configuration for this direction
698     void updateConfig(const NamedList* cfg);
699 private:
700     String m_routeName;
701     // call hangup reasons counters
702     unsigned int m_callCounters[NO_CAUSE - HANGUP];
703     unsigned int m_callCountersAll[NO_CAUSE - HANGUP];
704 
705     unsigned int m_totalCalls[TOTAL_IDX + 1];		// total calls
706     unsigned int m_answeredCalls[TOTAL_IDX + 1];     // total answered calls
707     unsigned int m_delivCalls[TOTAL_IDX + 1];	// total delivered calls
708 
709     // alarm flags for avoiding sending multiple times the same alarm
710     u_int8_t m_alarms;
711     u_int8_t m_overallAlarms;
712 
713     // flags for keeping track if an alarm has been sent or not
714     u_int8_t m_alarmsSent;
715     u_int8_t m_overallAlarmsSent;
716 
717     // alarm thresholds
718     int m_minASR;
719     int m_maxASR;
720     int m_minNER;
721 
722     // alarm counters
723     unsigned int m_alarmCounters[NER_LOW_ALL + 1];
724     // minimum number of calls before starting
725     unsigned int m_minCalls;
726     // index in the table
727     unsigned int m_index;
728     // flag if monitored direction is current (i.e. false means this direction was removed from monitoring)
729     bool m_isCurrent;
730 };
731 
732 /**
733  * Class CallMonitor
734  * Monitors number of calls, termination causes, computes statistic data
735  */
736 class CallMonitor : public MessageHandler, public Thread
737 {
738 public:
739      enum Indexes {
740 	IN_ASR_Idx	= 0,
741 	OUT_ASR_Idx	= 1,
742 	IN_NER_Idx	= 2,
743 	OUT_NER_Idx	= 3,
744     };
745 
746     enum Queries {
747 	INCOMING_CALLS    = 9,
748 	OUTGOING_CALLS    = 10,
749 	ROUTES_COUNT	  = 11,
750     };
751 
752     // constructor
753     CallMonitor(const NamedList* sect, unsigned int priority = 100);
~CallMonitor()754     virtual ~CallMonitor()
755 	{ }
756     // inherited methods
757     virtual bool received(Message& msg);
758     virtual bool init();
759     virtual void run();
760 
761     // obtain the value from the monitored data
762     void get(const String& query, const int& index, String& result);
763 
764     // get the value of a call counter
765     bool getCounter(int type, unsigned int& value);
766 
767     // send an alarm from a route
768     void sendAlarmFrom(CallRouteQoS* route);
769 
770     // add a route to be monitored
771     void addRoute(NamedList* sect);
772 
773     // reconfigure
774     void setConfigure(const NamedList* sect);
775     // update route after reinitialization
776     void updateRoutes();
777 
778 private:
779     // interval at which notifications are sent
780     unsigned int m_checkTime;
781     // time at which notifications are sent
782     unsigned int m_notifTime;
783     // call counters
784     unsigned int m_inCalls;
785     unsigned int m_outCalls;
786     // list of routes
787     ObjList m_routes;
788     Mutex m_routesMtx;
789     bool m_first;
790     // parameter on which to select routes from call.cdr message
791     String m_routeParam;
792     // Directions monitored?
793     bool m_monitor;
794     // configure mutex
795     Mutex m_cfgMtx;
796 };
797 
798 class SnmpMsgHandler;
799 class EngineStartHandler;
800 class AuthHandler;
801 class RegisterHandler;
802 
803 /**
804  * Class Monitor
805  * Monitoring module
806  */
807 class Monitor : public Module
808 {
809 public:
810     // enum for notification categories
811     enum Categories {
812 	CALL_MONITOR	= 1,
813 	DATABASE	= 2,
814 	ALARM_COUNTERS	= 3,
815 	ACTIVE_CALLS    = 4,
816 	PSTN		= 5,
817 	ENGINE		= 6,
818 	MODULE		= 7,
819 	AUTH_REQUESTS   = 8,
820 	REGISTER_REQUESTS = 9,
821 	INTERFACE	  = 10,
822 	SIP		  = 11,
823 	RTP		  = 12,
824 	TRUNKS		  = 13,
825 	LINKSETS	  = 14,
826 	LINKS		  = 15,
827 	IFACES		  = 16,
828 	ACCOUNTS	  = 17,
829 	MGCP		  = 18,
830     };
831 
832      enum SigTypes {
833 	SS7_MTP3	= 1,
834 	TRUNK	        = 2,
835 	ISDN		= 3,
836     };
837 
838     enum Cards {
839 	InterfaceDown = 1,
840 	InterfaceUp,
841     };
842 
843     enum SigNotifs {
844 	TrunkDown   = 1,
845 	TrunkUp,
846 	LinksetDown,
847 	LinksetUp,
848 	LinkDown,
849 	LinkUp,
850 	IsdnQ921Down,
851 	IsdnQ921Up,
852     };
853 
854     enum SipNotifs {
855         TransactTimedOut = 1,
856 	FailedAuths,
857 	ByesTimedOut,
858 	GWTimeout,
859 	GWUp,
860 	DeletesTimedOut,
861     };
862     Monitor();
863     virtual ~Monitor();
864     //inherited methods
865     virtual void initialize();
866     virtual bool received(Message& msg, int id);
867     bool unload();
868 
869     // handle module.update messages
870     void update(Message& msg);
871     // read configuration file
872     void readConfig(const Configuration& cfg);
873     // build and send SS7 notifications
874     void sendSigNotifs(Message& msg);
875     // build and send physical interface notifications
876     void sendCardNotifs(Message& msg);
877     // handle MGCP & SIP status notifications
878     void checkNotifs(Message& msg, unsigned int type);
879     // build a notification message
880     void sendTrap(const String& trap, const String& value, unsigned int index = 0,
881 	const char* text = 0);
882     // send multiple notifications at once
883     void sendTraps(const NamedList& traps);
884     // handle a monitor.query message
885     bool solveQuery(Message& msg);
886     // update monitored SIP gateway information
887     void handleChanHangup(const String& address, int& cause);
888     bool verifyGateway(const String& address);
889     // obtain SIP/MGCP transactions info
890     String getTransactionsInfo(const String& query, const int who);
891 private:
892     // message handlers
893     MsgUpdateHandler* m_msgUpdateHandler;
894     SnmpMsgHandler* m_snmpMsgHandler;
895     HangupHandler* m_hangupHandler;
896     EngineStartHandler* m_startHandler;
897     CallMonitor* m_callMonitor;
898     AuthHandler* m_authHandler;
899     RegisterHandler* m_registerHandler;
900     bool m_init;
901     bool m_newTraps;
902     // list of monitored SIP gateways and timed out gateways
903     ObjList* m_sipMonitoredGws;
904     ObjList m_timedOutGws;
905 
906     // flags if certain monitored information should be passed along in form of notifications
907     bool m_trunkMon;
908     bool m_linksetMon;
909     bool m_linkMon;
910     bool m_interfaceMon;
911     bool m_isdnMon;
912     // caches
913     ActiveCallsInfo* m_activeCallsCache;
914     TrunkInfo* m_trunkInfo;
915     EngineInfo* m_engineInfo;
916     ModuleInfo* m_moduleInfo;
917     DatabaseInfo* m_dbInfo;
918     RTPTable* m_rtpInfo;
919 
920     LinksetInfo* m_linksetInfo;
921     LinkInfo* m_linkInfo;
922     InterfaceInfo* m_ifaceInfo;
923     AccountsInfo* m_accountsInfo;
924 };
925 
926 static int s_yateRun = 0;
927 static int s_yateRunAlarm = 0;
928 static int s_alarmThreshold = DebugNote;
929 static String s_nodeState = "";
930 static double s_qosHysteresisFactor = 2.0;
931 
932 MGCPInfo s_mgcpInfo = { {0, 0, false}, {0, 0, false}, 0, 0, false};
933 static SIPInfo s_sipInfo = { {0, 0, false}, {0, 0, false},  {0, 0, false}, 0, 0};
934 
935 static TokenDict s_modules[] = {
936     {"mysqldb",	    Monitor::DATABASE},
937     {"pgsqldb",     Monitor::DATABASE},
938     {"sig",	    Monitor::PSTN},
939     {"wanpipe",     Monitor::INTERFACE},
940     {"zaptel",      Monitor::INTERFACE},
941     {"Tdm",	    Monitor::INTERFACE},
942     {"sip",	    Monitor::SIP},
943     {"yrtp",	    Monitor::RTP},
944     {"mgcpca",      Monitor::MGCP},
945     {0,0}
946 };
947 
948 static TokenDict s_categories[] = {
949     // database info
950     {"databaseCount",			Monitor::DATABASE},
951     {"databaseIndex",			Monitor::DATABASE},
952     {"databaseAccount",			Monitor::DATABASE},
953     {"queriesCount",			Monitor::DATABASE},
954     {"failedQueries",			Monitor::DATABASE},
955     {"errorQueries",			Monitor::DATABASE},
956     {"queryExecTime",			Monitor::DATABASE},
957     {"successfulConnections",		Monitor::DATABASE},
958     {"failedConnections",		Monitor::DATABASE},
959     // database alarm counters
960     {"tooManyQueriesAlarms",		Monitor::DATABASE},
961     {"tooManyFailedQueriesAlarms",	Monitor::DATABASE},
962     {"tooManyErrorQueriesAlarms",	Monitor::DATABASE},
963     {"queryExecTooLongAlarms",		Monitor::DATABASE},
964     {"noConnectionAlarms",		Monitor::DATABASE},
965     // database thresholds
966     {"queriesCountThreshold",		Monitor::DATABASE},
967     {"failedQueriesThreshold",		Monitor::DATABASE},
968     {"errorQueriesThreshold",		Monitor::DATABASE},
969     {"queryExecTimeThreshold",		Monitor::DATABASE},
970      // QOS
971     {"qosDirectionsCount",		Monitor::CALL_MONITOR},
972     {"qosEntryIndex",			Monitor::CALL_MONITOR},
973     {"qosEntryDirection",		Monitor::CALL_MONITOR},
974     {"lowASRThreshold",			Monitor::CALL_MONITOR},
975     {"highASRThreshold",		Monitor::CALL_MONITOR},
976     {"currentASR",			Monitor::CALL_MONITOR},
977     {"overallASR",			Monitor::CALL_MONITOR},
978     {"lowNERThreshold",			Monitor::CALL_MONITOR},
979     {"currentNER",			Monitor::CALL_MONITOR},
980     {"overallNER",			Monitor::CALL_MONITOR},
981     // QOS alarm counters
982     {"currentLowASRAlarmCount",		Monitor::CALL_MONITOR},
983     {"overallLowASRAlarmCount",		Monitor::CALL_MONITOR},
984     {"currentHighASRAlarmCount",	Monitor::CALL_MONITOR},
985     {"overallHighASRAlarmCount",	Monitor::CALL_MONITOR},
986     {"currentLowNERAlarmCount",		Monitor::CALL_MONITOR},
987     {"overallLowNERAlarmCount",		Monitor::CALL_MONITOR},
988     // call counters
989     {"incomingCalls",			Monitor::CALL_MONITOR},
990     {"outgoingCalls",			Monitor::CALL_MONITOR},
991 
992     {"currentHangupEndCause",		Monitor::CALL_MONITOR},
993     {"currentBusyEndCause",		Monitor::CALL_MONITOR},
994     {"currentRejectedEndCause",		Monitor::CALL_MONITOR},
995     {"currentCancelledEndCause",	Monitor::CALL_MONITOR},
996     {"currentNoAnswerEndCause",		Monitor::CALL_MONITOR},
997     {"currentNoRouteEndCause",		Monitor::CALL_MONITOR},
998     {"currentNoConnectionEndCause",	Monitor::CALL_MONITOR},
999     {"currentNoAuthEndCause",		Monitor::CALL_MONITOR},
1000     {"currentCongestionEndCause",       Monitor::CALL_MONITOR},
1001     {"currentNoMediaEndCause",		Monitor::CALL_MONITOR},
1002 
1003     {"overallHangupEndCause",		Monitor::CALL_MONITOR},
1004     {"overallBusyEndCause",		Monitor::CALL_MONITOR},
1005     {"overallRejectedEndCause",		Monitor::CALL_MONITOR},
1006     {"overallCancelledEndCause",	Monitor::CALL_MONITOR},
1007     {"overallNoAnswerEndCause",		Monitor::CALL_MONITOR},
1008     {"overallNoRouteEndCause",		Monitor::CALL_MONITOR},
1009     {"overallNoConnectionEndCause",	Monitor::CALL_MONITOR},
1010     {"overallNoAuthEndCause",		Monitor::CALL_MONITOR},
1011     {"overallCongestionEndCause",       Monitor::CALL_MONITOR},
1012     {"overallNoMediaEndCause",		Monitor::CALL_MONITOR},
1013 
1014     // connections info
1015     // linksets
1016     {"linksetCount",		Monitor::LINKSETS},
1017     {"linksetIndex",		Monitor::LINKSETS},
1018     {"linksetID",		Monitor::LINKSETS},
1019     {"linksetType",		Monitor::LINKSETS},
1020     {"linksetStatus",		Monitor::LINKSETS},
1021     {"linksetDownAlarms",       Monitor::LINKSETS},
1022     // links
1023     {"linkCount",		Monitor::LINKS},
1024     {"linkIndex",		Monitor::LINKS},
1025     {"linkID",			Monitor::LINKS},
1026     {"linkType",		Monitor::LINKS},
1027     {"linkStatus",		Monitor::LINKS},
1028     {"linkDownAlarms",		Monitor::LINKS},
1029     {"linkUptime",		Monitor::LINKS},
1030     // interfaces
1031     {"interfacesCount",		Monitor::IFACES},
1032     {"interfaceIndex",		Monitor::IFACES},
1033     {"interfaceID",		Monitor::IFACES},
1034     {"interfaceStatus",		Monitor::IFACES},
1035     {"interfaceDownAlarms",     Monitor::IFACES},
1036     // accounts
1037     {"accountsCount",		Monitor::ACCOUNTS},
1038     {"accountIndex",		Monitor::ACCOUNTS},
1039     {"accountID",		Monitor::ACCOUNTS},
1040     {"accountStatus",		Monitor::ACCOUNTS},
1041     {"accountProtocol",		Monitor::ACCOUNTS},
1042     {"accountUsername",		Monitor::ACCOUNTS},
1043     // active calls info
1044     {"activeCallsCount",	Monitor::ACTIVE_CALLS},
1045     {"callEntryIndex",		Monitor::ACTIVE_CALLS},
1046     {"callEntryID",		Monitor::ACTIVE_CALLS},
1047     {"callEntryStatus",		Monitor::ACTIVE_CALLS},
1048     {"callEntryCaller",		Monitor::ACTIVE_CALLS},
1049     {"callEntryCalled",		Monitor::ACTIVE_CALLS},
1050     {"callEntryPeerChan",	Monitor::ACTIVE_CALLS},
1051     {"callEntryDuration",       Monitor::ACTIVE_CALLS},
1052     // trunk info
1053     {"trunksCount",		Monitor::TRUNKS},
1054     {"trunkIndex",		Monitor::TRUNKS},
1055     {"trunkID",			Monitor::TRUNKS},
1056     {"trunkType",		Monitor::TRUNKS},
1057     {"trunkCircuitCount",	Monitor::TRUNKS},
1058     {"trunkCurrentCallsCount",	Monitor::TRUNKS},
1059     {"trunkDownAlarms",		Monitor::TRUNKS},
1060     {"trunkCircuitsLocked",	Monitor::TRUNKS},
1061     {"trunkCircuitsIdle",	Monitor::TRUNKS},
1062     // engine info
1063     {"plugins",			Monitor::ENGINE},
1064     {"handlers",		Monitor::ENGINE},
1065     {"messages",		Monitor::ENGINE},
1066     {"msgMaxQueued",		Monitor::ENGINE},
1067     {"msgLastSecond",		Monitor::ENGINE},
1068     {"msgMaxPerSecond",		Monitor::ENGINE},
1069     {"msgEnqueued",		Monitor::ENGINE},
1070     {"msgDequeued",		Monitor::ENGINE},
1071     {"msgDispatched",		Monitor::ENGINE},
1072     {"threads",			Monitor::ENGINE},
1073     {"workers",			Monitor::ENGINE},
1074     {"mutexes",			Monitor::ENGINE},
1075     {"locks",			Monitor::ENGINE},
1076     {"semaphores",		Monitor::ENGINE},
1077     {"waitingSemaphores",       Monitor::ENGINE},
1078     {"acceptStatus",		Monitor::ENGINE},
1079     {"unexpectedRestart",       Monitor::ENGINE},
1080     // node info
1081     {"runAttempt",		Monitor::ENGINE},
1082     {"name",			Monitor::ENGINE},
1083     {"state",			Monitor::ENGINE},
1084     // module info
1085     {"moduleCount",		Monitor::MODULE},
1086     {"moduleIndex",		Monitor::MODULE},
1087     {"moduleName",		Monitor::MODULE},
1088     {"moduleType",		Monitor::MODULE},
1089     {"moduleExtra",		Monitor::MODULE},
1090     // request stats
1091     {"authenticationRequests",  Monitor::AUTH_REQUESTS},
1092     {"registerRequests",	Monitor::REGISTER_REQUESTS},
1093     // rtp stats
1094     {"rtpDirectionsCount",      Monitor::RTP},
1095     {"rtpEntryIndex",		Monitor::RTP},
1096     {"rtpDirection",		Monitor::RTP},
1097     {"noAudioCounter",		Monitor::RTP},
1098     {"lostAudioCounter",	Monitor::RTP},
1099     {"packetsLost",		Monitor::RTP},
1100     {"syncLost",		Monitor::RTP},
1101     {"sequenceNumberLost",      Monitor::RTP},
1102     {"wrongSRC",		Monitor::RTP},
1103     {"wrongSSRC",		Monitor::RTP},
1104     // sip stats
1105     {"transactionsTimedOut",    Monitor::SIP},
1106     {"failedAuths",		Monitor::SIP},
1107     {"byesTimedOut",		Monitor::SIP},
1108     // mgcp stats
1109     {"mgcpTransactionsTimedOut",    Monitor::MGCP},
1110     {"deleteTransactionsTimedOut",  Monitor::MGCP},
1111     {0,0}
1112 };
1113 
1114 static TokenDict s_callQualityQueries[] = {
1115     // alarms
1116     {"currentLowASR",		    CallRouteQoS::ASR_LOW},
1117     {"overallLowASR",		    CallRouteQoS::ASR_LOW_ALL},
1118     {"currentHighASR",		    CallRouteQoS::ASR_HIGH},
1119     {"overallHighASR",		    CallRouteQoS::ASR_HIGH_ALL},
1120     {"currentLowNER",		    CallRouteQoS::NER_LOW},
1121     {"overallLowNER",		    CallRouteQoS::NER_LOW_ALL},
1122     {"qosEntryDirection",	    CallRouteQoS::NAME},
1123     {"qosEntryIndex",		    CallRouteQoS::INDEX},
1124     // notifications
1125     {"currentASR",		    CallRouteQoS::ASR},
1126     {"overallASR",		    CallRouteQoS::ASR_ALL},
1127     {"currentNER",		    CallRouteQoS::NER},
1128     {"overallNER",		    CallRouteQoS::NER_ALL},
1129     // end cause counters
1130     {"currentHangupEndCause",	    CallRouteQoS::HANGUP},
1131     {"currentBusyEndCause",	    CallRouteQoS::BUSY},
1132     {"currentRejectedEndCause",     CallRouteQoS::REJECT},
1133     {"currentCancelledEndCause",    CallRouteQoS::CANCELLED},
1134     {"currentNoAnswerEndCause",	    CallRouteQoS::NO_ANSWER},
1135     {"currentNoRouteEndCause",	    CallRouteQoS::NO_ROUTE},
1136     {"currentNoConnectionEndCause", CallRouteQoS::NO_CONN},
1137     {"currentNoAuthEndCause",       CallRouteQoS::NO_AUTH},
1138     {"currentCongestionEndCause",   CallRouteQoS::CONGESTION},
1139     {"currentNoMediaEndCause",      CallRouteQoS::NO_MEDIA},
1140 
1141     {"overallHangupEndCause",       CallRouteQoS::HANGUP_ALL},
1142     {"overallBusyEndCause",	    CallRouteQoS::BUSY_ALL},
1143     {"overallRejectedEndCause",     CallRouteQoS::REJECT_ALL},
1144     {"overallCancelledEndCause",    CallRouteQoS::CANCELLED_ALL},
1145     {"overallNoAnswerEndCause",     CallRouteQoS::NO_ANSWER_ALL},
1146     {"overallNoRouteEndCause",	    CallRouteQoS::NO_ROUTE_ALL},
1147     {"overallNoConnectionEndCause", CallRouteQoS::NO_CONN_ALL},
1148     {"overallNoAuthEndCause",	    CallRouteQoS::NO_AUTH_ALL},
1149     {"overallCongestionEndCause",   CallRouteQoS::CONGESTION_ALL},
1150     {"overallNoMediaEndCause",      CallRouteQoS::NO_MEDIA_ALL},
1151     // thresholds
1152     {"lowASRThreshold",		    CallRouteQoS::MIN_ASR},
1153     {"highASRThreshold",	    CallRouteQoS::MAX_ASR},
1154     {"lowNERThreshold",		    CallRouteQoS::MIN_NER},
1155     // alarm counters
1156     {"currentLowASRAlarmCount",	    CallRouteQoS::LOW_ASR_COUNT},
1157     {"currentHighASRAlarmCount",    CallRouteQoS::HIGH_ASR_COUNT},
1158     {"overallLowASRAlarmCount",	    CallRouteQoS::LOW_ASR_ALL_COUNT},
1159     {"overallHighASRAlarmCount",    CallRouteQoS::HIGH_ASR_ALL_COUNT},
1160     {"currentLowNERAlarmCount",     CallRouteQoS::LOW_NER_COUNT},
1161     {"overallLowNERAlarmCount",	    CallRouteQoS::LOW_NER_ALL_COUNT},
1162     {0,0}
1163 };
1164 // call end reasons
1165 static TokenDict s_endReasons[] = {
1166     {"User hangup",               CallRouteQoS::HANGUP},
1167     {"Rejected",                  CallRouteQoS::REJECT},
1168     {"rejected",                  CallRouteQoS::REJECT},
1169     {"User busy",                 CallRouteQoS::BUSY},
1170     {"busy",                      CallRouteQoS::BUSY},
1171     {"Request Terminated",        CallRouteQoS::NO_ANSWER},
1172     {"noanswer",                  CallRouteQoS::NO_ANSWER},
1173     {"No route to call target",   CallRouteQoS::NO_ROUTE},
1174     {"noroute",                   CallRouteQoS::NO_ROUTE},
1175     {"Service Unavailable",       CallRouteQoS::NO_CONN},
1176     {"noconn",                    CallRouteQoS::NO_CONN},
1177     {"service-unavailable",       CallRouteQoS::NO_CONN},
1178     {"Unauthorized",              CallRouteQoS::NO_AUTH},
1179     {"noauth",                    CallRouteQoS::NO_AUTH},
1180     {"Cancelled",                 CallRouteQoS::CANCELLED},
1181     {"Congestion",                CallRouteQoS::CONGESTION},
1182     {"congestion",                CallRouteQoS::CONGESTION},
1183     {"Unsupported Media Type",    CallRouteQoS::NO_MEDIA},
1184     {"nomedia",                   CallRouteQoS::NO_MEDIA},
1185     {0,0}
1186 };
1187 
1188 static TokenDict s_callCounterQueries[] = {
1189     {"incomingCalls",		CallMonitor::INCOMING_CALLS},
1190     {"outgoingCalls",		CallMonitor::OUTGOING_CALLS},
1191     {"qosDirectionsCount",      CallMonitor::ROUTES_COUNT},
1192     {0,0}
1193 };
1194 
1195 static TokenDict s_activeCallInfo[] = {
1196     {"activeCallsCount",    ActiveCallsInfo::COUNT},
1197     {"callEntryID",	    ActiveCallsInfo::ID},
1198     {"callEntryIndex",	    ActiveCallsInfo::INDEX},
1199     {"callEntryID",	    ActiveCallsInfo::ID},
1200     {"callEntryStatus",	    ActiveCallsInfo::STATUS},
1201     {"callEntryCaller",     ActiveCallsInfo::CALLER},
1202     {"callEntryCalled",     ActiveCallsInfo::CALLED},
1203     {"callEntryPeerChan",   ActiveCallsInfo::PEER},
1204     {"callEntryDuration",   ActiveCallsInfo::DURATION},
1205     {0,0}
1206 };
1207 
1208 TokenDict TrunkInfo::s_trunkInfo[] = {
1209     {"trunksCount",		TrunkInfo::COUNT},
1210     {"trunkIndex",		TrunkInfo::INDEX},
1211     {"trunkID",			TrunkInfo::ID},
1212     {"trunkType",		TrunkInfo::TYPE},
1213     {"trunkCircuitCount",	TrunkInfo::CIRCUITS},
1214     {"trunkCurrentCallsCount",	TrunkInfo::CALLS},
1215     {"trunkDownAlarms",		TrunkInfo::ALARMS_COUNT},
1216     {"trunkCircuitsLocked",	TrunkInfo::LOCKED},
1217     {"trunkCircuitsIdle",	TrunkInfo::IDLE},
1218     {0,0}
1219 };
1220 
1221 TokenDict TrunkInfo::s_trunkStatus[] = {
1222     {"module",      TrunkInfo::SKIP},
1223     {"trunk",       TrunkInfo::ID},
1224     {"type",	    TrunkInfo::TYPE},
1225     {"circuits",    TrunkInfo::CIRCUITS},
1226     {"calls",       TrunkInfo::CALLS},
1227     {"status",      TrunkInfo::STATUS},
1228     {"locked",      TrunkInfo::LOCKED},
1229     {"idle",        TrunkInfo::IDLE},
1230     {0,0}
1231 };
1232 
1233 static TokenDict s_accountInfo[] = {
1234     {"accountsCount",   AccountsInfo::COUNT},
1235     {"accountIndex",    AccountsInfo::INDEX},
1236     {"accountID",       AccountsInfo::ID},
1237     {"accountStatus",   AccountsInfo::STATUS},
1238     {"accountProtocol", AccountsInfo::PROTO},
1239     {"accountUsername", AccountsInfo::USERNAME},
1240     {0,0}
1241 };
1242 
1243 static TokenDict s_engineQuery[] = {
1244     {"plugins",		    EngineInfo::ENGINE_PLUGINS},
1245     {"handlers",	    EngineInfo::ENGINE_HANDLERS},
1246     {"messages",	    EngineInfo::ENGINE_MESSAGES},
1247     {"msgMaxQueued",	    EngineInfo::ENGINE_MAXQUEUED},
1248     {"msgLastSecond",	    EngineInfo::ENGINE_MSGRATE},
1249     {"msgMaxPerSecond",	    EngineInfo::ENGINE_MAXMSGRATE},
1250     {"msgEnqueued",	    EngineInfo::ENGINE_MSGENQUEUED},
1251     {"msgDequeued",	    EngineInfo::ENGINE_MSGDEQUEUED},
1252     {"msgDispatched",	    EngineInfo::ENGINE_MSGDISPATCHED},
1253     {"threads",		    EngineInfo::ENGINE_THREADS},
1254     {"workers",		    EngineInfo::ENGINE_WORKERS},
1255     {"mutexes",		    EngineInfo::ENGINE_MUTEXES},
1256     {"locks",		    EngineInfo::ENGINE_LOCKS},
1257     {"semaphores",	    EngineInfo::ENGINE_SEMAPHORES},
1258     {"waitingSemaphores",   EngineInfo::ENGINE_WAITING},
1259     {"acceptStatus",	    EngineInfo::ENGINE_CALL_ACCEPT},
1260     // node info
1261     {"runAttempt",	    EngineInfo::ENGINE_RUNATTEMPT},
1262     {"name",		    EngineInfo::ENGINE_NODENAME},
1263     {"state",		    EngineInfo::ENGINE_STATE},
1264     {"unexpectedRestart",   EngineInfo::ENGINE_UNEX_RESTART},
1265     {0,0}
1266 };
1267 
1268 TokenDict EngineInfo::s_engineInfo[] = {
1269     {"type",                EngineInfo::ENGINE_TYPE},
1270     {"plugins",             EngineInfo::ENGINE_PLUGINS},
1271     {"handlers",            EngineInfo::ENGINE_HANDLERS},
1272     {"messages",            EngineInfo::ENGINE_MESSAGES},
1273     {"maxqueue",            EngineInfo::ENGINE_MAXQUEUED},
1274     {"messagerate",         EngineInfo::ENGINE_MSGRATE},
1275     {"maxmsgrate",          EngineInfo::ENGINE_MAXMSGRATE},
1276     {"enqueued",            EngineInfo::ENGINE_MSGENQUEUED},
1277     {"dequeued",            EngineInfo::ENGINE_MSGDEQUEUED},
1278     {"dispatched",          EngineInfo::ENGINE_MSGDISPATCHED},
1279     {"threads",             EngineInfo::ENGINE_THREADS},
1280     {"workers",             EngineInfo::ENGINE_WORKERS},
1281     {"mutexes",             EngineInfo::ENGINE_MUTEXES},
1282     {"locks",               EngineInfo::ENGINE_LOCKS},
1283     {"semaphores",          EngineInfo::ENGINE_SEMAPHORES},
1284     {"waiting",             EngineInfo::ENGINE_WAITING},
1285     {"runattempt",          EngineInfo::ENGINE_RUNATTEMPT},
1286     {"nodename",            EngineInfo::ENGINE_NODENAME},
1287     {"acceptcalls",         EngineInfo::ENGINE_CALL_ACCEPT},
1288     {"lastsignal",          EngineInfo::ENGINE_UNEX_RESTART},
1289     {0,0}
1290 };
1291 
1292 TokenDict ModuleInfo::s_moduleInfo[] = {
1293     {"name",	    ModuleInfo::MODULE_NAME},
1294     {"type",	    ModuleInfo::MODULE_TYPE},
1295     {"format",      ModuleInfo::MODULE_FORMAT},
1296     {0,0}
1297 };
1298 
1299 static TokenDict s_moduleQuery[] = {
1300     {"moduleCount",     ModuleInfo::COUNT},
1301     {"moduleIndex",     ModuleInfo::INDEX},
1302     {"moduleName",      ModuleInfo::MODULE_NAME},
1303     {"moduleType",      ModuleInfo::MODULE_TYPE},
1304     {"moduleExtra",     ModuleInfo::MODULE_INFO},
1305     {0,0}
1306 };
1307 
1308 TokenDict DatabaseInfo::s_databaseInfo[] = {
1309     {"conns",	   DatabaseInfo::Connections},
1310     {"failed",     DatabaseInfo::FailedConns},
1311     {0,0}
1312 };
1313 
1314 static TokenDict s_databaseQuery[] = {
1315     {"successfulConnections",   DatabaseInfo::Connections},
1316     {"failedConnections",       DatabaseInfo::FailedConns},
1317     {"databaseCount",		DatabaseInfo::Accounts},
1318     {0,0}
1319 };
1320 
1321 static TokenDict s_dbAccountQueries[] = {
1322     {"databaseIndex",			DatabaseAccount::AccountIndex},
1323     {"databaseAccount",			DatabaseAccount::AccountName},
1324     {"queriesCount",			DatabaseAccount::QueriesCount},
1325     {"failedQueries",			DatabaseAccount::FailedCount},
1326     {"errorQueries",			DatabaseAccount::ErrorsCount},
1327     {"queryExecTime",			DatabaseAccount::ExecTime},
1328     // alarms counters
1329     {"tooManyQueriesAlarms",		DatabaseAccount::TooManyAlrmCount},
1330     {"tooManyFailedQueriesAlarms",	DatabaseAccount::TooManyFailedAlrmCount},
1331     {"tooManyErrorQueriesAlarms",	DatabaseAccount::TooManyErrorAlrmCount},
1332     {"queryExecTooLongAlarms",		DatabaseAccount::ExecTooLongAlrmCount},
1333     {"noConnectionAlarms",		DatabaseAccount::NoConnAlrmCount},
1334     // database thresholds
1335     {"queriesCountThreshold",		DatabaseAccount::MaxQueries},
1336     {"failedQueriesThreshold",		DatabaseAccount::MaxFailedQueries},
1337     {"errorQueriesThreshold",		DatabaseAccount::MaxErrorQueries},
1338     {"queryExecTimeThreshold",		DatabaseAccount::MaxExecTime},
1339     // alarm
1340     {"tooManyQueries",			DatabaseAccount::TooManyAlrm},
1341     {"tooManyFailedQueries",		DatabaseAccount::TooManyFailedAlrm},
1342     {"tooManyErrorQueries",		DatabaseAccount::TooManyErrorAlrm},
1343     {"queryExecTimeTooLong",		DatabaseAccount::ExecTooLongAlrm},
1344     {"noConnection",			DatabaseAccount::NoConnAlrm},
1345     {0,0}
1346 };
1347 
1348 static TokenDict s_dbAccountInfo[] = {
1349     {"maxqueries",	    DatabaseAccount::MaxQueries},
1350     {"maxfailed",	    DatabaseAccount::MaxFailedQueries},
1351     {"maxerrors",	    DatabaseAccount::MaxErrorQueries},
1352     {"maxtimeperquery",	    DatabaseAccount::MaxExecTime},
1353     {"total",		    DatabaseAccount::TOTAL_IDX},
1354     {"failed",		    DatabaseAccount::FAILED_IDX},
1355     {"errorred",	    DatabaseAccount::ERROR_IDX},
1356     {"querytime",	    DatabaseAccount::TIME_IDX},
1357     {"hasconn",		    DatabaseAccount::CONN_IDX},
1358     {0,0}
1359 };
1360 
1361 TokenDict RTPEntry::s_rtpInfo[] = {
1362     {"remoteip",    RTPEntry::Direction},
1363     {"noaudio",     RTPEntry::NoAudio},
1364     {"lostaudio",   RTPEntry::LostAudio},
1365     {"lostpkts",    RTPEntry::PktsLost},
1366     {"synclost",    RTPEntry::SyncLost},
1367     {"seqslost",    RTPEntry::SeqLost},
1368     {"wrongsrc",    RTPEntry::WrongSRC},
1369     {"wrongssrc",   RTPEntry::WrongSSRC},
1370     {0,0}
1371 };
1372 
1373 static TokenDict s_rtpQuery[] = {
1374     {"rtpDirectionsCount",  RTPEntry::Count},
1375     {"rtpEntryIndex",       RTPEntry::Index},
1376     {"rtpDirection",	    RTPEntry::Direction},
1377     {"noAudioCounter",      RTPEntry::NoAudio},
1378     {"lostAudioCounter",    RTPEntry::LostAudio},
1379     {"packetsLost",	    RTPEntry::PktsLost},
1380     {"syncLost",	    RTPEntry::SyncLost},
1381     {"sequenceNumberLost",  RTPEntry::SeqLost},
1382     {"wrongSRC",	    RTPEntry::WrongSRC},
1383     {"wrongSSRC",	    RTPEntry::WrongSSRC},
1384     {0,0}
1385 };
1386 
1387 static TokenDict s_sigTypes[] = {
1388     {"ss7-mtp3", 	Monitor::SS7_MTP3},
1389     {"trunk",		Monitor::TRUNK},
1390     {"isdn-q921", 	Monitor::ISDN},
1391     {0,0}
1392 };
1393 
1394 static TokenDict s_sipNotifs[] = {
1395     {"transactionsTimedOut",    Monitor::TransactTimedOut},
1396     {"failedAuths",		Monitor::FailedAuths},
1397     {"byesTimedOut",		Monitor::ByesTimedOut},
1398     {"gatewayTimeout",		Monitor::GWTimeout},
1399     {"gatewayUp",		Monitor::GWUp},
1400     {0,0}
1401 };
1402 
1403 static TokenDict s_mgcpNotifs[] = {
1404     {"mgcpTransactionsTimedOut",    Monitor::TransactTimedOut},
1405     {"deleteTransactionsTimedOut",  Monitor::DeletesTimedOut},
1406     {"mgcpGatewayTimedOut",	    Monitor::GWTimeout},
1407     {"mgcpGatewayUp",		    Monitor::GWUp},
1408     {0,0}
1409 };
1410 
1411 static TokenDict s_sigNotifs[] = {
1412     {"trunkDown",       Monitor::TrunkDown},
1413     {"trunkUp",		Monitor::TrunkUp},
1414     {"linksetDown",     Monitor::LinksetDown},
1415     {"linksetUp",       Monitor::LinksetUp},
1416     {"linkUp",		Monitor::LinkUp},
1417     {"linkDown",	Monitor::LinkDown},
1418     {"linkUp",		Monitor::LinkUp},
1419     {"isdnQ921Down",    Monitor::IsdnQ921Down},
1420     {"isdnQ921Up",      Monitor::IsdnQ921Up},
1421     {0,0}
1422 };
1423 
1424 static TokenDict s_cardInfo[] = {
1425     {"interfaceDown",       Monitor::InterfaceDown},
1426     {"interfaceUp",	    Monitor::InterfaceUp},
1427     {0,0}
1428 };
1429 
1430 static TokenDict s_cardNotifs[] = {
1431     {"interfaceDown",       Monitor::InterfaceDown},
1432     {"interfaceUp",	    Monitor::InterfaceUp},
1433     {0,0}
1434 };
1435 
1436 TokenDict LinksetInfo::s_linksetInfo[] = {
1437     {"linksetCount",	    LinksetInfo::COUNT},
1438     {"linksetIndex",	    LinksetInfo::INDEX},
1439     {"linksetID",	    LinksetInfo::ID},
1440     {"linksetType",	    LinksetInfo::TYPE},
1441     {"linksetStatus",       LinksetInfo::STATUS},
1442     {"linksetDownAlarms",   LinksetInfo::ALARMS_COUNT},
1443     {0,0}
1444 };
1445 
1446 TokenDict LinksetInfo::s_linksetStatus[] = {
1447     {"module",      LinksetInfo::SKIP},
1448     {"component",   LinksetInfo::ID},
1449     {"type",	    LinksetInfo::TYPE},
1450     {"status",      LinksetInfo::STATUS},
1451     {0,0}
1452 };
1453 
1454 TokenDict LinkInfo::s_linkInfo[] = {
1455     {"linkCount",       LinkInfo::COUNT},
1456     {"linkIndex",       LinkInfo::INDEX},
1457     {"linkID",		LinkInfo::ID},
1458     {"linkType",	LinkInfo::TYPE},
1459     {"linkStatus",      LinkInfo::STATUS},
1460     {"linkDownAlarms",  LinkInfo::ALARMS_COUNT},
1461     {"linkUptime",      LinkInfo::UPTIME},
1462     {0,0}
1463 };
1464 
1465 TokenDict InterfaceInfo::s_ifacesInfo[] = {
1466     {"interfacesCount",     InterfaceInfo::COUNT},
1467     {"interfaceIndex",      InterfaceInfo::INDEX},
1468     {"interfaceID",	    InterfaceInfo::ID},
1469     {"interfaceStatus",     InterfaceInfo::STATUS},
1470     {"interfaceDownAlarms", InterfaceInfo::ALARMS_COUNT},
1471     {0,0}
1472 };
1473 
1474 String DatabaseInfo::s_dbParam = "database.";
1475 String DatabaseInfo::s_totalParam = "total.";
1476 String DatabaseInfo::s_failedParam = "failed.";
1477 String DatabaseInfo::s_errorParam = "errorred.";
1478 String DatabaseInfo::s_hasConnParam = "hasconn.";
1479 String DatabaseInfo::s_timeParam = "querytime.";
1480 
1481 INIT_PLUGIN(Monitor);
1482 
UNLOAD_PLUGIN(unloadNow)1483 UNLOAD_PLUGIN(unloadNow)
1484 {
1485     if (unloadNow && !__plugin.unload())
1486 	return false;
1487     return true;
1488 }
1489 
1490 
1491 /**
1492  * Class MsgUpdateHandler
1493  * Class for handling a "module.update" message
1494  */
1495 class MsgUpdateHandler : public MessageHandler
1496 {
1497 public:
MsgUpdateHandler(unsigned int priority=100)1498     inline MsgUpdateHandler(unsigned int priority = 100)
1499 	: MessageHandler("module.update",priority,__plugin.name())
1500 	{ }
~MsgUpdateHandler()1501     virtual ~MsgUpdateHandler()
1502 	{ }
1503     virtual bool received(Message& msg);
1504 };
1505 
1506 /**
1507  * Class SnmpMsgHandler
1508  * Class for handling a "monitor.query" message, message used for obtaining information from the monitor
1509  */
1510 class SnmpMsgHandler : public MessageHandler
1511 {
1512 public:
SnmpMsgHandler(unsigned int priority=100)1513     inline SnmpMsgHandler(unsigned int priority = 100)
1514 	: MessageHandler("monitor.query",priority,__plugin.name())
1515 	{ }
~SnmpMsgHandler()1516     virtual ~SnmpMsgHandler()
1517 	{ }
1518     virtual bool received(Message& msg);
1519 };
1520 
1521 /**
1522  * Class HangupHandler
1523  * Handler for "chan.hangup" message"
1524  */
1525 class HangupHandler : public MessageHandler
1526 {
1527 public:
HangupHandler(unsigned int priority=100)1528     inline HangupHandler(unsigned int priority = 100)
1529 	: MessageHandler("chan.hangup",priority,__plugin.name())
1530 	{ }
~HangupHandler()1531     virtual ~HangupHandler()
1532 	{ }
1533     virtual bool received(Message& msg);
1534 };
1535 
1536 /**
1537  * Class EngineStartHandler
1538  * Handler for "engine.start" message
1539  */
1540 class EngineStartHandler : public MessageHandler
1541 {
1542 public:
EngineStartHandler(unsigned int priority=100)1543     inline EngineStartHandler(unsigned int priority = 100)
1544 	: MessageHandler("engine.start",priority,__plugin.name())
1545 	{ }
~EngineStartHandler()1546     virtual ~EngineStartHandler()
1547 	{ }
1548     virtual bool received(Message& msg);
1549 };
1550 
1551 /**
1552  * Class AuthHandler
1553  * Handler for a "user.auth" message. It counts the number of authentication requests
1554  */
1555 class AuthHandler : public MessageHandler
1556 {
1557 public:
AuthHandler()1558     inline AuthHandler()
1559 	: MessageHandler("user.auth",1,__plugin.name()),
1560 	  m_count(0)
1561 	{ }
~AuthHandler()1562     virtual ~AuthHandler()
1563 	{ }
1564     virtual bool received(Message& msg);
1565     // return the number of authentication requests
getCount()1566     inline unsigned int getCount()
1567 	{ return m_count; }
1568 private:
1569     unsigned int m_count;
1570 };
1571 
1572 /**
1573  * Class RegisterHandler
1574  * Handler for a "user.register" message. It counts the number of register requests
1575  */
1576 class RegisterHandler : public MessageHandler
1577 {
1578 public:
RegisterHandler()1579     inline RegisterHandler()
1580 	: MessageHandler("user.register",1,__plugin.name()),
1581 	  m_count(0)
1582 	{ }
~RegisterHandler()1583     virtual ~RegisterHandler()
1584 	{ }
1585     virtual bool received(Message& msg);
1586     // return the count
getCount()1587     inline unsigned int getCount()
1588 	{ return m_count; }
1589 private:
1590     unsigned int m_count;
1591 };
1592 
1593 
1594 // helper function to get rid of new line characters
cutNewLine(String & line)1595 static void cutNewLine(String& line) {
1596     if (line.endsWith("\n"))
1597 	line = line.substr(0,line.length() - 1);
1598     if (line.endsWith("\r"))
1599 	line = line.substr(0,line.length() - 1);
1600 }
1601 
1602 // callback for engine alarm hook
alarmCallback(const char * message,int level,const char * component,const char * info)1603 static void alarmCallback(const char* message, int level, const char* component, const char* info)
1604 {
1605     if (TelEngine::null(component) || TelEngine::null(message))
1606 	return;
1607     const char* lvl = debugLevelName(level);
1608     if (TelEngine::null(lvl))
1609 	return;
1610     TempObjectCounter cnt(__plugin.objectsCounter());
1611     Message* msg = new Message("module.update");
1612     msg->addParam("module",__plugin.name());
1613     msg->addParam("level",String(level));
1614     msg->addParam("from",component,false);
1615     msg->addParam("text",message,false);
1616     msg->addParam("info",info,false);
1617     Engine::enqueue(msg);
1618     if ((s_alarmThreshold >= DebugFail) && (level <= s_alarmThreshold)) {
1619 	msg = new Message("monitor.notify",0,true);
1620 	msg->addParam("notify","genericAlarm");
1621 	msg->addParam("notify.0","alarmSource");
1622 	msg->addParam("value.0",component);
1623 	msg->addParam("notify.1","alarmLevel");
1624 	msg->addParam("value.1",lvl);
1625 	msg->addParam("notify.2","alarmText");
1626 	msg->addParam("value.2",message);
1627 	if (!TelEngine::null(info)) {
1628 	    msg->addParam("notify.3","alarmInfo");
1629 	    msg->addParam("value.3",info);
1630 	}
1631 	Engine::enqueue(msg);
1632     }
1633 }
1634 
1635 
1636 /**
1637   * MsgUpdateHandler
1638   */
received(Message & msg)1639 bool MsgUpdateHandler::received(Message& msg)
1640 {
1641     DDebug(__plugin.name(),DebugAll,"MsgUpdateHandler::received()");
1642     __plugin.update(msg);
1643     return true;
1644 }
1645 
1646 /**
1647   * SnmpMsgHandler
1648   */
received(Message & msg)1649 bool SnmpMsgHandler::received(Message& msg)
1650 {
1651     DDebug(__plugin.name(),DebugAll,"SnmpMsgHandler::received()");
1652     return __plugin.solveQuery(msg);
1653 }
1654 
1655 /**
1656   * HangupHandler
1657   */
received(Message & msg)1658 bool HangupHandler::received(Message& msg)
1659 {
1660     DDebug(__plugin.name(),DebugAll,"HangupHandler::received()");
1661     String status = msg.getValue("status","");
1662     String address = msg.getValue("address","");
1663     int cause = msg.getIntValue("cause_sip",0);
1664     if (status == "outgoing" && cause && !address.null())
1665 	__plugin.handleChanHangup(address,cause);
1666     if (status == "ringing" && !address.null())
1667 	__plugin.verifyGateway(address);
1668     return false;
1669 }
1670 
1671 /**
1672   * EngineStartHandler
1673   */
received(Message & msg)1674 bool EngineStartHandler::received(Message& msg)
1675 {
1676     DDebug(__plugin.name(),DebugAll,"EngineStartHandler::received()");
1677     s_yateRun = Engine::runParams().getIntValue("runattempt",0);
1678     if (s_yateRun >= s_yateRunAlarm && s_yateRunAlarm >= 1) {
1679 	String notif = lookup(EngineInfo::ENGINE_RUNATTEMPT,s_engineQuery,"");
1680 	if (!notif.null())
1681 	    __plugin.sendTrap(notif,String(s_yateRun));
1682     }
1683     int lastsignal = Engine::runParams().getIntValue(YSTRING("lastsignal"),0);
1684     if (lastsignal > 0) {
1685 	String notif = lookup(EngineInfo::ENGINE_UNEX_RESTART,s_engineQuery,"");
1686 	if (!notif.null())
1687 	    __plugin.sendTrap(notif,String(lastsignal));
1688     }
1689     return false;
1690 };
1691 
1692 /**
1693  * AuthHandler
1694  */
received(Message & msg)1695 bool AuthHandler::received(Message& msg)
1696 {
1697     String user = msg.getValue("username","");
1698     if (!user.null())
1699         m_count++;
1700     return false;
1701 }
1702 
1703 /**
1704  * RegisterHandler
1705  */
received(Message & msg)1706 bool RegisterHandler::received(Message& msg)
1707 {
1708     m_count++;
1709     return false;
1710 }
1711 
1712 /**
1713  * Cache
1714  */
~Cache()1715 Cache::~Cache()
1716 {
1717     discard();
1718 }
1719 
load()1720 bool Cache::load()
1721 {
1722     return false;
1723 }
1724 
1725 // discard cached data
discard()1726 void Cache::discard()
1727 {
1728     DDebug(&__plugin,DebugInfo,"Cache::discard() [%p] - dropping cached data",this);
1729     Lock l(this);
1730     m_reload = true;
1731     m_table.clear();
1732 }
1733 
getInfo(const String & query,unsigned int & index,TokenDict * dict)1734 String Cache::getInfo(const String& query, unsigned int& index, TokenDict* dict)
1735 {
1736     DDebug(&__plugin,DebugAll,"Cache::getInfo(query='%s',index='%d') [%p]",query.c_str(),index,this);
1737     // if we have data, check if it is still valid
1738     if (isExpired())
1739 	discard();
1740     else
1741 	// if the data has not yet expired, update the expire time
1742 	updateExpire();
1743 
1744     String retStr;
1745     // if the is no data available, obtain it from an engine.status message
1746     if (m_reload && !load())
1747 	return retStr;
1748 
1749     Lock l(this);
1750     // lookup the type of the query, and if it's of type COUNT, return the number of entries
1751     int type = lookup(query,dict,0);
1752     if (type == COUNT) {
1753 	retStr += m_table.count();
1754 	return retStr;
1755     }
1756     // if it's not of type COUNT, check if the requested index is within the range of the table
1757     if (index < 1 || index > m_table.count())
1758 	return retStr;
1759     // get the entry of the given index
1760     NamedList* nl = static_cast<NamedList*>(m_table[index - 1]);
1761     if (!nl)
1762 	return retStr;
1763     // get the result
1764     if (type == INDEX) {
1765 	retStr += index;
1766 	return retStr;
1767     }
1768     retStr = nl->getValue(query,"");
1769     if (retStr.null())
1770 	retStr = "no info";
1771     return retStr;
1772 }
1773 
1774 /**
1775  * ActiveCallInfo
1776  */
1777 // match bill ids to find a call's peer
checkPeers(const String & billID,const String & id)1778 String ActiveCallsInfo::checkPeers(const String& billID, const String& id)
1779 {
1780     DDebug(&__plugin,DebugAll,"ActiveCallsInfo::checkPeers('%s','%s')",billID.c_str(),id.c_str());
1781     if (billID.null())
1782 	return id;
1783     String retPeers;
1784     for(ObjList* o = m_table.skipNull(); o; o = o->skipNext()) {
1785 	NamedList* nl = static_cast<NamedList*>(o->get());
1786 	if (!nl)
1787 	    continue;
1788 	String otherBillID = nl->getValue("billId","");
1789 	String peers = nl->getValue(lookup(PEER,s_activeCallInfo,0),"");
1790 	if (billID == otherBillID) {
1791 	    String otherID = nl->getValue(lookup(ID,s_activeCallInfo,0),"");
1792 	    peers.append(id,";");
1793 	    retPeers.append(otherID,";");
1794 	}
1795 	nl->setParam(lookup(PEER,s_activeCallInfo,0),peers);
1796     }
1797     return retPeers;
1798 }
1799 
1800 // load data into the cache
load()1801 bool ActiveCallsInfo::load()
1802 {
1803     DDebug(&__plugin,DebugInfo,"ActiveCallsInfo::load() [%p] - loading data",this);
1804     // emit an engine.status message
1805     Message m("engine.status");
1806     m.addParam("module","cdrbuild");
1807     Engine::dispatch(m);
1808     String& status = m.retValue();
1809     if (TelEngine::null(status))
1810 	return false;
1811 
1812     Lock l(this);
1813     m_table.clear();
1814 
1815     int pos = status.rfind(';');
1816     if (pos < 0)
1817 	return false;
1818 
1819     status = status.substr(pos + 1);
1820     ObjList* calls = status.split(',');
1821     for (ObjList* o = calls->skipNull(); o; o = o->skipNext()) {
1822 	String* callInfo = static_cast<String*>(o->get());
1823 	if ( *callInfo == "\r\n")
1824 	    continue;
1825 	if (pos > -1) {
1826 	    int pos = callInfo->find("=");
1827 	    NamedList* nl = new NamedList("");
1828 
1829 	    String id = callInfo->substr(0,pos);
1830 	    callInfo->startSkip(String(id + "="));
1831 	    nl->setParam(lookup(ID,s_activeCallInfo,0),id);
1832 	    int i = 0;
1833 	    String peers;
1834 	    while (i < 5) {
1835 		pos = callInfo->find("|");
1836 		if (pos < -1)
1837 		    break;
1838 		String val = callInfo->substr(0,pos);
1839 		callInfo->startSkip(String(val + "|"),false);
1840 		val = val.uriUnescape();
1841 		int p = -1;
1842 		switch (i) {
1843 		    case 0:
1844 			p = val.find("=");
1845 			if (p > -1)
1846 			    val = val.substr(p+1);
1847 			nl->setParam(lookup(STATUS,s_activeCallInfo,0),val);
1848 			break;
1849 		    case 1:
1850 			val = ( val.null() ? "no info" : val);
1851 			nl->setParam(lookup(CALLER,s_activeCallInfo,0),val);
1852 			break;
1853 		    case 2:
1854 			val = ( val.null() ? "no info" : val);
1855 			nl->setParam(lookup(CALLED,s_activeCallInfo,0),val);
1856 			break;
1857 		    case 3:
1858 			peers = checkPeers(val,nl->getValue(lookup(ID,s_activeCallInfo,0),""));
1859 			nl->setParam("billId",val);
1860 			nl->setParam(lookup(PEER,s_activeCallInfo,0),peers);
1861 			break;
1862 		    case 4:
1863 			cutNewLine(val);
1864 			val = ( val.null() ? "no info" : val);
1865 			nl->setParam(lookup(DURATION,s_activeCallInfo,0),val);
1866 			break;
1867 		    default:
1868 			break;
1869 		}
1870 		i++;
1871 	    }
1872 	    m_table.append(nl);
1873 	}
1874     }
1875     TelEngine::destruct(calls);
1876     updateExpire();
1877     return true;
1878 }
1879 
1880 /**
1881  * InterfaceInfo
1882  */
1883 // reimplementation of the Cache::discard() function, only resets the status information
discard()1884 void SigInfo::discard()
1885 {
1886     if (!m_dictionary)
1887 	return;
1888     DDebug(&__plugin,DebugAll,"SigInfo::discard() [%p] - dropping cached data",this);
1889     for (ObjList* o = m_table.skipNull(); o; o = o->skipNext()) {
1890 	NamedList* nl = static_cast<NamedList*>(o->get());
1891         nl->setParam(lookup(STATUS,m_dictionary,""),"unknown");
1892     }
1893     m_reload = true;
1894 }
1895 
1896 // STUB
load()1897 bool SigInfo::load()
1898 {
1899     DDebug(&__plugin,DebugWarn,"SigInfo::load() [%p] - STUB",this);
1900     return true;
1901 }
1902 
1903 // increase the counter for number of alarms sent
updateAlarmCounter(const String & name)1904 void SigInfo::updateAlarmCounter(const String& name)
1905 {
1906     if (!m_dictionary)
1907 	return;
1908     DDebug(&__plugin,DebugAll,"SigInfo::updateAlarmCounter('%s') [%p]",name.c_str(),this);
1909     NamedList* nl = static_cast<NamedList*>(m_table[name]);
1910     if (!nl) {
1911 	load();
1912 	nl = static_cast<NamedList*>(m_table[name]);
1913     }
1914     if (nl) {
1915 	String param = lookup(ALARMS_COUNT,m_dictionary,"");
1916 	if (param.null())
1917 	    return;
1918 	int val = nl->getIntValue(param,0);
1919 	val++;
1920 	nl->setParam(param,String(val));
1921     }
1922 }
1923 
1924 /**
1925  * InterfaceInfo
1926  */
1927 // parse interface information and store it in the cache
load()1928 bool InterfaceInfo::load()
1929 {
1930     DDebug(&__plugin,DebugAll,"InterfaceInfo::load() [%p] - updating internal data",this);
1931     Message m("engine.status");
1932     m.addParam("module","sig ifaces");
1933     Engine::dispatch(m);
1934     String& status = m.retValue();
1935     if (!TelEngine::null(status)) {
1936 	cutNewLine(status);
1937 	ObjList* parts = status.split(';');
1938 	if (!(parts && parts->count() > 2)) {
1939 	    TelEngine::destruct(parts);
1940 	    return true;
1941 	}
1942 	String ifaces = static_cast<String*>(parts->at(2));
1943 	if (ifaces.null()) {
1944 	    TelEngine::destruct(parts);
1945 	    return true;
1946 	}
1947 	Lock l(this);
1948 	ObjList* list = ifaces.split(',');
1949 	for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
1950 	    String iface = static_cast<String*>(o->get());
1951 	    String name, status;
1952 	    iface.extractTo("=",name).extractTo("|",status);
1953 	    if (name.null())
1954 		continue;
1955 	    NamedList* nl = static_cast<NamedList*>(m_table[name]);
1956 	    if (!nl) {
1957 		nl = new NamedList(name);
1958 		nl->setParam(lookup(ID,s_ifacesInfo,""),name);
1959 		nl->setParam(lookup(STATUS,s_ifacesInfo,""),status);
1960 		m_table.append(nl);
1961 	    }
1962 	    else
1963 		nl->setParam(lookup(STATUS,s_ifacesInfo,""),status);
1964 	    if (!nl->getParam(lookup(ALARMS_COUNT,s_ifacesInfo,"")))
1965 		nl->setParam(lookup(ALARMS_COUNT,s_ifacesInfo,""),"0");
1966 	}
1967 	TelEngine::destruct(list);
1968 	TelEngine::destruct(parts);
1969     }
1970     updateExpire();
1971     return true;
1972 }
1973 
1974 /**
1975  * LinkInfo
1976  */
1977 // parse link information
load()1978 bool LinkInfo::load()
1979 {
1980     DDebug(&__plugin,DebugAll,"LinkInfo::load() [%p] - loading data",this);
1981     Message m("engine.status");
1982     m.addParam("module","sig links");
1983     Engine::dispatch(m);
1984     String& status = m.retValue();
1985     if (!TelEngine::null(status)) {
1986 	cutNewLine(status);
1987 	ObjList* parts = status.split(';');
1988 	if (!(parts && parts->count() > 2)) {
1989 	    TelEngine::destruct(parts);
1990 	    return true;
1991 	}
1992 	String links = static_cast<String*>(parts->at(2));
1993 	if (links.null()) {
1994 	    TelEngine::destruct(parts);
1995 	    return true;
1996 	}
1997 	Lock l(this);
1998 	ObjList* list = links.split(',');
1999 	for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
2000 	    String link = static_cast<String*>(o->get());
2001 	    String name,type,status;
2002 	    int uptime = 0;
2003 	    link.extractTo("=",name).extractTo("|",type).
2004 		extractTo("|",status).extractTo("|",uptime);
2005 	    if (name.null() || type.null())
2006 		continue;
2007 	    NamedList* nl = static_cast<NamedList*>(m_table[name]);
2008 	    if (!nl) {
2009 		nl = new NamedList(name);
2010 		nl->setParam(lookup(ID,s_linkInfo,""),name);
2011 		nl->setParam(lookup(TYPE,s_linkInfo,""),type);
2012 		nl->setParam(lookup(STATUS,s_linkInfo,""),status);
2013 		nl->setParam(lookup(UPTIME,s_linkInfo,""),String(uptime));
2014 		m_table.append(nl);
2015 	    }
2016 	    else {
2017 		nl->setParam(lookup(STATUS,s_linkInfo,""),status);
2018 		nl->setParam(lookup(UPTIME,s_linkInfo,""),String(uptime));
2019 	    }
2020 	    if (!nl->getParam(lookup(ALARMS_COUNT,s_linkInfo,"")))
2021 		nl->setParam(lookup(ALARMS_COUNT,s_linkInfo,""),"0");
2022 	    if (!nl->getParam(lookup(UPTIME,s_linkInfo,"")))
2023 		nl->setParam(lookup(UPTIME,s_linkInfo,""),"0");
2024 	}
2025 	TelEngine::destruct(list);
2026 	TelEngine::destruct(parts);
2027     }
2028     updateExpire();
2029     return true;
2030 }
2031 
2032 /**
2033  * LinksetInfo
2034  */
2035 // parse linkset information
load()2036 bool LinksetInfo::load()
2037 {
2038     DDebug(&__plugin,DebugAll,"LinksetInfo::load() [%p] - loading data",this);
2039     Message m("engine.command");
2040     m.addParam("partial","status sig ");
2041     m.addParam("partline","status sig");
2042     if (!Engine::dispatch(m))
2043 	return false;
2044     String& status = m.retValue();
2045     if (TelEngine::null(status))
2046 	return false;
2047 
2048     Lock l(this);
2049     ObjList* links = status.split('\t');
2050     for (ObjList* o = links->skipNull(); o; o = o->skipNext()) {
2051 	String* link = static_cast<String*>(o->get());
2052 	if (*link == "links" || *link == "ifaces")
2053 	    continue;
2054 	Message msg("engine.status");
2055 	msg.addParam("module",String("sig " + *link));
2056 	Engine::dispatch(msg);
2057 	String& linkInfo = msg.retValue();
2058 	if (linkInfo.null())
2059 	    continue;
2060 	NamedList* nl = static_cast<NamedList*>(m_table[*link]);
2061 	if (nl)
2062 	    parseLinksetInfo(linkInfo,*link,nl);
2063 	else {
2064 	    nl = parseLinksetInfo(linkInfo,*link);
2065 	    if (nl)
2066 		m_table.append(nl);
2067 	}
2068     }
2069     TelEngine::destruct(links);
2070     updateExpire();
2071     return true;
2072 }
2073 
2074 // parse the information about a single linkset
parseLinksetInfo(String & info,const String & link,NamedList * infoFill)2075 NamedList* LinksetInfo::parseLinksetInfo(String& info,const String& link, NamedList* infoFill)
2076 {
2077     cutNewLine(info);
2078     DDebug(&__plugin,DebugAll,"LinksetInfo::parseLinkInfo(info='%s',link='%s', infoFill='%p') [%p]",info.c_str(),link.c_str(),infoFill,this);
2079     NamedList* nl = (infoFill ? infoFill : new NamedList(link));
2080 
2081     ObjList* parts = info.split(';',false);
2082     for (ObjList* obj = parts->skipNull(); obj; obj = obj->skipNext()) {
2083 	String* infoPart = static_cast<String*>(obj->get());
2084 	if (TelEngine::null(infoPart))
2085 	    continue;
2086 	ObjList* params = infoPart->split(',',false);
2087 	for (ObjList* o = params->skipNull(); o; o = o->skipNext()) {
2088 	    String* param = static_cast<String*>(o->get());
2089 	    int pos = param->find("=");
2090 	    if (pos < 0)
2091 		continue;
2092 	    String nameParam = param->substr(0,pos);
2093 	    String valParam = param->substr(pos + 1);
2094 	    int type = lookup(nameParam,s_linksetStatus,0);
2095 	    if (type > 0) {
2096 		if (type == TYPE && (valParam.null() || valParam != "ss7-mtp3")) {
2097 		    TelEngine::destruct(params);
2098 		    TelEngine::destruct(nl);
2099 		    TelEngine::destruct(parts);
2100 		    return 0;
2101 		}
2102 		nl->setParam(lookup(type,s_linksetInfo,""),valParam);
2103 	    }
2104 	}
2105 	TelEngine::destruct(params);
2106     }
2107     NamedString* linksetId = nl->getParam(lookup(ID,s_linksetInfo));
2108     NamedString* typeStr = nl->getParam(lookup(TYPE,s_linksetInfo));
2109     if (TelEngine::null(linksetId) || TelEngine::null(typeStr)) {
2110 	TelEngine::destruct(parts);
2111 	TelEngine::destruct(nl);
2112 	return 0;
2113     }
2114     TelEngine::destruct(parts);
2115     if (!nl->getParam(lookup(ALARMS_COUNT,s_linksetInfo,"")))
2116 	nl->setParam(lookup(ALARMS_COUNT,s_linksetInfo,""),"0");
2117     return nl;
2118 }
2119 
2120 /**
2121  * TrunkInfo
2122  */
2123 // reset trunk information
discard()2124 void TrunkInfo::discard()
2125 {
2126     DDebug(&__plugin,DebugAll,"TrunkInfo::discard() [%p] - dropping cached data",this);
2127     for (ObjList* o = m_table.skipNull(); o; o = o->skipNext()) {
2128 	NamedList* nl = static_cast<NamedList*>(o->get());
2129 	nl->setParam(lookup(TYPE,s_trunkInfo,""),"");
2130 	nl->setParam(lookup(CIRCUITS,s_trunkInfo,""),"0");
2131 	nl->setParam(lookup(CALLS,s_trunkInfo,""),"0");
2132 	nl->setParam(lookup(LOCKED,s_trunkInfo,""),"0");
2133 	nl->setParam(lookup(IDLE,s_trunkInfo,""),"0");
2134     }
2135     m_reload = true;
2136 }
2137 
2138 // parse and load trunk information
load()2139 bool TrunkInfo::load()
2140 {
2141     DDebug(&__plugin,DebugAll,"TrunkInfo::load() [%p] - loading data",this);
2142     Message m("engine.command");
2143     m.addParam("partial","status sig ");
2144     m.addParam("partline","status sig");
2145     if (!Engine::dispatch(m))
2146 	return false;
2147     String& status = m.retValue();
2148     if (TelEngine::null(status))
2149 	return false;
2150 
2151     Lock l(this);
2152     ObjList* trunks = status.split('\t');
2153     for (ObjList* o = trunks->skipNull(); o; o = o->skipNext()) {
2154 	String* trunk = static_cast<String*>(o->get());
2155 	if ((*trunk) == "links" || (*trunk) == "ifaces")
2156 	    continue;
2157 	Message msg("engine.status");
2158 	msg.addParam("module",String("sig " + *trunk));
2159 	Engine::dispatch(msg);
2160 	String& trunkInfo = msg.retValue();
2161 	if (trunkInfo.null())
2162 	    continue;
2163 	NamedList* nl = static_cast<NamedList*>(m_table[*trunk]);
2164 	if (nl)
2165 	    parseTrunkInfo(trunkInfo,*trunk,nl);
2166 	else {
2167 	    nl = parseTrunkInfo(trunkInfo,*trunk);
2168 	    if (nl)
2169 		m_table.append(nl);
2170 	}
2171     }
2172     TelEngine::destruct(trunks);
2173     // update the expire time
2174     updateExpire();
2175     return true;
2176 }
2177 
2178 // parse the information for a single trunk
parseTrunkInfo(String & info,const String & trunk,NamedList * infoFill)2179 NamedList* TrunkInfo::parseTrunkInfo(String& info, const String& trunk, NamedList* infoFill)
2180 {
2181     cutNewLine(info);
2182     DDebug(&__plugin,DebugAll,"TrunkInfo::parseTrunkInfo(info='%s',trunk='%s', infoFill='%p') [%p]",info.c_str(),trunk.c_str(),infoFill,this);
2183     NamedList* nl = (infoFill ? infoFill : new NamedList(trunk));
2184 
2185     ObjList* parts = info.split(';',false);
2186     for (ObjList* obj = parts->skipNull(); obj; obj = obj->skipNext()) {
2187 	String* infoPart = static_cast<String*>(obj->get());
2188 	if (TelEngine::null(infoPart))
2189 	    continue;
2190 	ObjList* params = infoPart->split(',',false);
2191 	for (ObjList* o = params->skipNull(); o; o = o->skipNext()) {
2192 	    String* param = static_cast<String*>(o->get());
2193 	    int pos = param->find("=");
2194 	    if (pos < 0) {
2195 		TelEngine::destruct(params);
2196 		continue;
2197 	    }
2198 	    String nameParam = param->substr(0,pos);
2199 	    String valParam = param->substr(pos + 1);
2200 
2201 	    int type = lookup(nameParam,s_trunkStatus,0);
2202 	    if (type > 0)
2203 		nl->setParam(lookup(type,s_trunkInfo,""),valParam);
2204 	}
2205 	TelEngine::destruct(params);
2206     }
2207     // check that it's indeed a trunk
2208     NamedString* trunkId = nl->getParam(lookup(ID,s_trunkInfo));
2209     if (TelEngine::null(trunkId)) {
2210 	TelEngine::destruct(parts);
2211 	TelEngine::destruct(nl);
2212 	return 0;
2213     }
2214     TelEngine::destruct(parts);
2215     if (!nl->getParam(lookup(ALARMS_COUNT,s_trunkInfo,"")))
2216 	nl->setParam(lookup(ALARMS_COUNT,s_trunkInfo,""),"0");
2217     return nl;
2218 }
2219 
2220 /**
2221  * AccountsInfo
2222  */
2223 // parse and store accounts information
load()2224 bool AccountsInfo::load()
2225 {
2226     DDebug(&__plugin,DebugAll,"AccountsInfo::load() [%p] - loading data",this);
2227     String modules[] = {"sip","h323","iax","jabberclient"};
2228 
2229     int i = 0;
2230     while (i < 4) {
2231 	Message m("engine.status");
2232 	m.setParam("module",modules[i] + " accounts");
2233 	Engine::dispatch(m);
2234 	String& status = m.retValue();
2235 	i++;
2236 	if (TelEngine::null(status))
2237 	    continue;
2238 
2239 	cutNewLine(status);
2240 	//find protocol
2241 	String protoParam = "protocol=";
2242 	int pos = status.find(protoParam);
2243 	if (pos < 0)
2244 	    continue;
2245 	int auxPos = status.find(",",pos);
2246 	if (auxPos < pos + (int)protoParam.length())
2247 	    continue;
2248 	String proto = status.substr(pos + protoParam.length(),auxPos - (pos + protoParam.length()));
2249 
2250 	pos = status.rfind(';');
2251 	if (pos < 0)
2252 	    continue;
2253 	status = status.substr(pos + 1);
2254 	Lock l(this);
2255 	ObjList* accounts = status.split(',',false);
2256 	for (ObjList* o = accounts->skipNull(); o; o = o->skipNext()) {
2257 	    String* account = static_cast<String*>(o->get());
2258 	    int pos1 = account->find("=");
2259 	    int pos2 = account->find("|");
2260 	    if (pos1 < 0 || pos2 < 0)
2261 		continue;
2262 	    String name = account->substr(0,pos1).uriUnescape();
2263 	    String username = account->substr(pos1 + 1,pos2 - pos1 -1).uriUnescape();
2264 	    String status = account->substr(pos2 + 1);
2265 
2266 	    if (name.null())
2267 		continue;
2268 	    NamedList* nl = new NamedList("");
2269 	    nl->setParam(lookup(ID,s_accountInfo,""),name);
2270 	    nl->setParam(lookup(USERNAME,s_accountInfo,""),username);
2271 	    nl->setParam(lookup(STATUS,s_accountInfo,""),status);
2272 	    nl->setParam(lookup(PROTO,s_accountInfo,""),proto);
2273 	    m_table.append(nl);
2274 	}
2275 
2276 	TelEngine::destruct(accounts);
2277 	l.drop();
2278     }
2279     updateExpire();
2280     return true;
2281 }
2282 
2283 /**
2284   * EngineInfo - engine status information cache
2285   */
2286 // reimplemented from Cache; obtain the result for a engine query
getInfo(const String query,unsigned int index,TokenDict * dict)2287 String EngineInfo::getInfo(const String query, unsigned int index, TokenDict* dict)
2288 {
2289     DDebug(&__plugin,DebugAll,"EngineInfo::getInfo(%s %d) [%p]",query.c_str(),index,this);
2290 
2291     // if we have data, check if it is still valid
2292     if (isExpired())
2293 	discard();
2294     else
2295 	// if the data has not yet expired, update the expire time
2296 	updateExpire();
2297 
2298     String retStr;
2299     // if the is no data available, obtain it from an engine.status message
2300     if (m_reload && !load())
2301 	return retStr;
2302 
2303     Lock l(this);
2304     int type = lookup(query,s_engineQuery,0);
2305     if (!type)
2306 	return retStr;
2307 
2308     if (index > 1)
2309 	return retStr;
2310     NamedList* nl = static_cast<NamedList*>(m_table[index]);
2311     if (!nl)
2312 	return retStr;
2313     if (type == ENGINE_STATE)
2314 	return s_nodeState;
2315     retStr = nl->getValue(query,"");
2316     if (retStr.null())
2317 	retStr = "no info";
2318     return retStr;
2319 }
2320 
2321 // load data into the cache
load()2322 bool EngineInfo::load()
2323 {
2324     DDebug(&__plugin,DebugInfo,"EngineInfo::load() [%p] - loading data",this);
2325     // emit an engine.status message
2326     Message m("engine.status");
2327     m.setParam("module","engine");
2328     Engine::dispatch(m);
2329     String& status = m.retValue();
2330     if (TelEngine::null(status))
2331 	return false;
2332     cutNewLine(status);
2333 
2334     Lock l(this);
2335     m_table.clear();
2336     NamedList* nl = new NamedList("");
2337     ObjList* params = status.split(';');
2338     for(ObjList* o = params->skipNull(); o; o = o->skipNext()) {
2339 	String* strVal = static_cast<String*>(o->get());
2340 	ObjList* l = strVal->split(',');
2341 	for (ObjList* j = l->skipNull(); j; j = j->skipNext()) {
2342 	    String* str = static_cast<String*>(j->get());
2343 	    int pos = str->find("=");
2344 	    if (pos < 0)
2345 		continue;
2346 	    String param = str->substr(0,pos);
2347 	    String value = str->substr(pos + 1);
2348 	    int type = lookup(param,s_engineInfo,0);
2349 	    if (!type)
2350 		continue;
2351 	    nl->setParam(lookup(type,s_engineQuery,""),value);
2352 	}
2353 	TelEngine::destruct(l);
2354     }
2355     m_table.append(nl);
2356     TelEngine::destruct(params);
2357     updateExpire();
2358     return true;
2359 }
2360 
2361 /**
2362   * ModuleInfo
2363   */
2364 // load data into the cache
load()2365 bool ModuleInfo::load()
2366 {
2367     DDebug(&__plugin,DebugInfo,"ModuleInfo::load() [%p] - loading data",this);
2368     // emit an engine.status message
2369     Message m("engine.status");
2370     m.setParam("details",String::boolText(false));
2371     Engine::dispatch(m);
2372     String& status = m.retValue();
2373     if (TelEngine::null(status))
2374 	return false;
2375 
2376     Lock l(this);
2377     m_table.clear();
2378 
2379     // split the info into lines
2380     ObjList* lines = status.split('\n',false);
2381     for (ObjList* o = lines->skipNull(); o; o = o->skipNext()) {
2382 	String* line = static_cast<String*>(o->get());
2383 	if (!line)
2384 	    continue;
2385 	cutNewLine(*line);
2386 	ObjList* parts = line->split(';');
2387 	NamedList* nl = new NamedList("");
2388 	for (ObjList* p = parts->skipNull(); p; p = p->skipNext()) {
2389 	    String* str = static_cast<String*>(p->get());
2390 	    ObjList* paramVal = str->split(',');
2391 	    String info = "";
2392 	    for (ObjList* l = paramVal->skipNull(); l; l = l->skipNext()) {
2393 	    	String* pair = static_cast<String*>(l->get());
2394 	    	int pos = pair->find("=");
2395 		if (pos < 0)
2396 		    continue;
2397 		String param = pair->substr(0,pos);
2398 		String value = pair->substr(pos + 1);
2399 		int type = lookup(param,s_moduleInfo,0);
2400 		if (!type) {
2401 		    info += (info.null() ? pair : String("," + *pair));
2402 		    continue;
2403 		}
2404 		nl->setParam(lookup(type,s_moduleQuery,""),value);
2405 	    }
2406 	    nl->setParam(lookup(MODULE_INFO,s_moduleQuery,""),info.uriUnescape());
2407 	    TelEngine::destruct(paramVal);
2408 	}
2409 	TelEngine::destruct(parts);
2410 	if ( String("engine") == nl->getValue(lookup(MODULE_NAME,s_moduleQuery,""),"")) {
2411 	    TelEngine::destruct(nl);
2412 	    continue;
2413 	}
2414         m_table.append(nl);
2415 
2416     }
2417     TelEngine::destruct(lines);
2418     updateExpire();
2419     return true;
2420 }
2421 
2422 /**
2423  *  DatabaseAccount - an object holding information about a single monitored database account
2424  */
2425 // configure a database account for monitoring and initialize internal data
DatabaseAccount(const NamedList * cfg)2426 DatabaseAccount::DatabaseAccount(const NamedList* cfg)
2427     : m_resetTime(0), m_resetInterval(3600),
2428     m_isCurrent(true)
2429 {
2430     if (cfg) {
2431 	Debug(&__plugin,DebugAll,"DatabaseAccount('%s') created for monitoring [%p]",cfg->c_str(),this);
2432 	m_name = cfg->c_str();
2433 	m_alarms = 0;
2434 	for (int i = 0; i < ExecTime; i++) {
2435 	    m_dbCounters[i] = 0;
2436 	    m_prevDbCounters[i] = 0;
2437 	}
2438 	for (int i = 0; i <= NoConnAlrmCount - TooManyAlrmCount; i++)
2439 	    m_alarmCounters[i] = 0;
2440 	updateConfig(cfg);
2441 	m_resetTime = Time::secNow() + m_resetInterval;
2442     }
2443     m_isCurrent = true;
2444 }
2445 
2446 // update internal data from a message received from the sql modules
update(const NamedList & info)2447 void DatabaseAccount::update(const NamedList& info)
2448 {
2449     XDebug(&__plugin,DebugAll,"DatabaseAccount::update() [%p]",this);
2450     for (unsigned int i = 0; i < info.count(); i++) {
2451 	NamedString* ns = info.getParam(i);
2452 	if (!(ns && *ns))
2453 	    continue;
2454 	int type = lookup(ns->name(),s_dbAccountInfo,-1);
2455 	if (type < 0)
2456 	    continue;
2457 
2458 	u_int16_t alarm = TOTAL_ALARM << (type);
2459 	if (type <= TIME_IDX) {
2460 	    m_dbCounters[type] = ns->toInteger();
2461 	    if ((type != TIME_IDX) && (m_dbCounters[type] - m_prevDbCounters[type] >= m_thresholds[type]) && ((m_alarms & alarm) == 0)) {
2462 		m_alarms |= alarm;
2463 		m_alarmCounters[type]++;
2464 		__plugin.sendTrap(lookup(TooManyAlrm + type,s_dbAccountQueries,""),toString(),index());
2465 	    }
2466 	}
2467 	if (type == CONN_IDX) {
2468 	    if (!ns->toBoolean()) {
2469 		if ((m_alarms & alarm) == 0) {
2470 		    m_alarmCounters[CONN_IDX]++;
2471 		    m_alarms |= alarm;
2472 		    __plugin.sendTrap(lookup(NoConnAlrm,s_dbAccountQueries,""),toString(),index());
2473 		}
2474 	    }
2475 	    else
2476 		m_alarms &= ~alarm;
2477 	}
2478     }
2479     // wait to gather all necessary data to compute average time
2480     double execTime = m_dbCounters[TIME_IDX] - m_prevDbCounters[TIME_IDX];
2481     double queriesNo = (m_dbCounters[TOTAL_IDX] - m_prevDbCounters[TOTAL_IDX]) - (m_dbCounters[FAILED_IDX] - m_prevDbCounters[FAILED_IDX]);
2482     if (queriesNo > 0 && (execTime / queriesNo / 1000)  >= m_thresholds[TIME_IDX]) {
2483 	if ((m_alarms & EXEC_ALARM) == 0)  {
2484 	    m_alarms |= EXEC_ALARM;
2485 	    m_alarmCounters[TIME_IDX]++;
2486 	    __plugin.sendTrap(lookup(ExecTooLongAlrm,s_dbAccountQueries,""),toString(),index());
2487 	}
2488     }
2489     else
2490 	m_alarms &= ~EXEC_ALARM;
2491 }
2492 
updateConfig(const NamedList * cfg)2493 void DatabaseAccount::updateConfig(const NamedList* cfg)
2494 {
2495     if (!cfg)
2496 	return;
2497     for (int i = 0; i <= MaxExecTime - MaxQueries; i++)
2498 	m_thresholds[i] = cfg->getIntValue(lookup(MaxQueries + i,s_dbAccountInfo,""),0);
2499     m_resetInterval = cfg->getIntValue("notiftime",3600);
2500     if (m_resetTime > Time::secNow() + m_resetInterval)
2501 	m_resetTime = Time::secNow() + m_resetInterval;
2502     m_isCurrent = true;
2503 }
2504 
2505 // obtain information from this entry
getInfo(unsigned int query)2506 const String DatabaseAccount::getInfo(unsigned int query)
2507 {
2508     DDebug(&__plugin,DebugAll,"DatabaseAccount::getInfo('%s') [%p]",lookup(query,s_dbAccountQueries,""),this);
2509     String ret = "";
2510     double execTime = 0;
2511     double queriesNo = 0;
2512     switch (query) {
2513 	case QueriesCount:
2514 	case FailedCount:
2515 	case ErrorsCount:
2516 	    ret << m_dbCounters[query - 1] - m_prevDbCounters[query - 1];
2517 	    break;
2518 	case ExecTime:
2519 	    execTime = m_dbCounters[TIME_IDX] - m_prevDbCounters[TIME_IDX];
2520 	    queriesNo = (m_dbCounters[TOTAL_IDX] - m_prevDbCounters[TOTAL_IDX]) - (m_dbCounters[FAILED_IDX] - m_prevDbCounters[FAILED_IDX]);
2521 	    if (queriesNo > 0)
2522 		ret << (unsigned int) (execTime / queriesNo / 1000);
2523 	    else
2524 		ret << 0;
2525 	    break;
2526 	case TooManyAlrmCount:
2527 	case TooManyFailedAlrmCount:
2528 	case TooManyErrorAlrmCount:
2529 	case ExecTooLongAlrmCount:
2530 	case NoConnAlrmCount:
2531 	    ret << m_alarmCounters[query - TooManyAlrmCount];
2532 	    break;
2533 	case MaxQueries:
2534 	case MaxFailedQueries:
2535 	case MaxErrorQueries:
2536 	case MaxExecTime:
2537 	    ret << m_thresholds[query - MaxQueries];
2538 	    break;
2539 	case AccountName:
2540 	    ret = m_name;
2541 	    break;
2542 	case AccountIndex:
2543 	    ret = index();
2544 	    break;
2545 	default:
2546 	    break;
2547     }
2548     return ret;
2549 }
2550 
reset()2551 void DatabaseAccount::reset()
2552 {
2553     if (Time::secNow() < m_resetTime)
2554 	return;
2555 
2556     __plugin.sendTrap(lookup(QueriesCount,s_dbAccountQueries,""),String(m_dbCounters[TOTAL_IDX] - m_prevDbCounters[TOTAL_IDX]),index());
2557     __plugin.sendTrap(lookup(FailedCount,s_dbAccountQueries,""),String(m_dbCounters[FAILED_IDX] - m_prevDbCounters[FAILED_IDX]),index());
2558     __plugin.sendTrap(lookup(ErrorsCount,s_dbAccountQueries,""),String(m_dbCounters[ERROR_IDX] - m_prevDbCounters[ERROR_IDX]),index());
2559 
2560     double execTime = m_dbCounters[TIME_IDX] - m_prevDbCounters[TIME_IDX];
2561     double queriesNo = (m_dbCounters[TOTAL_IDX] - m_prevDbCounters[TOTAL_IDX]) - (m_dbCounters[FAILED_IDX] - m_prevDbCounters[FAILED_IDX]);
2562     unsigned int time = 0;
2563     if (queriesNo > 0)
2564 	time = (unsigned int) (execTime / queriesNo / 1000);
2565     __plugin.sendTrap(lookup(ExecTime,s_dbAccountQueries,""),String(time),index());
2566 
2567     m_alarms = 0;
2568     for (unsigned int i = 0; i < CONN_IDX; i++)
2569 	m_prevDbCounters[i] = m_dbCounters[i];
2570 
2571     m_resetTime = Time::secNow() + m_resetInterval;
2572 }
2573 
2574 /**
2575  * DatabaseInfo
2576  */
2577 // parse database status information and store it for all sql modules
load()2578 bool DatabaseInfo::load()
2579 {
2580     DDebug(&__plugin,DebugInfo,"DatabaseInfo::load() [%p] - loading data",this);
2581     String modules[] = {"pgsqldb", "mysqldb"};
2582     unsigned int i = 0;
2583 
2584     for (int i = 0; i < FailedConns; i++)
2585 	m_connData[i] = 0;
2586 
2587     while (i < 2) {
2588 	Message msg("engine.status");
2589 	msg.addParam("module",modules[i]);
2590 	msg.addParam("details","false");
2591 	Engine::dispatch(msg);
2592 	String& status = msg.retValue();
2593 	if (!TelEngine::null(status))  {
2594 	    cutNewLine(status);
2595 	    /* status example: name=mysqldb,type=database,format=Total|Failed|Errors|AvgExecTime;conns=1,failed=1 */
2596 	    int pos = status.rfind(';');
2597 	    if (pos < 0)
2598 		continue;
2599 	    String connInfo = status.substr(pos + 1);
2600 	    if (connInfo.null())
2601 		continue;
2602 	    ObjList* l = connInfo.split(',');
2603 	    for (ObjList* j = l->skipNull(); j; j = j->skipNext()) {
2604 		String* str = static_cast<String*>(j->get());
2605 		pos = str->find("=");
2606 		if (pos < 0)
2607 		    continue;
2608 		String param = str->substr(0,pos);
2609 		String value = str->substr(pos + 1);
2610 		int type = lookup(param,s_databaseInfo,0);
2611 		if (!type)
2612 		    continue;
2613 		m_connData[type - 1] += value.toInteger();
2614 	    }
2615 	    TelEngine::destruct(l);
2616 	}
2617 	i++;
2618     }
2619     updateExpire();
2620     return true;
2621 }
2622 
2623 // get the answer to a query for the entry with the given index
getInfo(const String & query,unsigned int & index,TokenDict * dict)2624 String DatabaseInfo::getInfo(const String& query, unsigned int& index, TokenDict* dict)
2625 {
2626     DDebug(&__plugin,DebugAll,"DatabaseInfo::getInfo(query='%s',index='%d',dict='%p') [%p]",query.c_str(),index,dict,this);
2627     // handle a query about an entry in the table
2628     Lock l(this);
2629     int type = lookup(query,s_dbAccountQueries,0);
2630     if (type) {
2631 	if (index == 0 || index > m_table.count())
2632 	    return "";
2633 	DatabaseAccount* acc = static_cast<DatabaseAccount*>(m_table[index - 1]);
2634 	if (!acc)
2635 	    return "";
2636 	return acc->getInfo(type);
2637     }
2638     if (!isExpired())
2639 	// if the data has not yet expired, update the expire time
2640 	updateExpire();
2641     // if the is no data available, obtain it from an engine.status message
2642     if (m_reload && !load())
2643 	return "";
2644     // handle queries about connections
2645     type = lookup(query,s_databaseQuery,0);
2646     switch (type) {
2647 	case Accounts:
2648 	    return String(m_table.count());
2649 	case Connections:
2650 	case FailedConns:
2651 	    return String(m_connData[type - 1]);
2652 	default:
2653 	    break;
2654     }
2655     return "";
2656 }
2657 
reset()2658 void DatabaseInfo::reset()
2659 {
2660     Lock l(this);
2661     for (ObjList* o = m_table.skipNull(); o; o = o->skipNext()) {
2662 	DatabaseAccount* acc = static_cast<DatabaseAccount*>(o->get());
2663 	acc->reset();
2664     }
2665 }
2666 
2667 // create a new DatabaseAccount object for monitoring a database
addDatabase(NamedList * cfg)2668 void DatabaseInfo::addDatabase(NamedList* cfg)
2669 {
2670     if (!cfg || !m_monitor)
2671 	return;
2672     DDebug(&__plugin,DebugInfo,"DatabaseInfo::addDatabase('%s') [%p]",cfg->c_str(),this);
2673     lock();
2674     DatabaseAccount* acc = static_cast<DatabaseAccount*>(m_table[*cfg]);
2675     if (!acc) {
2676 	acc = new DatabaseAccount(cfg);;
2677         m_table.append(acc);
2678 	acc->setIndex(m_table.count());
2679     }
2680     else
2681 	acc->updateConfig(cfg);
2682     unlock();
2683 }
2684 
updateDatabaseAccounts()2685 void DatabaseInfo::updateDatabaseAccounts()
2686 {
2687     lock();
2688     bool deletedRoute = true;
2689     while (deletedRoute) {
2690 	deletedRoute = false;
2691 	for (ObjList* o = m_table.skipNull(); o; o = o->skipNext()) {
2692 	    DatabaseAccount* acc = static_cast<DatabaseAccount*>(o->get());
2693 	    if (!acc->isCurrent()) {
2694 		DDebug(__plugin.name(),DebugAll,"DatabaseInfo::updateDatabaseAccounts() - removed database account '%s' from monitoring",
2695 			    acc->toString().c_str());
2696 		m_table.remove(acc);
2697 		deletedRoute = true;
2698 	    }
2699 	}
2700     }
2701     unsigned int index = 1;
2702     for (ObjList* o = m_table.skipNull(); o; o = o->skipNext()) {
2703 	DatabaseAccount* acc = static_cast<DatabaseAccount*>(o->get());
2704 	acc->setIsCurrent(false);
2705 	acc->setIndex(index);
2706 	index++;
2707     }
2708     unlock();
2709 }
2710 
2711 //update the information for a given account
update(const Message & msg)2712 void DatabaseInfo::update(const Message& msg)
2713 {
2714     XDebug(&__plugin,DebugInfo,"DatabaseInfo::update()");
2715     int count = msg.getIntValue("count",0);
2716     for (int i = 0; i < count; i++) {
2717 	String str = s_dbParam;
2718 	str << i;
2719 	String acc = msg.getValue(str);
2720 	DatabaseAccount* dbAccount = static_cast<DatabaseAccount*>(m_table[acc]);
2721 	if (!dbAccount)
2722 	    continue;
2723 	NamedList nl(acc);
2724 	str = s_totalParam;
2725 	str << i;
2726 	nl.setParam("total",msg.getValue(str));
2727 	str = s_failedParam;
2728 	str << i;
2729 	nl.setParam("failed",msg.getValue(str));
2730 	str = s_errorParam;
2731 	str << i;
2732 	nl.setParam("errorred",msg.getValue(str));
2733 	str = s_hasConnParam;
2734 	str << i;
2735 	nl.setParam("hasconn",msg.getValue(str));
2736 	str = s_timeParam;
2737 	str << i;
2738 	nl.setParam("querytime",msg.getValue(str));
2739 	dbAccount->update(nl);
2740     }
2741 }
2742 
2743 /**
2744  * RTPEntry
2745  */
RTPEntry(String rtpDirection)2746 RTPEntry::RTPEntry(String rtpDirection)
2747     : m_rtpDir(rtpDirection), m_index(0),
2748       m_isCurrent(true)
2749 {
2750     Debug(&__plugin,DebugAll,"RTPEntry '%s' created [%p]",rtpDirection.c_str(),this);
2751     reset();
2752 }
2753 
~RTPEntry()2754 RTPEntry::~RTPEntry()
2755 {
2756     Debug(&__plugin,DebugAll,"RTPEntry '%s' destroyed [%p]",m_rtpDir.c_str(),this);
2757 }
2758 
2759 // update the RTP info from the given list
update(const NamedList & nl)2760 void RTPEntry::update(const NamedList& nl)
2761 {
2762     DDebug(&__plugin,DebugAll,"RTPEntry::update() name='%s' [%p]",m_rtpDir.c_str(),this);
2763     for (unsigned int i = 0; i < nl.count(); i++) {
2764 	NamedString* n = nl.getParam(i);
2765 	if (!n)
2766 	    continue;
2767 	int type = lookup(n->name(),s_rtpInfo,0);
2768 	if (!type || type < NoAudio)
2769 	    continue;
2770 	m_counters[type - NoAudio] += (*n).toInteger();
2771     }
2772 }
2773 
2774 // reset counters
reset()2775 void RTPEntry::reset()
2776 {
2777     DDebug(&__plugin,DebugAll,"RTPEntry::reset() '%s' [%p]",m_rtpDir.c_str(),this);
2778     for (int i = 0; i < WrongSSRC - Direction; i++)
2779 	m_counters[i] = 0;
2780 }
2781 
2782 // the the answer to a query about this RTP direction
getInfo(unsigned int query)2783 String RTPEntry::getInfo(unsigned int query)
2784 {
2785     DDebug(&__plugin,DebugAll,"RTPEntry::getInfo('%s') '%s' [%p]",lookup(query,s_rtpQuery,""),m_rtpDir.c_str(),this);
2786     String retStr = "";
2787     switch (query) {
2788 	case Direction:
2789 	    retStr << m_rtpDir;
2790 	    break;
2791 	case Index:
2792 	    retStr << m_index;
2793 	    break;
2794 	case NoAudio:
2795 	case LostAudio:
2796 	case PktsLost:
2797 	case SyncLost:
2798 	case SeqLost:
2799 	case WrongSRC:
2800 	case WrongSSRC:
2801 	    retStr << m_counters[query - NoAudio];
2802 	    break;
2803 	default:
2804 	    break;
2805     }
2806     return retStr;
2807 }
2808 
2809 /**
2810  *  RTPTable
2811  */
RTPTable(const NamedList * cfg)2812 RTPTable::RTPTable(const NamedList* cfg)
2813     : m_rtpMtx(false,"Monitor::rtpInfo"),
2814       m_resetInterval(3600), m_monitor(false)
2815 {
2816     Debug(&__plugin,DebugAll,"RTPTable created [%p]",this);
2817     // build RTPEntry objects for monitoring RTP directions if monitoring is enabled
2818     reconfigure(cfg);
2819 }
2820 
reconfigure(const NamedList * cfg)2821 void RTPTable::reconfigure(const NamedList* cfg)
2822 {
2823     if (!cfg)
2824 	return;
2825     m_monitor = cfg->getBoolValue("monitor",false);
2826     m_resetInterval = cfg->getIntValue("reset_interval",3600);
2827 
2828     m_rtpMtx.lock();
2829     if (!m_monitor)
2830 	m_rtpEntries.clear();
2831     String directions = cfg->getValue("rtp_directions","");
2832     Debug(&__plugin,DebugAll,"RTPTable [%p] configured with directions='%s',resetTime=" FMT64U,this,directions.c_str(),m_resetInterval);
2833     if (m_monitor) {
2834 	ObjList* l = directions.split(',');
2835 	for (ObjList* o = l->skipNull(); o; o = o->skipNext()) {
2836 	    String* str = static_cast<String*>(o->get());
2837 	    RTPEntry* entry = static_cast<RTPEntry*>(m_rtpEntries[*str]);
2838 	    if (!entry) {
2839 		entry = new RTPEntry(*str);
2840 		m_rtpEntries.append(entry);
2841 		entry->setIndex(m_rtpEntries.count());
2842 	    }
2843 	    else
2844 		entry->setIsCurrent(true);
2845 	}
2846 	TelEngine::destruct(l);
2847     }
2848 
2849     bool deletedDir = true;
2850     while (deletedDir) {
2851 	deletedDir = false;
2852 	for (ObjList* o = m_rtpEntries.skipNull(); o; o = o->skipNext()) {
2853 	    RTPEntry* entry = static_cast<RTPEntry*>(o->get());
2854 	    if (!entry->isCurrent()) {
2855 		DDebug(__plugin.name(),DebugAll,"RTPTable::reconfigure() - removed direction '%s' from monitoring",entry->toString().c_str());
2856 		m_rtpEntries.remove(entry);
2857 		deletedDir = true;
2858 	    }
2859 	}
2860     }
2861     unsigned int index = 1;
2862     for (ObjList* o = m_rtpEntries.skipNull(); o; o = o->skipNext()) {
2863 	RTPEntry* entry = static_cast<RTPEntry*>(o->get());
2864 	entry->setIsCurrent(false);
2865 	entry->setIndex(index);
2866 	index++;
2867     }
2868 
2869     m_rtpMtx.unlock();
2870     m_resetTime = Time::secNow() + m_resetInterval;
2871 }
2872 
2873 // update an entry
update(Message & msg)2874 void RTPTable::update(Message& msg)
2875 {
2876     XDebug(&__plugin,DebugAll,"RTPTable::update() [%p]",this);
2877     String dir = lookup(RTPEntry::Direction,RTPEntry::s_rtpInfo,"");
2878     if (dir.null())
2879 	dir = "remoteip";
2880     String rtpDir = msg.getValue(dir,"");
2881     if (rtpDir.null())
2882 	return;
2883     m_rtpMtx.lock();
2884     RTPEntry* entry = static_cast<RTPEntry*>(m_rtpEntries[rtpDir]);
2885     if (entry)
2886 	entry->update(msg);
2887     m_rtpMtx.unlock();
2888 }
2889 
getInfo(const String & query,const unsigned int & index)2890 String RTPTable::getInfo(const String& query, const unsigned int& index)
2891 {
2892     DDebug(&__plugin,DebugAll,"RTPTable::getInfo(query='%s',index='%u') [%p]",query.c_str(),index,this);
2893     String retStr = "";
2894     int type = lookup(query,s_rtpQuery,0);
2895     if (!type)
2896 	return retStr;
2897     if (type == RTPEntry::Count)
2898 	retStr << m_rtpEntries.count();
2899     else if (index > 0 && index <= m_rtpEntries.count()) {
2900 	m_rtpMtx.lock();
2901 	RTPEntry* entry = static_cast<RTPEntry*>(m_rtpEntries[index - 1]);
2902 	if (entry)
2903 	    retStr << entry->getInfo(type);
2904 	m_rtpMtx.unlock();
2905     }
2906     return retStr;
2907 }
2908 
2909 // reset information
reset()2910 void RTPTable::reset()
2911 {
2912     XDebug(&__plugin,DebugAll,"RTPTable::reset() [%p]",this);
2913     m_rtpMtx.lock();
2914     for (ObjList* o = m_rtpEntries.skipNull(); o; o = o->skipNext()) {
2915 	RTPEntry* e = static_cast<RTPEntry*>(o->get());
2916 	e->reset();
2917     }
2918     m_rtpMtx.unlock();
2919     m_resetTime = Time::secNow() + m_resetInterval;
2920 }
2921 
2922 /**
2923   * CallRouteQoS
2924   */
CallRouteQoS(const String direction,const NamedList * cfg)2925 CallRouteQoS::CallRouteQoS(const String direction, const NamedList* cfg)
2926     : m_routeName(direction)
2927 {
2928     Debug(&__plugin,DebugAll,"CallRouteQoS [%p] created for route '%s',cfg='%p'",this,direction.c_str(),cfg);
2929     for (int i = 0; i < NO_CAUSE - HANGUP; i++) {
2930 	m_callCounters[i] = 0;
2931 	m_callCountersAll[i] = 0;
2932     }
2933 
2934     for (int i = 0; i <= TOTAL_IDX; i++) {
2935 	m_totalCalls[i] = 0;
2936 	m_answeredCalls[i] = 0;
2937 	m_delivCalls[i] = 0;
2938     }
2939 
2940     for (int i = 0; i <= NER_LOW_ALL; i++)
2941 	m_alarmCounters[i] = 0;
2942 
2943     m_alarms = 0;
2944     m_overallAlarms = 0;
2945 
2946     m_alarmsSent = 0;
2947     m_overallAlarmsSent = 0;
2948 
2949     m_minCalls = 1;
2950     m_minASR = m_maxASR = m_minNER = -1;
2951     if (cfg)
2952 	updateConfig(cfg);
2953     m_index = 0;
2954 }
2955 
~CallRouteQoS()2956 CallRouteQoS::~CallRouteQoS()
2957 {
2958     Debug(&__plugin,DebugAll,"CallRouteQoS [%p] destroyed",this);
2959 }
2960 
updateConfig(const NamedList * cfg)2961 void CallRouteQoS::updateConfig(const NamedList* cfg)
2962 {
2963     if (!cfg)
2964 	return;
2965     m_minCalls = cfg->getIntValue("mincalls",m_minCalls);
2966     m_minASR = cfg->getIntValue("minASR",m_minASR);
2967     if (m_minASR > 100 || m_minASR < -1) {
2968 	Debug(&__plugin,DebugNote,"CallRouteQoS::updateConfig() - route '%s': configured minASR is not in the -1..100 interval, "
2969 		"defaulting to -1",m_routeName.c_str());
2970 	m_minASR = -1;
2971     }
2972     m_maxASR = cfg->getIntValue("maxASR",m_maxASR);
2973     if (m_maxASR > 100 || m_maxASR < -1) {
2974 	Debug(&__plugin,DebugNote,"CallRouteQoS::updateConfig() - route '%s': configured maxASR is not in the -1..100 interval, "
2975 		"defaulting to -1",m_routeName.c_str());
2976 	m_maxASR = -1;
2977     }
2978     m_minNER = cfg->getIntValue("minNER",m_minNER);
2979     if (m_minNER > 100 || m_minNER < -1) {
2980 	Debug(&__plugin,DebugNote,"CallRouteQoS::updateConfig() - route '%s': configured minNER is not in the -1..100 interval, "
2981 		"defaulting to -1",m_routeName.c_str());
2982 	m_minNER = -1;
2983     }
2984     m_isCurrent = true;
2985 }
2986 
2987 // update the counters taking into account the type of the call and reason with which the call was ended
update(int type,int endReason)2988 void CallRouteQoS::update(int type, int endReason)
2989 {
2990     DDebug(&__plugin,DebugAll,"CallRouteQoS::update(callType='%d',endReason='%d') [%p] ",type,endReason,this);
2991     m_totalCalls[CURRENT_IDX]++;
2992     m_totalCalls[TOTAL_IDX]++;
2993     switch (type) {
2994 	case ANSWERED:
2995 	    m_answeredCalls[CURRENT_IDX]++;
2996 	    m_answeredCalls[TOTAL_IDX]++;
2997 	    break;
2998 	case DELIVERED:
2999 	    m_delivCalls[CURRENT_IDX]++;
3000 	    m_delivCalls[TOTAL_IDX]++;
3001 	    break;
3002 	default:
3003 	    break;
3004     }
3005     if (endReason != -1 && endReason >= HANGUP && endReason < NO_CAUSE) {
3006 	m_callCounters[endReason - HANGUP]++;
3007 	m_callCountersAll[endReason - HANGUP]++;
3008     }
3009 }
3010 
3011 // update the internal ASR/NER values and check for alarms
updateQoS()3012 void CallRouteQoS::updateQoS()
3013 {
3014     //XDebug(&__plugin,DebugAll,"CallRouteQoS::updateQoS() [%p]",this);
3015     int realASR, totalASR;
3016     if ((m_totalCalls[CURRENT_IDX] != m_totalCalls[PREVIOUS_IDX]) && (m_totalCalls[CURRENT_IDX] >= m_minCalls)) {
3017 
3018 	float currentHyst = 50.0 / m_totalCalls[CURRENT_IDX] * s_qosHysteresisFactor;
3019 	float totalHyst = 50.0 / m_totalCalls[TOTAL_IDX] * s_qosHysteresisFactor;
3020 
3021 	realASR = (int) (m_answeredCalls[CURRENT_IDX] * 100.0 / m_totalCalls[CURRENT_IDX]);
3022 	checkForAlarm(realASR,currentHyst,m_alarms,m_minASR,m_maxASR,LOW_ASR,HIGH_ASR);
3023 	m_totalCalls[PREVIOUS_IDX] = m_totalCalls[CURRENT_IDX];
3024 
3025 	totalASR = (int) (m_answeredCalls[TOTAL_IDX] * 100.0 / m_totalCalls[TOTAL_IDX]);
3026 	checkForAlarm(totalASR,totalHyst,m_overallAlarms,m_minASR,m_maxASR,LOW_ASR,HIGH_ASR);
3027 
3028 	int ner = (int) ((m_answeredCalls[CURRENT_IDX] + m_delivCalls[CURRENT_IDX]) * 100.0 / m_totalCalls[CURRENT_IDX]);
3029 	checkForAlarm(ner,currentHyst,m_alarms,m_minNER,-1,LOW_NER);
3030 
3031 	ner = (int) ((m_answeredCalls[TOTAL_IDX] + m_delivCalls[TOTAL_IDX]) * 100.0 / m_totalCalls[TOTAL_IDX]);
3032 	checkForAlarm(ner,totalHyst,m_overallAlarms,m_minNER,-1,LOW_NER);
3033     }
3034 }
3035 
3036 // reset counters
reset()3037 void CallRouteQoS::reset()
3038 {
3039     DDebug(&__plugin,DebugInfo,"CallRoute::reset() [%p]",this);
3040     m_totalCalls[CURRENT_IDX] = m_totalCalls[PREVIOUS_IDX] = 0;
3041     m_answeredCalls[CURRENT_IDX] = m_answeredCalls[PREVIOUS_IDX] = 0;
3042     m_delivCalls[CURRENT_IDX] = m_delivCalls[PREVIOUS_IDX] = 0;
3043     m_alarms = 0;
3044     m_alarmsSent = 0;
3045     m_alarmCounters[ASR_LOW] = m_alarmCounters[ASR_HIGH] = m_alarmCounters[NER_LOW] = 0;
3046     for (int i = 0; i < NO_CAUSE - HANGUP; i++)
3047 	m_callCounters[i] = 0;
3048 }
3049 
3050 // check a value against a threshold and if necessary set an alarm
checkForAlarm(int & value,float hysteresis,u_int8_t & alarm,const int min,const int max,u_int8_t minAlarm,u_int8_t maxAlarm)3051 void CallRouteQoS::checkForAlarm(int& value, float hysteresis, u_int8_t& alarm, const int min,
3052 			const int max, u_int8_t minAlarm, u_int8_t maxAlarm)
3053  {
3054     if (min >= 0) {
3055 	float hystValue = (alarm & minAlarm ? value - hysteresis : value + hysteresis);
3056 	if (hystValue <= min)
3057 	    alarm |= minAlarm;
3058 	else
3059 	    alarm &= ~minAlarm;
3060     }
3061     if (max >= 0) {
3062 	float hystValue  = (alarm & maxAlarm ? value + hysteresis : value - hysteresis);
3063 	if (hystValue >= max)
3064 	    alarm |= maxAlarm;
3065 	else
3066 	    alarm &= ~maxAlarm;
3067     }
3068 }
3069 
3070 // is this entry in an alarm state?
alarm()3071 bool CallRouteQoS::alarm()
3072 {
3073     if (m_alarms || m_overallAlarms)
3074 	return true;
3075     m_alarmsSent = m_overallAlarmsSent = 0;
3076     return false;
3077 }
3078 
3079 // get the string version of the alarm and remember that we sent this alarm (avoid sending the same alarm multiple times)
alarmText()3080 const String CallRouteQoS::alarmText()
3081 {
3082     String text = "";
3083     if (m_alarms & LOW_ASR) {
3084 	if (!(m_alarmsSent & LOW_ASR)) {
3085 	    m_alarmsSent |= LOW_ASR;
3086 	    m_alarmCounters[ASR_LOW]++;
3087 	    return text = lookup(ASR_LOW,s_callQualityQueries,"");
3088 	}
3089     }
3090     else
3091 	m_alarmsSent &= ~LOW_ASR;
3092 
3093     if (m_alarms & HIGH_ASR) {
3094 	if (!(m_alarmsSent & HIGH_ASR)) {
3095 	    m_alarmsSent |= HIGH_ASR;
3096 	    m_alarmCounters[ASR_HIGH]++;
3097 	    return text = lookup(ASR_HIGH,s_callQualityQueries,"");
3098 	}
3099     }
3100     else
3101 	m_alarmsSent &= ~HIGH_ASR;
3102 
3103     if (m_alarms & LOW_NER) {
3104 	if (!(m_alarmsSent & LOW_NER)) {
3105 	    m_alarmsSent |= LOW_NER;
3106 	    m_alarmCounters[NER_LOW]++;
3107 	    return text = lookup(NER_LOW,s_callQualityQueries,"");
3108 	}
3109     }
3110     else
3111 	m_alarmsSent &= ~LOW_NER;
3112 
3113     if (m_overallAlarms & LOW_ASR) {
3114 	if (!(m_overallAlarmsSent & LOW_ASR)) {
3115 	    m_overallAlarmsSent |= LOW_ASR;
3116 	    m_alarmCounters[ASR_LOW_ALL]++;
3117 	    return text = lookup(ASR_LOW_ALL,s_callQualityQueries,"");
3118 	}
3119     }
3120     else
3121 	m_overallAlarmsSent &= ~LOW_ASR;
3122 
3123     if (m_overallAlarms & HIGH_ASR) {
3124 	if (!(m_overallAlarmsSent & HIGH_ASR)) {
3125 	    m_overallAlarmsSent |= HIGH_ASR;
3126     	    m_alarmCounters[ASR_HIGH_ALL]++;
3127 	    return text = lookup(ASR_HIGH_ALL,s_callQualityQueries,"");
3128 	}
3129     }
3130     else
3131 	m_overallAlarmsSent &= ~HIGH_ASR;
3132 
3133     if (m_overallAlarms & LOW_NER) {
3134 	if (!(m_overallAlarmsSent & LOW_NER)) {
3135 	    m_overallAlarmsSent |= LOW_NER;
3136 	    m_alarmCounters[NER_LOW_ALL]++;
3137 	    return text = lookup(NER_LOW_ALL,s_callQualityQueries,"");
3138 	}
3139     }
3140     else
3141 	m_overallAlarmsSent &= ~LOW_NER;
3142     return text;
3143 }
3144 
3145 // send periodical notification containing current ASR and NER values
sendNotifs(unsigned int index,bool rst)3146 void CallRouteQoS::sendNotifs(unsigned int index, bool rst)
3147 {
3148     DDebug(&__plugin,DebugInfo,"CallRouteQoS::sendNotifs() - route='%s' reset=%s [%p]",toString().c_str(),String::boolText(rst),this);
3149     // we dont want notifcations if we didn't have the minimum number of calls
3150     if (m_totalCalls[CURRENT_IDX] >= m_minCalls) {
3151 	NamedList nl("");
3152 	String value;
3153 	nl.addParam("index",String(index));
3154 	nl.addParam("count","4");
3155 
3156 	for (int i = 0; i < 4; i++) {
3157 	    String param = "notify.";
3158 	    param << i;
3159 	    String paramValue = "value.";
3160 	    paramValue << i;
3161 	    nl.addParam(param,lookup(ASR + i,s_callQualityQueries,""));
3162 	    get(ASR + i,value);
3163 	    nl.addParam(paramValue,value);
3164 	}
3165 	__plugin.sendTraps(nl);
3166     }
3167     if (rst)
3168 	reset();
3169 }
3170 
3171 // get the value for a query
get(int query,String & result)3172 bool CallRouteQoS::get(int query, String& result)
3173 {
3174     DDebug(&__plugin,DebugInfo,"CallRouteQoS::get(query='%s') [%p]",lookup(query,s_callQualityQueries,""),this);
3175     int val = 0;
3176     if (query) {
3177 	switch (query) {
3178 	    case ASR:
3179 		if (m_totalCalls[CURRENT_IDX]) {
3180 		    val = (int) (m_answeredCalls[CURRENT_IDX] * 100.0 / m_totalCalls[CURRENT_IDX]);
3181 		    result = String( val);
3182 		}
3183 		else
3184 		    result = "-1";
3185 		return true;
3186 	    case NER:
3187 	    	if (m_totalCalls[CURRENT_IDX]) {
3188 	    	    val = (int) ((m_answeredCalls[CURRENT_IDX] + m_delivCalls[CURRENT_IDX]) * 100.0 / m_totalCalls[CURRENT_IDX]);
3189 		    result = String(val);
3190 		}
3191 		else
3192 		    result = "-1";
3193 		return true;
3194 	    case ASR_ALL:
3195 		if (m_totalCalls[TOTAL_IDX]) {
3196 		    val = (int) (m_answeredCalls[TOTAL_IDX] * 100.0 / m_totalCalls[TOTAL_IDX]);
3197 		    result = String(val);
3198 		}
3199 		else
3200 		    result = "-1";
3201 		return true;
3202 	    case NER_ALL:
3203 	    	if (m_totalCalls[TOTAL_IDX]) {
3204 	    	    val = (int) ((m_answeredCalls[TOTAL_IDX] + m_delivCalls[TOTAL_IDX]) * 100.0 / m_totalCalls[TOTAL_IDX]);
3205 	    	    result = String(val);
3206 	    	}
3207 		else
3208 		    result = "-1";
3209 		return true;
3210 	    case MIN_ASR:
3211 	        result << m_minASR;
3212 	        return true;
3213 	    case MAX_ASR:
3214 		result << m_maxASR;
3215 		return true;
3216 	    case MIN_NER:
3217 	    	result << m_minNER;
3218 	    	return true;
3219 	    case LOW_ASR_COUNT:
3220 	    case HIGH_ASR_COUNT:
3221 	    case LOW_ASR_ALL_COUNT:
3222 	    case HIGH_ASR_ALL_COUNT:
3223 	    case LOW_NER_COUNT:
3224 	    case LOW_NER_ALL_COUNT:
3225 		result << m_alarmCounters[query - LOW_ASR_COUNT + 1];
3226 		return true;
3227 	    case HANGUP:
3228 	    case REJECT:
3229 	    case BUSY:
3230 	    case CANCELLED:
3231 	    case NO_ANSWER:
3232 	    case NO_ROUTE:
3233 	    case NO_CONN:
3234 	    case NO_AUTH:
3235 	    case CONGESTION:
3236 	    case NO_MEDIA:
3237 		result << m_callCounters[query - HANGUP];
3238 		return true;
3239 	    case HANGUP_ALL:
3240 	    case REJECT_ALL:
3241 	    case BUSY_ALL:
3242 	    case CANCELLED_ALL:
3243 	    case NO_ANSWER_ALL:
3244 	    case NO_ROUTE_ALL:
3245 	    case NO_CONN_ALL:
3246 	    case NO_AUTH_ALL:
3247 	    case CONGESTION_ALL:
3248 	    case NO_MEDIA_ALL:
3249 		result << m_callCountersAll[query - HANGUP_ALL];
3250 		return true;
3251 	    case NAME:
3252 		result << toString();
3253 		return true;
3254 	    case INDEX:
3255 		result << m_index;
3256 		return true;
3257 	    default:
3258 		return false;
3259 	}
3260     }
3261     return false;
3262 }
3263 
3264 /**
3265   * CallMonitor
3266   */
CallMonitor(const NamedList * sect,unsigned int priority)3267 CallMonitor::CallMonitor(const NamedList* sect, unsigned int priority)
3268       : MessageHandler("call.cdr",priority),
3269 	Thread("Call Monitor"),
3270 	m_checkTime(3600),
3271 	m_notifTime(0), m_inCalls(0), m_outCalls(0), m_first(true),
3272 	m_routeParam("address"),
3273 	m_monitor(false)
3274 {
3275     setFilter("operation","finalize");
3276     // configure
3277     setConfigure(sect);
3278 
3279     m_notifTime = Time::secNow() + m_checkTime;
3280     init();
3281 }
3282 
init()3283 bool CallMonitor::init()
3284 {
3285     return startup();
3286 }
3287 
3288 // main loop. Update the monitor data, if neccessary, send alarms and/or notifications
run()3289 void CallMonitor::run()
3290 {
3291     for (;;) {
3292 	check();
3293 	idle();
3294 	bool sendNotif = false;
3295 
3296 	m_cfgMtx.lock();
3297 	if (!m_first && Time::secNow() >= m_notifTime) {
3298 	    m_notifTime = Time::secNow() + m_checkTime;
3299 	    sendNotif = true;
3300 	}
3301 	m_cfgMtx.unlock();
3302 
3303 	m_routesMtx.lock();
3304 	unsigned int index = 0;
3305 	for (ObjList* o = m_routes.skipNull(); o; o = o->skipNext()) {
3306 	    index++;
3307 	    CallRouteQoS* route = static_cast<CallRouteQoS*>(o->get());
3308 	    route->updateQoS();
3309 	    if (route->alarm())
3310 		sendAlarmFrom(route);
3311 	    if (sendNotif)
3312 		route->sendNotifs(index,true);
3313 	}
3314 	if (sendNotif)
3315 	    sendNotif = false;
3316 
3317 	if (m_first)
3318 	    m_first = false;
3319 	m_routesMtx.unlock();
3320     }
3321 }
3322 
3323 // set configuration
setConfigure(const NamedList * sect)3324 void CallMonitor::setConfigure(const NamedList* sect)
3325 {
3326     if (!sect)
3327 	return;
3328     m_cfgMtx.lock();
3329     m_checkTime = sect ? sect->getIntValue("time_interval",3600) : 3600;
3330     m_routeParam = sect ? sect->getValue("route","address") : "address";
3331     m_monitor = sect ? sect->getBoolValue("monitor",false) : false;
3332     if (!m_monitor)
3333 	m_routes.clear();
3334 
3335     // if the previous time for notification is later than the one with the new interval, reset it
3336     if (m_notifTime > Time::secNow() + m_checkTime)
3337 	m_notifTime = Time::secNow() + m_checkTime;
3338 
3339     s_qosHysteresisFactor = sect ? sect->getDoubleValue("hysteresis_factor",2.0) : 2.0;
3340     if (s_qosHysteresisFactor > 10 || s_qosHysteresisFactor < 1.0) {
3341 	Debug(&__plugin,DebugNote,"CallMonitor::setConfigure() - configured hysteresis_factor is not in the 1.0 - 10.0 interval,"
3342 		" defaulting to 2.0");
3343 	s_qosHysteresisFactor = 2.0;
3344     }
3345     m_cfgMtx.unlock();
3346 }
3347 
3348 // add a route to be monitored
addRoute(NamedList * cfg)3349 void CallMonitor::addRoute(NamedList* cfg)
3350 {
3351     if (!m_monitor || !cfg)
3352 	return;
3353     m_routesMtx.lock();
3354     CallRouteQoS* route = static_cast<CallRouteQoS*>(m_routes[*cfg]);
3355     if (!route) {
3356 	route = new CallRouteQoS(*cfg,cfg);
3357         m_routes.append(route);
3358 	route->setIndex(m_routes.count());
3359     }
3360     else
3361 	route->updateConfig(cfg);
3362     m_routesMtx.unlock();
3363 }
3364 
updateRoutes()3365 void CallMonitor::updateRoutes()
3366 {
3367     m_routesMtx.lock();
3368     bool deletedRoute = true;
3369     while (deletedRoute) {
3370 	deletedRoute = false;
3371 	for (ObjList* o = m_routes.skipNull(); o; o = o->skipNext()) {
3372 	    CallRouteQoS* route = static_cast<CallRouteQoS*>(o->get());
3373 	    if (!route->isCurrent()) {
3374 		DDebug(__plugin.name(),DebugAll,"CallMonitor::updateRoutes() - removed route '%s' from monitoring",route->toString().c_str());
3375 		m_routes.remove(route);
3376 		deletedRoute = true;
3377 	    }
3378 	}
3379     }
3380     unsigned int index = 1;
3381     for (ObjList* o = m_routes.skipNull(); o; o = o->skipNext()) {
3382 	CallRouteQoS* route = static_cast<CallRouteQoS*>(o->get());
3383 	route->setIsCurrent(false);
3384 	route->setIndex(index);
3385 	index++;
3386     }
3387     m_routesMtx.unlock();
3388 }
3389 
3390 // send an alarm received from a route
sendAlarmFrom(CallRouteQoS * route)3391 void CallMonitor::sendAlarmFrom(CallRouteQoS* route)
3392 {
3393     if (!route)
3394 	return;
3395     String alarm = route->alarmText();
3396     if (!alarm.null())
3397 	__plugin.sendTrap(alarm,route->toString());
3398 }
3399 
3400 // extract from a call.cdr message call information and update the monitor data
received(Message & msg)3401 bool CallMonitor::received(Message& msg)
3402 {
3403     DDebug(__plugin.name(),DebugAll,"CdrHandler::received()");
3404 
3405     if (m_routeParam.null())
3406 	return false;
3407     String routeStr = msg.getValue(m_routeParam);
3408     if (routeStr.null())
3409 	return false;
3410     if (m_routeParam == YSTRING("address")) {
3411 	int pos = routeStr.rfind(':');
3412 	if (pos < 0)
3413 	    pos = routeStr.rfind('/');
3414 	if (pos > 0)
3415 	    routeStr = routeStr.substr(0,pos);
3416     }
3417 
3418     const String& status = msg[YSTRING("status")];
3419     int code = -1;
3420     if (status == YSTRING("answered"))
3421 	code = CallRouteQoS::ANSWERED;
3422     else if (status == YSTRING("ringing") || status == YSTRING("accepted"))
3423 	code = CallRouteQoS::DELIVERED;
3424 
3425     const String& direction = msg[YSTRING("direction")];
3426     bool outgoing = false;
3427     if (msg.getBoolValue("cdrwrite",true)) {
3428 	if (direction == YSTRING("incoming"))
3429 	    m_inCalls++;
3430 	else if (direction == YSTRING("outgoing")) {
3431 	    outgoing = true;
3432 	    m_outCalls++;
3433 	}
3434     }
3435 
3436     const String& reason = msg[YSTRING("reason")];
3437     int type = lookup(reason,s_endReasons,CallRouteQoS::HANGUP);
3438     if (type == CallRouteQoS::HANGUP && code == CallRouteQoS::DELIVERED && outgoing)
3439 	type = CallRouteQoS::CANCELLED;
3440     else if (type <= CallRouteQoS::NO_ANSWER && !outgoing)
3441 	type = CallRouteQoS::HANGUP;
3442 
3443     m_routesMtx.lock();
3444     CallRouteQoS* route = static_cast<CallRouteQoS*>(m_routes[routeStr]);
3445     if (route)
3446 	route->update(code,type);
3447     m_routesMtx.unlock();
3448     return false;
3449 }
3450 
3451 // get a value from the call counters
getCounter(int type,unsigned int & value)3452 bool CallMonitor::getCounter(int type, unsigned int& value)
3453 {
3454     DDebug(__plugin.name(),DebugAll,"CallMonitor::getCounter(%s)",lookup(type,s_callCounterQueries,""));
3455     if (type == 0 || type > ROUTES_COUNT)
3456 	return false;
3457     switch (type) {
3458 	case INCOMING_CALLS:
3459 	    value = m_inCalls;
3460 	    break;
3461 	case OUTGOING_CALLS:
3462 	    value = m_outCalls;
3463 	    break;
3464 	case ROUTES_COUNT:
3465 	    value = m_routes.count();
3466 	    break;
3467 	default:
3468 	    return false;
3469     }
3470     return true;
3471 }
3472 
3473 // obtain call monitor specific data
get(const String & query,const int & index,String & result)3474 void CallMonitor::get(const String& query, const int& index, String& result)
3475 {
3476     DDebug(__plugin.name(),DebugAll,"CallMonitor::get(%s,%d)",query.c_str(),index);
3477     if (index > 0) {
3478 	CallRouteQoS* route = static_cast<CallRouteQoS*>(m_routes[index - 1]);
3479 	if (!route)
3480 	    return;
3481 
3482 	int type = lookup(query,s_callQualityQueries,0);
3483 	if (type && route->get(type,result))
3484 	    return;
3485     }
3486     int type = lookup(query,s_callCounterQueries,0);
3487     unsigned int value = 0;
3488     if (getCounter(type,value)) {
3489 	result += value;
3490 	return;
3491     }
3492 }
3493 
3494 /**
3495   * Monitor
3496   */
Monitor()3497 Monitor::Monitor()
3498       : Module("monitoring","misc"),
3499 	m_msgUpdateHandler(0),
3500 	m_snmpMsgHandler(0),
3501 	m_hangupHandler(0),
3502 	m_startHandler(0),
3503 	m_callMonitor(0),
3504 	m_authHandler(0),
3505 	m_registerHandler(0),
3506 	m_init(false),
3507 	m_newTraps(false),
3508 	m_sipMonitoredGws(0),
3509 	m_trunkMon(false),
3510 	m_linksetMon(false),
3511 	m_linkMon(false),
3512 	m_interfaceMon(false),
3513 	m_isdnMon(false),
3514 	m_activeCallsCache(0),
3515 	m_trunkInfo(0),
3516 	m_engineInfo(0),
3517 	m_moduleInfo(0),
3518 	m_dbInfo(0),
3519 	m_rtpInfo(0),
3520 	m_linksetInfo(0),
3521 	m_linkInfo(0),
3522 	m_ifaceInfo(0),
3523 	m_accountsInfo(0)
3524 {
3525     Output("Loaded module Monitoring");
3526 }
3527 
~Monitor()3528 Monitor::~Monitor()
3529 {
3530     Output("Unloaded module Monitoring");
3531 
3532     Debugger::setAlarmHook();
3533 
3534     TelEngine::destruct(m_moduleInfo);
3535     TelEngine::destruct(m_engineInfo);
3536     TelEngine::destruct(m_activeCallsCache);
3537     TelEngine::destruct(m_linkInfo);
3538     TelEngine::destruct(m_linksetInfo);
3539     TelEngine::destruct(m_trunkInfo);
3540     TelEngine::destruct(m_dbInfo);
3541     TelEngine::destruct(m_rtpInfo);
3542     TelEngine::destruct(m_ifaceInfo);
3543     TelEngine::destruct(m_accountsInfo);
3544     TelEngine::destruct(m_sipMonitoredGws);
3545 
3546     TelEngine::destruct(m_msgUpdateHandler);
3547     TelEngine::destruct(m_snmpMsgHandler);
3548     TelEngine::destruct(m_startHandler);
3549     TelEngine::destruct(m_authHandler);
3550     TelEngine::destruct(m_registerHandler);
3551     TelEngine::destruct(m_hangupHandler);
3552 }
3553 
unload()3554 bool Monitor::unload()
3555 {
3556     DDebug(this,DebugAll,"::unload()");
3557     if (!lock(500000))
3558 	return false;
3559 
3560     Engine::uninstall(m_msgUpdateHandler);
3561     Engine::uninstall(m_snmpMsgHandler);
3562     Engine::uninstall(m_startHandler);
3563     Engine::uninstall(m_authHandler);
3564     Engine::uninstall(m_registerHandler);
3565     Engine::uninstall(m_hangupHandler);
3566 
3567     if (m_callMonitor) {
3568 	Engine::uninstall(m_callMonitor);
3569 	m_callMonitor->cancel();
3570 	m_callMonitor = 0;
3571     }
3572 
3573     uninstallRelays();
3574     unlock();
3575     return true;
3576 }
3577 
initialize()3578 void Monitor::initialize()
3579 {
3580     Output("Initializing module Monitoring");
3581 
3582     // read configuration
3583     Configuration cfg(Engine::configFile("monitoring"));
3584 
3585     if (!m_init) {
3586 	m_init = true;
3587 	setup();
3588 	installRelay(Halt);
3589 	installRelay(Timer);
3590 	Debugger::setAlarmHook(alarmCallback);
3591 
3592 	s_nodeState = "active";
3593     }
3594 
3595     if (!m_msgUpdateHandler) {
3596 	m_msgUpdateHandler = new MsgUpdateHandler();
3597         Engine::install(m_msgUpdateHandler);
3598     }
3599     if (!m_snmpMsgHandler) {
3600 	m_snmpMsgHandler = new SnmpMsgHandler();
3601 	Engine::install(m_snmpMsgHandler);
3602     }
3603     if (!m_hangupHandler) {
3604 	m_hangupHandler = new HangupHandler();
3605 	Engine::install(m_hangupHandler);
3606     }
3607     if (!m_startHandler) {
3608 	m_startHandler = new EngineStartHandler();
3609 	Engine::install(m_startHandler);
3610     }
3611     if (!m_authHandler) {
3612 	m_authHandler = new AuthHandler();
3613 	Engine::install(m_authHandler);
3614     }
3615     if (!m_registerHandler) {
3616 	m_registerHandler = new RegisterHandler();
3617 	Engine::install(m_registerHandler);
3618     }
3619 
3620     // build a call monitor
3621     NamedList* asrCfg = cfg.getSection("call_qos");
3622     if (!m_callMonitor) {
3623 	m_callMonitor = new CallMonitor(asrCfg);
3624 	Engine::install(m_callMonitor);
3625     }
3626     else
3627 	m_callMonitor->setConfigure(asrCfg);
3628 
3629     int cacheFor = cfg.getIntValue("general","cache",1);
3630     if (!m_activeCallsCache)
3631 	m_activeCallsCache = new ActiveCallsInfo();
3632     m_activeCallsCache->setRetainInfoTime(cacheFor);//seconds
3633 
3634     if (!m_trunkInfo)
3635         m_trunkInfo = new TrunkInfo();
3636     m_trunkInfo->setRetainInfoTime(cacheFor);//seconds
3637 
3638     if (!m_linksetInfo)
3639 	m_linksetInfo = new LinksetInfo();
3640     m_linksetInfo->setRetainInfoTime(cacheFor);//seconds
3641 
3642     if (!m_linkInfo)
3643 	m_linkInfo = new LinkInfo();
3644     m_linkInfo->setRetainInfoTime(cacheFor);//seconds
3645 
3646     if (!m_ifaceInfo)
3647 	m_ifaceInfo = new InterfaceInfo();
3648     m_ifaceInfo->setRetainInfoTime(cacheFor);//seconds
3649 
3650     if (!m_accountsInfo)
3651 	m_accountsInfo = new AccountsInfo();
3652     m_accountsInfo->setRetainInfoTime(cacheFor);//seconds
3653 
3654     if (!m_engineInfo)
3655 	m_engineInfo = new EngineInfo();
3656     m_engineInfo->setRetainInfoTime(cacheFor);//seconds
3657 
3658     if (!m_moduleInfo)
3659 	m_moduleInfo = new ModuleInfo();
3660     m_moduleInfo->setRetainInfoTime(cacheFor);//seconds
3661 
3662     bool enable = cfg.getBoolValue("database","monitor",false);
3663     if (!m_dbInfo)
3664 	m_dbInfo = new DatabaseInfo(enable);
3665     else
3666 	m_dbInfo->setMonitorEnabled(enable);
3667     m_dbInfo->setRetainInfoTime(cacheFor);
3668 
3669     readConfig(cfg);
3670 }
3671 
readConfig(const Configuration & cfg)3672 void Monitor::readConfig(const Configuration& cfg)
3673 {
3674     // get the threshold for yate restart alarm
3675     s_yateRunAlarm = cfg.getIntValue("general","restart_alarm",1);
3676     int level = cfg.getIntValue("general","alarm_threshold",DebugNote);
3677     if (level < DebugFail)
3678 	level = -1;
3679     else if (level < DebugConf)
3680 	level = DebugConf;
3681     else if (level > DebugAll)
3682 	level = DebugAll;
3683     s_alarmThreshold = level;
3684     m_newTraps = !cfg.getBoolValue("general","old_trap_style");
3685 
3686     // read configs for database monitoring (they type=database, the name of the section is the database account)
3687     for (unsigned int i = 0; i < cfg.sections(); i++) {
3688 	NamedList* sec = cfg.getSection(i);
3689 	if (!sec || (*sec == "general"))
3690 	    continue;
3691 	String type = sec->getValue("type","");
3692 	if (type.null())
3693 	    continue;
3694 	if (type == "database" && m_dbInfo)
3695 	    m_dbInfo->addDatabase(sec);
3696 	if (type == "call_qos" && m_callMonitor)
3697 	    m_callMonitor->addRoute(sec);
3698     }
3699     m_callMonitor->updateRoutes();
3700     m_dbInfo->updateDatabaseAccounts();
3701 
3702     // read config for SIP monitoring
3703     String gw = cfg.getValue("sip","gateways","");
3704     if (!gw.null()) {
3705 	if (m_sipMonitoredGws)
3706 	    TelEngine::destruct(m_sipMonitoredGws);
3707         m_sipMonitoredGws = gw.split(';',false);
3708 	for (ObjList* o = m_sipMonitoredGws->skipNull(); o; o = o->skipNext()) {
3709 	    String* addr = static_cast<String*>(o->get());
3710 	    int pos = addr->find(":");
3711 	    if (pos == -1)
3712 	        addr->append(":" + String(SIP_PORT));
3713 	    else {
3714 	        String tmp = addr->substr(pos+1);
3715 	        if (tmp.null())
3716 		    addr->append(":" + String(SIP_PORT));
3717 	    }
3718         }
3719     }
3720     s_sipInfo.auths.threshold = cfg.getIntValue("sip","max_failed_auths",0);
3721     s_sipInfo.transactions.threshold = cfg.getIntValue("sip","max_transaction_timeouts",0);
3722     s_sipInfo.byes.threshold = cfg.getIntValue("sip","max_byes_timeouts",0);
3723     s_sipInfo.reset = cfg.getIntValue("sip","reset_time",0);
3724     if (s_sipInfo.reset)
3725         s_sipInfo.resetTime = Time::secNow() + s_sipInfo.reset;
3726 
3727     // read SS7 monitoring
3728     bool sigEnable = cfg.getBoolValue("sig","monitor",false);
3729     m_trunkMon = cfg.getBoolValue("sig","trunk",sigEnable);
3730     m_interfaceMon = cfg.getBoolValue("sig","interface",sigEnable);
3731     m_linksetMon = cfg.getBoolValue("sig","linkset",sigEnable);
3732     m_linkMon = cfg.getBoolValue("sig","link",sigEnable);
3733     m_isdnMon = cfg.getBoolValue("sig","isdn",sigEnable);
3734 
3735     // read RTP monitoring
3736     NamedList* sect = cfg.getSection("rtp");
3737     if (sect) {
3738         if (!m_rtpInfo)
3739 	    m_rtpInfo = new RTPTable(sect);
3740 	else
3741 	    m_rtpInfo->reconfigure(sect);
3742     }
3743     else
3744 	TelEngine::destruct(m_rtpInfo);
3745 
3746     // read config for MGCP monitoring
3747     s_mgcpInfo.transactions.threshold = cfg.getIntValue("mgcp","max_transaction_timeouts",0);
3748     s_mgcpInfo.deletes.threshold = cfg.getIntValue("mgcp","max_deletes_timeouts",0);
3749     s_mgcpInfo.reset = cfg.getIntValue("mgcp","reset_time",0);
3750     s_mgcpInfo.gw_monitor = cfg.getBoolValue("mgcp","gw_monitor",false);
3751     if (s_mgcpInfo.reset)
3752         s_mgcpInfo.resetTime = Time::secNow() + s_mgcpInfo.reset;
3753 
3754 }
3755 
3756 // handle messages received by the module
received(Message & msg,int id)3757 bool Monitor::received(Message& msg, int id)
3758 {
3759     if (id == Halt) {
3760 	DDebug(this,DebugInfo,"::received() - Halt Message");
3761 	s_nodeState = "exiting";
3762 	unload();
3763     }
3764     if (id == Timer) {
3765 	if (m_rtpInfo && m_rtpInfo->shouldReset())
3766 	    m_rtpInfo->reset();
3767 	if (s_sipInfo.resetTime && Time::secNow() > s_sipInfo.resetTime) {
3768 	    s_sipInfo.auths.counter = s_sipInfo.transactions.counter = s_sipInfo.byes.counter = 0;
3769 	    s_sipInfo.auths.alarm = s_sipInfo.transactions.alarm = s_sipInfo.byes.alarm = false;
3770 	    s_sipInfo.resetTime = Time::secNow() + s_sipInfo.reset;
3771 	}
3772 	if (s_mgcpInfo.resetTime && Time::secNow() > s_mgcpInfo.resetTime) {
3773 	    s_mgcpInfo.transactions.counter = s_mgcpInfo.deletes.counter = 0;
3774 	    s_mgcpInfo.transactions.alarm = s_mgcpInfo.deletes.alarm = false;
3775 	    s_mgcpInfo.resetTime = Time::secNow() + s_mgcpInfo.reset;
3776 	}
3777 	if (m_dbInfo)
3778 	    m_dbInfo->reset();
3779     }
3780     return Module::received(msg,id);
3781 }
3782 
3783 // handle module.update messages
update(Message & msg)3784 void Monitor::update(Message& msg)
3785 {
3786     String module = msg.getValue("module","");
3787     XDebug(this,DebugAll,"Monitor::update() from module=%s",module.c_str());
3788     int type = lookup(module,s_modules,0);
3789     switch (type) {
3790 	case DATABASE:
3791 	    if (m_dbInfo)
3792 		m_dbInfo->update(msg);
3793 	    break;
3794 	case PSTN:
3795 	    sendSigNotifs(msg);
3796 	    break;
3797 	case INTERFACE:
3798 	    sendCardNotifs(msg);
3799 	    break;
3800 	case RTP:
3801 	    if (m_rtpInfo)
3802 		m_rtpInfo->update(msg);
3803 	    break;
3804 	case SIP:
3805 	case MGCP:
3806 	    checkNotifs(msg,type);
3807 	    break;
3808 	default:
3809 	    break;
3810     }
3811 }
3812 
3813 // build SS7 notifications
sendSigNotifs(Message & msg)3814 void Monitor::sendSigNotifs(Message& msg)
3815 {
3816     const String& type = msg[YSTRING("type")];
3817     const String& name = msg[YSTRING("from")];
3818     if (type.null() || name.null())
3819 	return;
3820     // get the type of the notification
3821     int t = lookup(type,s_sigTypes,0);
3822     DDebug(this,DebugInfo,"Monitor::sendSigNotifs() - send notification from '%s'",name.c_str());
3823     bool up = msg.getBoolValue(YSTRING("operational"));
3824     const char* text = msg.getValue(YSTRING("text"));
3825     String notif;
3826     // build trap information
3827     switch (t) {
3828 	case ISDN:
3829 	    if (m_isdnMon)
3830 		sendTrap(lookup((up ? IsdnQ921Up : IsdnQ921Down),s_sigNotifs),name,0,text);
3831 	    if (!up && m_linkInfo)
3832 		m_linkInfo->updateAlarmCounter(name);
3833 	    break;
3834 	case SS7_MTP3:
3835 	    if (m_linksetMon) {
3836 		sendTrap(lookup((up ? LinksetUp : LinksetDown),s_sigNotifs),name,0,text);
3837 		if (!up && m_linksetInfo)
3838 		    m_linksetInfo->updateAlarmCounter(name);
3839 	    }
3840 	    notif = msg.getValue("link","");
3841 	    if (m_linkMon && !notif.null()) {
3842 		up = msg.getBoolValue(YSTRING("linkup"),false);
3843 		sendTrap(lookup(( up ? LinkUp : LinkDown),s_sigNotifs),notif);
3844 		if (!up && m_linkInfo)
3845 		    m_linkInfo->updateAlarmCounter(name);
3846 	    }
3847 	    break;
3848 	case TRUNK:
3849 	    if (m_trunkMon)
3850 		sendTrap(lookup(( up ? TrunkUp : TrunkDown),s_sigNotifs),name,0,text);
3851 	    if (!up && m_trunkInfo)
3852 		m_trunkInfo->updateAlarmCounter(name);
3853 	    break;
3854 	default:
3855 	    break;
3856     }
3857 }
3858 
3859 // build and send trap from interface notifications
sendCardNotifs(Message & msg)3860 void Monitor::sendCardNotifs(Message& msg)
3861 {
3862     String device = msg.getValue("interface","");
3863     DDebug(this,DebugInfo,"::sendCardNotifs() - a notification from interface '%s' has been received",device.c_str());
3864     if (device.null())
3865 	return;
3866     String notif = msg.getValue("notify","");
3867     int type = lookup(notif,s_cardInfo,0);
3868     if (type && m_interfaceMon) {
3869 	String trap = lookup(type,s_cardNotifs,"");
3870 	if (!trap.null())
3871 	    sendTrap(notif,device);
3872 	if (m_ifaceInfo)
3873 	    m_ifaceInfo->updateAlarmCounter(device);
3874     }
3875 }
3876 
3877 // helper function to check for a value against a threshold from a BaseInfo struct and send a notification if necessary
checkInfo(unsigned int count,BaseInfo & info,unsigned int alrm,TokenDict * dict)3878 static void checkInfo(unsigned int count, BaseInfo& info, unsigned int alrm, TokenDict* dict)
3879 {
3880     DDebug(&__plugin,DebugAll,"checkInfo(count=%d, info={threshold=%d,alarm=%s,counter=%d})",
3881 		count,info.threshold,String::boolText(info.alarm),info.counter);
3882     info.counter += count;
3883     if (info.threshold && !info.alarm && info.counter >= info.threshold) {
3884 	info.alarm = true;
3885 	String notif = lookup(alrm,dict,"");
3886 	if (!notif.null())
3887 	    __plugin.sendTrap(notif,String(info.counter));
3888     }
3889 }
3890 
3891 // handle module.update messages from SIP and MGCP
checkNotifs(Message & msg,unsigned int type)3892 void Monitor::checkNotifs(Message& msg, unsigned int type)
3893 {
3894     DDebug(&__plugin,DebugAll,"::checkNotifs() from module='%s'",lookup(type,s_modules,""));
3895     if (type == SIP) {
3896 	unsigned int count = msg.getIntValue("failed_auths",0);
3897 	checkInfo(count,s_sipInfo.auths,FailedAuths,s_sipNotifs);
3898 
3899 	count = msg.getIntValue("transaction_timeouts",0);
3900 	checkInfo(count,s_sipInfo.transactions,TransactTimedOut,s_sipNotifs);
3901 
3902 	count = msg.getIntValue("bye_timeouts",0);
3903 	checkInfo(count,s_sipInfo.byes,ByesTimedOut,s_sipNotifs);
3904     }
3905     if (type == MGCP) {
3906 	unsigned int transTO = msg.getIntValue("tr_timedout",0);
3907 	checkInfo(transTO,s_mgcpInfo.transactions,TransactTimedOut,s_mgcpNotifs);
3908 
3909 	transTO = msg.getIntValue("del_timedout",0);
3910 	checkInfo(transTO,s_mgcpInfo.deletes,DeletesTimedOut,s_mgcpNotifs);
3911 
3912 	if (s_mgcpInfo.gw_monitor) {
3913 	    if (msg.getValue("mgcp_gw_down"))
3914 		sendTrap(lookup(GWTimeout,s_mgcpNotifs,"mgcpGatewayTimedOut"),msg.getValue("mgcp_gw_down"));
3915 	    if (msg.getValue("mgcp_gw_up"))
3916 		sendTrap(lookup(GWUp,s_mgcpNotifs,"mgcpGatewayUp"),msg.getValue("mgcp_gw_up"));
3917 	}
3918     }
3919 }
3920 
3921 // get SIP/MGCP transaction info
getTransactionsInfo(const String & query,const int who)3922 String Monitor::getTransactionsInfo(const String& query, const int who)
3923 {
3924     String result = "";
3925     if (who == SIP) {
3926 	int type = lookup(query,s_sipNotifs,0);
3927 	switch (type) {
3928 	    case TransactTimedOut:
3929 		result << s_sipInfo.transactions.counter;
3930 		return result;
3931 	    case FailedAuths:
3932 	    	result << s_sipInfo.auths.counter;
3933 		return result;
3934 	    case ByesTimedOut:
3935 	    	result << s_sipInfo.byes.counter;
3936 		return result;
3937 	    default:
3938 		break;
3939 	}
3940     }
3941     else if (who == MGCP) {
3942         int type = lookup(query,s_mgcpNotifs,0);
3943         switch (type) {
3944 	    case TransactTimedOut:
3945 		result << s_mgcpInfo.transactions.counter;
3946 		return result;
3947 	    case DeletesTimedOut:
3948 		result << s_mgcpInfo.deletes.counter;
3949 		return result;
3950 	    default:
3951 		break;
3952 	}
3953     }
3954     return "";
3955 }
3956 
3957 // build a notification message. Increase the alarm counters if the notification was an alarm
sendTrap(const String & trap,const String & value,unsigned int index,const char * text)3958 void Monitor::sendTrap(const String& trap, const String& value, unsigned int index, const char* text)
3959 {
3960     DDebug(&__plugin,DebugAll,"Monitor::sendtrap(trap='%s',value='%s',index='%d') [%p]",trap.c_str(),value.c_str(),index,this);
3961     Message* msg = new Message("monitor.notify",0,true);
3962     if (m_newTraps)
3963 	msg->addParam("notify","specificAlarm");
3964     msg->addParam("notify.0",trap);
3965     msg->addParam("value.0",value);
3966     if (text && m_newTraps) {
3967 	msg->addParam("notify.1","alarmText");
3968 	msg->addParam("value.1",text);
3969     }
3970     if (index)
3971 	msg->addParam("index",String(index));
3972     Engine::enqueue(msg);
3973 }
3974 
3975 // build a notification message. Increase the alarm counters if the notification was an alarm
sendTraps(const NamedList & traps)3976 void Monitor::sendTraps(const NamedList& traps)
3977 {
3978     Message* msg = new Message("monitor.notify",0,true);
3979     if (m_newTraps)
3980 	msg->addParam("notify","specificAlarm");
3981     msg->copyParams(traps);
3982     Engine::enqueue(msg);
3983 }
3984 
3985 // handle a query for a specific monitored value
solveQuery(Message & msg)3986 bool Monitor::solveQuery(Message& msg)
3987 {
3988     XDebug(__plugin.name(),DebugAll,"::solveQuery()");
3989     String query = msg.getValue("name","");
3990     if (query.null())
3991 	return false;
3992     int queryWho = lookup(query,s_categories,-1);
3993     String result = "";
3994     unsigned int index = msg.getIntValue("index",0);
3995     DDebug(__plugin.name(),DebugAll,"::solveQuery(query=%s, index=%u)",query.c_str(),index);
3996     switch (queryWho) {
3997 	case DATABASE:
3998 	    if (m_dbInfo)
3999 		result = m_dbInfo->getInfo(query,index,s_databaseQuery);
4000 	    break;
4001 	case CALL_MONITOR:
4002 	    if (m_callMonitor)
4003 		m_callMonitor->get(query,index,result);
4004 	    break;
4005 	case ACTIVE_CALLS:
4006 	    if (m_activeCallsCache)
4007 		result = m_activeCallsCache->getInfo(query,index,s_activeCallInfo);
4008 	    break;
4009 	case TRUNKS:
4010 	    if (m_trunkInfo)
4011 		result = m_trunkInfo->getInfo(query,index,m_trunkInfo->s_trunkInfo);
4012 	    break;
4013 	case LINKSETS:
4014 	    if (m_linksetInfo)
4015 		result = m_linksetInfo->getInfo(query,index,m_linksetInfo->s_linksetInfo);
4016 	    break;
4017 	case LINKS:
4018 	    if (m_linkInfo)
4019 		result = m_linkInfo->getInfo(query,index,m_linkInfo->s_linkInfo);
4020 	    break;
4021 	case IFACES:
4022 	    if (m_ifaceInfo)
4023 		result = m_ifaceInfo->getInfo(query,index,m_ifaceInfo->s_ifacesInfo);
4024 	    break;
4025 	case ACCOUNTS:
4026 	    if (m_accountsInfo)
4027 		result = m_accountsInfo->getInfo(query,index,s_accountInfo);
4028 	    break;
4029 	case ENGINE:
4030 	    if (m_engineInfo)
4031 	        result = m_engineInfo->getInfo(query,index,s_engineQuery);
4032 	    break;
4033 	case MODULE:
4034 	    if (m_moduleInfo)
4035 		result = m_moduleInfo->getInfo(query,index,s_moduleQuery);
4036 	    break;
4037 	case AUTH_REQUESTS:
4038 	    if (m_authHandler)
4039 		result = m_authHandler->getCount();
4040 	    break;
4041 	case REGISTER_REQUESTS:
4042 	    if (m_registerHandler)
4043 	        result = m_registerHandler->getCount();
4044 	    break;
4045 	case RTP:
4046 	    if (m_rtpInfo)
4047 		result = m_rtpInfo->getInfo(query,index);
4048 	    break;
4049 	case SIP:
4050 	case MGCP:
4051 	    result = getTransactionsInfo(query,queryWho);
4052 	    break;
4053 	default:
4054 	    return false;
4055     }
4056     msg.setParam("value",result);
4057     return true;
4058 }
4059 
4060 // verify if a call hasn't hangup because of a gateway timeout. In that case, if the gateway was
4061 // monitored send a notification
handleChanHangup(const String & address,int & code)4062 void Monitor::handleChanHangup(const String& address, int& code)
4063 {
4064     DDebug(this,DebugInfo,"::handleChanHangup('%s', '%d')",address.c_str(),code);
4065     if (address.null())
4066 	return;
4067     if (m_sipMonitoredGws && m_sipMonitoredGws->find(address)) {
4068 	if (code == 408 && !m_timedOutGws.find(address)) {
4069 	    sendTrap(lookup(GWTimeout,s_sipNotifs,"gatewayTimeout"),address);
4070 	    m_timedOutGws.append(new String(address));
4071 	}
4072     }
4073 }
4074 
4075 // if a call has passed through, get the gateway and verify if it was previously down
4076 // if it was send a notification that the gateway is up again
verifyGateway(const String & address)4077 bool Monitor::verifyGateway(const String& address)
4078 {
4079     if (address.null())
4080 	return false;
4081     if (m_timedOutGws.find(address)) {
4082 	m_timedOutGws.remove(address);
4083 	sendTrap(lookup(GWUp,s_sipNotifs,"gatewayUp"),address);
4084     }
4085     return true;
4086 }
4087 
4088 };
4089 
4090 /* vi: set ts=8 sw=4 sts=4 noet: */
4091