diff --git a/src/Makefile.am b/src/Makefile.am index 0264f55..305e57e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -42,7 +42,8 @@ pkglib_LTLIBRARIES = plugin_demo.la \ plugin_codecfilter.la \ plugin_stripheader.la \ plugin_siptrunk.la \ - plugin_fix_fbox_anoncall.la + plugin_fix_fbox_anoncall.la \ + plugin_stats.la DLOPENPLUGINS = -dlopen plugin_demo.la \ -dlopen plugin_shortdial.la \ @@ -56,7 +57,8 @@ DLOPENPLUGINS = -dlopen plugin_demo.la \ -dlopen plugin_codecfilter.la \ -dlopen plugin_stripheader.la \ -dlopen plugin_siptrunk.la \ - -dlopen plugin_fix_fbox_anoncall.la + -dlopen plugin_fix_fbox_anoncall.la \ + -dlopen plugin_stats.la # plugin_demo_la_SOURCES = plugin_demo.c plugin_demo_la_LDFLAGS = -module -avoid-version -shrext '.so' @@ -96,6 +98,9 @@ plugin_siptrunk_la_LDFLAGS = -module -avoid-version -shrext '.so' # plugin_fix_fbox_anoncall_la_SOURCES = plugin_fix_fbox_anoncall.c plugin_fix_fbox_anoncall_la_LDFLAGS = -module -avoid-version -shrext '.so' +# +plugin_stats_la_SOURCES = plugin_stats.c +plugin_stats_la_LDFLAGS = -module -avoid-version -shrext '.so' # diff --git a/src/plugin_stats.c b/src/plugin_stats.c new file mode 100644 index 0000000..ad1f832 --- /dev/null +++ b/src/plugin_stats.c @@ -0,0 +1,365 @@ +/* + Copyright (C) 2016 Thomas Ries + + This file is part of Siproxd. + + Siproxd is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Siproxd is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warrantry of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Siproxd; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +/* must be defined before including */ +#define PLUGIN_NAME plugin_stats + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "siproxd.h" +#include "rtpproxy.h" +#include "plugins.h" +#include "log.h" + + +static char const ident[]="$Id$"; + +/* Plug-in identification */ +static char name[]="plugin_stats"; +static char desc[]="Upon receiving SIGUSR1, dump some call statistics"; + +/* constants */ +#define STATS_SYSLOG 0x01 +#define STATS_FILE 0x02 +#define STATS_ALL 0xff + +/* global configuration storage - required for config file location */ +extern struct siproxd_config configuration; +/* need access to the proxy table */ +extern rtp_proxytable_t rtp_proxytable[]; +extern struct urlmap_s urlmap[]; + +/* plugin configuration storage */ +static struct plugin_config { + int to_syslog; // 0: off, -1 only upon SIGUSR1, >0, every n seconds + int to_file; // 0: off, -1 only upon SIGUSR1, >0, every n seconds + char *filename; +} plugin_cfg; + +/* Instructions for config parser */ +static cfgopts_t plugin_cfg_opts[] = { + { "plugin_stats_to_syslog", TYP_INT4, &plugin_cfg.to_syslog, {0, NULL} }, + { "plugin_stats_to_file", TYP_INT4, &plugin_cfg.to_file, {0, NULL} }, + { "plugin_stats_filename", TYP_STRING, &plugin_cfg.filename, {0, NULL} }, + {0, 0, 0} +}; + +/* local storage needed by plugin */ +static int dump_stats=0; +static int idx_to_rtp_proxytable[RTPPROXY_SIZE]; // <0: empty, >=0, index into rtp_proxytable +static int stats_num_streams=0; +static int stats_num_calls=0; +static int stats_num_act_clients=0; +static int stats_num_reg_clients=0; + + +/* local prototypes */ +static void stats_sighandler(int sig); +static void stats_prepare(void); +static void stats_to_syslog(void); +static void stats_to_file(void); + +/* + * Initialization. + * Called once suring siproxd startup. + */ +int PLUGIN_INIT(plugin_def_t *plugin_def) { + struct sigaction act; + + /* API version number of siproxd that this plugin is built against. + * This constant will change whenever changes to the API are made + * that require adaptions in the plugin. */ + plugin_def->api_version=SIPROXD_API_VERSION; + + /* Name and descriptive text of the plugin */ + plugin_def->name=name; + plugin_def->desc=desc; + + /* Execution mask - during what stages of SIP processing shall + * the plugin be called. */ + plugin_def->exe_mask=PLUGIN_TIMER; + + /* read the config file */ + if (read_config(configuration.configfile, + configuration.config_search, + plugin_cfg_opts, name) == STS_FAILURE) { + ERROR("Plugin '%s': could not load config file", name); + return STS_FAILURE; + } + + /* check if statistics dump is actually enabled, if not exit */ + if ((plugin_cfg.to_syslog == 0) && (plugin_cfg.to_file == 0)) { + plugin_def->exe_mask=PLUGIN_NOOP; + WARN("Plugin_stats loaded but not enabled in config."); + return STS_SUCCESS; + } +/*&&& require rtp_proxy_enable==1, otherwise no stats in rtp_proxytable */ + + /* setup signal handler - SIGUSR1 */ + act.sa_handler=stats_sighandler; + sigemptyset(&act.sa_mask); + act.sa_flags=SA_RESTART; + if (sigaction(SIGUSR1, &act, NULL)) { + ERROR("Failed to install SIGUSR1 handler"); + } + + INFO("plugin_stats is initialized and armed"); + return STS_SUCCESS; +} + +/* + * Processing. + * + */ +int PLUGIN_PROCESS(int stage, sip_ticket_t *ticket){ + static time_t last_run=0; + time_t now=0; + /* stage contains the PLUGIN_* value - the stage of SIP processing. */ +// INFO("plugin_stats: processing - stage %i",stage); + + time(&now); + if (last_run==0) { last_run=now; } + + if ((plugin_cfg.to_syslog > 0) && + (now - last_run > plugin_cfg.to_syslog)) { dump_stats |= STATS_SYSLOG; } + if ((plugin_cfg.to_file > 0) && + (now - last_run > plugin_cfg.to_file)) { dump_stats |= STATS_FILE; } + + if (dump_stats) { + DEBUGC(DBCLASS_PLUGIN, "plugin_stats: triggered, dump_stats=0x%x", dump_stats); + stats_prepare(); + if (dump_stats & STATS_SYSLOG) { stats_to_syslog(); } + if (dump_stats & STATS_FILE) { stats_to_file(); } + dump_stats=0; + last_run=now; + } + + + return STS_SUCCESS; +} + +/* + * De-Initialization. + * Called during shutdown of siproxd. Gives the plugin the chance + * to clean up its mess (e.g. dynamic memory allocation, database + * connections, whatever the plugin messes around with) + */ +int PLUGIN_END(plugin_def_t *plugin_def){ + INFO("plugin_stats ends here"); + return STS_SUCCESS; +} + + +/* + * module-local functions + */ + +/* + * Signal handler + * + * this one is called asynchronously whevener a registered + * signal is applied. Just set a flag and don't do any funny + * things here. + */ +static void stats_sighandler(int sig) { + /* set flag to dump statistics at next Tick */ + if (sig==SIGUSR1) dump_stats=STATS_ALL; + return; +} + +static int stats_compare(const void *p1, const void *p2) { + int i1; + int i2; + int sts; + + // safety: don't do anythigng if bad data given. + if ((p1 == NULL) || (p2 == NULL)) return 0; + + i1=*(int*)p1; + i2=*(int*)p2; + + // sort by (1)client-id, (2)call-id, (3)stream number + + // check client-id + sts = memcmp(&rtp_proxytable[i1].client_id, + &rtp_proxytable[i2].client_id, + sizeof(client_id_t)); + if (sts != 0) return sts; + + // check call-id host + sts = memcmp(&rtp_proxytable[i1].callid_host, + &rtp_proxytable[i2].callid_host, + sizeof(CALLIDHOST_SIZE)); + if (sts != 0) return sts; + + // check call-id number + sts = memcmp(&rtp_proxytable[i1].callid_number, + &rtp_proxytable[i2].callid_number, + sizeof(CALLIDNUM_SIZE)); + if (sts != 0) return sts; + + // check media stream number + if (rtp_proxytable[i1].media_stream_no < rtp_proxytable[i2].media_stream_no) return -1; + if (rtp_proxytable[i1].media_stream_no > rtp_proxytable[i2].media_stream_no) return 1; + + return 0; +} + +static void stats_prepare(void) { + int i; + int j=0; + int sts; + + // loop through rtp_proxytable and populate idx_to_rtp_proxytable + for (i=0; i < RTPPROXY_SIZE; i++) { + if (rtp_proxytable[i].rtp_rx_sock) { + idx_to_rtp_proxytable[j++] = i; + } + } + + // put the EOT mark + idx_to_rtp_proxytable[j]=-1; + + // run Q-Sort + qsort(idx_to_rtp_proxytable, j, sizeof(idx_to_rtp_proxytable[0]), stats_compare); + + // run through sorted table and calculate counters + stats_num_streams=0; + stats_num_calls=0; + stats_num_act_clients=0; + stats_num_reg_clients=0; + + for (i=0; i < j; i++) { + // each entry -> +1 stream + stats_num_streams++; + + if (i>0) { + // change of call-id? -> +1 call + // check call-id host + sts = memcmp(&rtp_proxytable[i].callid_host, + &rtp_proxytable[i-1].callid_host, + sizeof(CALLIDHOST_SIZE)); + if (sts != 0) { + stats_num_calls++; + } else { + // check call-id number + sts = memcmp(&rtp_proxytable[i].callid_number, + &rtp_proxytable[i-1].callid_number, + sizeof(CALLIDNUM_SIZE)); + if (sts != 0) { + stats_num_calls++; + } + } + // change of client-id -> +1 client + sts = memcmp(&rtp_proxytable[i].client_id, + &rtp_proxytable[i-1].client_id, + sizeof(client_id_t)); + if (sts != 0) { + stats_num_act_clients++; + } + } + } + + for (i=0; i < URLMAP_SIZE; i++) { + if (urlmap[i].active == 1) { stats_num_reg_clients++; } + } + +} + +static void stats_to_syslog(void) { + INFO("RTP-STATS: %i active Streams, %i active Calls, %i active Clients, %i registered Clients", + stats_num_streams, stats_num_calls, stats_num_act_clients, stats_num_reg_clients); +} + +static void stats_to_file(void) { + int i; + FILE *stream; + char remip[16]; + char lclip[16]; + + if (plugin_cfg.filename) { + DEBUGC(DBCLASS_REG,"opening stats file for write"); + /* write urlmap back to file */ + stream = fopen(plugin_cfg.filename, "w+"); + if (!stream) { + /* try to unlink it and open again */ + unlink(plugin_cfg.filename); + stream = fopen(plugin_cfg.filename, "w+"); + + /* open file for write failed, complain */ + if (!stream) { + ERROR("unable to write statistics file, disabling statistics"); + plugin_cfg.to_file = 0; + return; + } + } + + // write header + fprintf(stream, "RTP-STATS\n---------\n"); +// fprintf(stream, "%i active Streams\n", stats_num_streams); +// fprintf(stream, "%i active Calls\n", stats_num_calls); +// fprintf(stream, "%i active Clients\n", stats_num_act_clients); +// fprintf(stream, "%i registered Clients\n\n", stats_num_reg_clients); + + fprintf(stream, "active Streams: %6i\n", stats_num_streams); + fprintf(stream, "active Calls: %6i\n", stats_num_calls); + fprintf(stream, "active Clients: %6i\n", stats_num_act_clients); + fprintf(stream, "registered Clients: %6i\n\n", stats_num_reg_clients); + + fprintf(stream, "Client-Id;Call-Id;Direction;local IP; remote IP\n"); + + for (i=0; i < RTPPROXY_SIZE; i++) { + if (idx_to_rtp_proxytable[i] < 0) break; + + fprintf(stream, "%s;", rtp_proxytable[i].client_id.idstring); + fprintf(stream, "%s@%s;", rtp_proxytable[i].callid_number, rtp_proxytable[i].callid_host); + fprintf(stream, "%s;\n", (rtp_proxytable[i].direction==DIR_INCOMING)? "Incoming":"Outgoing"); + strcpy(lclip, utils_inet_ntoa(rtp_proxytable[i].local_ipaddr)); + fprintf(stream, "%s;", lclip); + strcpy(remip, utils_inet_ntoa(rtp_proxytable[i].remote_ipaddr)); + fprintf(stream, "%s;", remip); + fprintf(stream, "\n"); + +// - # of RTP streams +// - last activity time + + + } + fclose(stream); + DEBUGC(DBCLASS_REG,"closed stats file"); + } else { + ERROR("no statistics file name given, disabling statistics"); + plugin_cfg.to_file = 0; + } + return; +} diff --git a/src/plugins.h b/src/plugins.h index 2fb3135..d39ea65 100644 --- a/src/plugins.h +++ b/src/plugins.h @@ -45,6 +45,10 @@ /* * Processing stages for Plugins */ +/* NOOP */ +/* No activation */ +#define PLUGIN_NOOP 0x00000000 + /* get cyclic trigger */ /* NO ticket is present (ticket = NULL pointer) */ #define PLUGIN_TIMER 0x00000001 @@ -53,7 +57,7 @@ /* may end the current SIP processing in siproxd by returning STS_FALSE * * may be used to intercept other traffic on SIP port */ /* ticket with NO sipmsg is present (ticket.sipmsg = NULL pointer) */ -#define PLUGIN_PROCESS_RAW 0x00000005 +#define PLUGIN_PROCESS_RAW 0x00000008 /*--------- below here a valid sip message (ticket->sipmsg) is present ---*/