freeswitch plugin: Some coding style changes.
[collectd.git] / src / freeswitch.c
1 /**
2  * collectd - src/freeswitch.c
3  * Copyright (C) 2009       Leon de Rooij
4  * Copyright (C) 2005-2007  Florian octo Forster
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; only version 2 of the License is applicable.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Authors:
20  *   Florian octo Forster <octo at verplant.org>
21  *   Leon de Rooij <leon at scarlet-internet.nl>
22  **/
23
24 #include "collectd.h"
25 #include "common.h"
26 #include "plugin.h"
27 #include "utils_match.h"
28 #include "esl.h"
29
30 #define FS_DEF_HOST "127.0.0.1"
31 #define FS_DEF_PORT "8021"
32 #define FS_DEF_PASS "ClueCon"
33
34 /*
35  *      <Plugin freeswitch>
36  *              Host "127.0.0.1"
37  *              Port "8021"
38  *              Password "ClueCon"
39  *              <Command "api sofia status profile res-public">
40  *                      Instance "profile-sofia-res-public"
41  *                      <Match>
42  *                              Instance "calls-in"
43  *                              Regex "CALLS-IN\\s+([0-9]+)"
44  *                              DSType "GaugeLast"
45  *                              Type "gauge"
46  *                      </Match>
47  *              </Command>
48  *      </Plugin>
49  */
50
51 /*
52  * Data types
53  */
54 struct fs_match_s;
55 typedef struct fs_match_s fs_match_t;
56 struct fs_match_s
57 {
58         char *regex;
59         int dstype;
60         char *type;
61         char *instance;
62         cu_match_t *match;
63         fs_match_t *next;
64 };
65
66 struct fs_command_s;
67 typedef struct fs_command_s fs_command_t;
68 struct fs_command_s
69 {
70         char *line;             /* "api sofia status profile res-public" */
71         char *instance;         /* "profile-sofia-res-public" */
72         char *buffer;           /* <output from esl command as a char*> */
73         size_t buffer_size;     /* strlen(*buffer)+3 */
74         size_t buffer_fill;     /* 0 or 1 */
75         fs_match_t *matches;
76         fs_command_t *next;
77 };
78
79 static fs_command_t *fs_commands_g = NULL;
80
81 static char *fs_host = NULL;
82 static char *fs_port = NULL;
83 static char *fs_pass = NULL;
84
85 static esl_handle_t esl_handle = {{0}};
86 /* static int thread_running = 0; for when subscribing to esl events */
87
88 /*
89  * Private functions
90  */
91
92 static int fs_config_add_string (const char *name, char **dest, oconfig_item_t *ci)
93 {
94         if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
95         {
96                 WARNING ("freeswitch plugin: '%s' needs exactly one string argument.", name);
97                 return (-1);
98         }
99
100         sfree (*dest);
101         *dest = strdup (ci->values[0].value.string);
102         if (*dest == NULL)
103                 return (-1);
104
105         return (0);
106 } /* int fs_config_add_string */
107
108 static void fs_match_free (fs_match_t *fm)
109 {
110         if (fm == NULL)
111                 return;
112
113         sfree (fm->regex);
114         sfree (fm->type);
115         sfree (fm->instance);
116         match_destroy (fm->match);
117         fs_match_free (fm->next);
118         sfree (fm);
119 } /* void fs_match_free */
120
121 static void fs_command_free (fs_command_t *fc)
122 {
123         if (fc == NULL)
124                 return;
125
126         sfree (fc->line);
127         sfree (fc->instance);
128         sfree (fc->buffer);
129         fs_match_free (fc->matches);
130         fs_command_free (fc->next);
131         sfree (fc);
132 } /* void fs_command_free */
133
134 static int fs_config_add_match_dstype (int *dstype_ret, oconfig_item_t *ci)
135 {
136         int dstype;
137
138         if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
139         {
140                 WARNING ("freeswitch plugin: 'DSType' needs exactly one string argument.");
141                 return (-1);
142         }
143
144         if (strncasecmp ("Gauge", ci->values[0].value.string, strlen ("Gauge")) == 0)
145         {
146                 dstype = UTILS_MATCH_DS_TYPE_GAUGE;
147                 if (strcasecmp ("GaugeAverage", ci->values[0].value.string) == 0)
148                         dstype |= UTILS_MATCH_CF_GAUGE_AVERAGE;
149                 else if (strcasecmp ("GaugeMin", ci->values[0].value.string) == 0)
150                         dstype |= UTILS_MATCH_CF_GAUGE_MIN;
151                 else if (strcasecmp ("GaugeMax", ci->values[0].value.string) == 0)
152                         dstype |= UTILS_MATCH_CF_GAUGE_MAX;
153                 else if (strcasecmp ("GaugeLast", ci->values[0].value.string) == 0)
154                         dstype |= UTILS_MATCH_CF_GAUGE_LAST;
155                 else
156                         dstype = 0;
157         }
158         else if (strncasecmp ("Counter", ci->values[0].value.string, strlen ("Counter")) == 0)
159         {
160                 dstype = UTILS_MATCH_DS_TYPE_COUNTER;
161                 if (strcasecmp ("CounterSet", ci->values[0].value.string) == 0)
162                         dstype |= UTILS_MATCH_CF_COUNTER_SET;
163                 else if (strcasecmp ("CounterAdd", ci->values[0].value.string) == 0)
164                         dstype |= UTILS_MATCH_CF_COUNTER_ADD;
165                 else if (strcasecmp ("CounterInc", ci->values[0].value.string) == 0)
166                         dstype |= UTILS_MATCH_CF_COUNTER_INC;
167                 else
168                         dstype = 0;
169         }
170         else
171         {
172                 dstype = 0;
173         }
174
175         if (dstype == 0)
176         {
177                 WARNING ("freeswitch plugin: `%s' is not a valid argument to `DSType'.",
178                 ci->values[0].value.string);
179                 return (-1);
180         }
181
182         *dstype_ret = dstype;
183         return (0);
184 } /* int fs_config_add_match_dstype */
185
186 static int fs_config_add_match (fs_command_t *fs_command, oconfig_item_t *ci)
187 {
188         fs_match_t *fs_match;
189         int status;
190         int i;
191
192         if (ci->values_num != 0)
193         {
194                 WARNING ("freeswitch plugin: Ignoring arguments for the 'Match' block.");
195         }
196
197         fs_match = (fs_match_t *) malloc (sizeof (*fs_match));
198         if (fs_match == NULL)
199         {
200                 ERROR ("freeswitch plugin: malloc failed.");
201                 return (-1);
202         }
203         memset (fs_match, 0, sizeof (*fs_match));
204
205         status = 0;
206         for (i = 0; i < ci->children_num; i++)
207         {
208                 oconfig_item_t *child = ci->children + i;
209
210                 if (strcasecmp ("Regex", child->key) == 0)
211                         status = fs_config_add_string ("Regex", &fs_match->regex, child);
212                 else if (strcasecmp ("DSType", child->key) == 0)
213                         status = fs_config_add_match_dstype (&fs_match->dstype, child);
214                 else if (strcasecmp ("Type", child->key) == 0)
215                         status = fs_config_add_string ("Type", &fs_match->type, child);
216                 else if (strcasecmp ("Instance", child->key) == 0)
217                         status = fs_config_add_string ("Instance", &fs_match->instance, child);
218                 else
219                 {
220                         WARNING ("freeswitch plugin: Option `%s' not allowed here.", child->key);
221                         status = -1;
222                 }
223
224                 if (status != 0)
225                         break;
226         } /* for (i = 0; i < ci->children_num; i++) */
227
228         while (status == 0)
229         {
230                 if (fs_match->regex == NULL)
231                 {
232                         WARNING ("freeswitch plugin: `Regex' missing in `Match' block.");
233                         status = -1;
234                 }
235
236                 if (fs_match->type == NULL)
237                 {
238                         WARNING ("freeswitch plugin: `Type' missing in `Match' block.");
239                         status = -1;
240                 }
241
242                 if (fs_match->dstype == 0)
243                 {
244                         WARNING ("freeswitch plugin: `DSType' missing in `Match' block.");
245                         status = -1;
246                 }
247
248                 break;
249         } /* while (status == 0) */
250
251         if (status != 0)
252                 return (status);
253
254         fs_match->match = match_create_simple (fs_match->regex, fs_match->dstype);
255         if (fs_match->match == NULL)
256         {
257                 ERROR ("freeswitch plugin: tail_match_add_match_simple failed.");
258                 fs_match_free (fs_match);
259                 return (-1);
260         }
261         else
262         {
263                 fs_match_t *prev;
264
265                 prev = fs_command->matches;
266                 while ((prev != NULL) && (prev->next != NULL))
267                         prev = prev->next;
268
269                 if (prev == NULL)
270                         fs_command->matches = fs_match;
271                 else
272                         prev->next = fs_match;
273         }
274
275         return (0);
276 } /* int fs_config_add_match */
277
278 static int fs_config_add_command (oconfig_item_t *ci)
279 {
280         fs_command_t *command;
281         int status;
282         int i;
283
284         if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
285         {
286                 WARNING ("freeswitch plugin: 'Command' blocks need exactly one string argument.");
287                 return (-1);
288         }
289
290         command = (fs_command_t *) malloc (sizeof (*command));
291         if (command == NULL)
292         {
293                 ERROR ("freeswitch plugin: malloc failed.");
294                 return (-1);
295         }
296         memset (command, 0, sizeof (*command));
297
298         command->line = NULL;
299         command->line = strdup (ci->values[0].value.string);
300
301         if (command->line == NULL)
302         {
303                 ERROR ("freeswitch plugin: strdup failed.");
304                 sfree (command);
305                 return (-1);
306         }
307
308         /* Process all children */
309         status = 0;
310         for (i = 0; i < ci->children_num; i++)
311         {
312                 oconfig_item_t *child = ci->children + i;
313
314                 if (strcasecmp ("Instance", child->key) == 0)
315                         status = fs_config_add_string ("Instance", &command->instance, child);
316                 else if (strcasecmp ("Match", child->key) == 0)
317                         fs_config_add_match (command, child);
318                 else
319                 {
320                         WARNING ("freeswitch plugin: Option '%s' not allowed here.", child->key);
321                         status = -1;
322                 }
323
324                 if (status != 0)
325                         break;
326         }
327
328         if (status != 0)
329         {
330                 fs_command_free (command);
331                 return (status);
332         }
333
334         /* Add the new command to the linked list */
335         if (fs_commands_g == NULL)
336                 fs_commands_g = command;
337         else
338         {
339                 fs_command_t *prev;
340
341                 prev = fs_commands_g;
342                 while ((prev != NULL) && (prev->next != NULL))
343                         prev = prev->next;
344                 prev->next = command;
345         }
346
347         return (0);
348 } /* int fs_config_add_command */
349
350 static int fs_complex_config (oconfig_item_t *ci)
351 {
352         int success;
353         int errors;
354         int status;
355         int i;
356
357         success = 0;
358         errors = 0;
359
360         for (i = 0; i < ci->children_num; i++)
361         {
362                 oconfig_item_t *child = ci->children + i;
363
364                 /* FIXME: Don't simply dereference the
365                  * child->values[0].value.string pointer! */
366                 if (strcasecmp ("Host", child->key) == 0)
367                 {
368                         if (fs_host != NULL)
369                                 free (fs_host);
370                         fs_host = strdup(child->values[0].value.string);
371                 }
372                 else if (strcasecmp ("Port", child->key) == 0)
373                 {
374                         if (fs_port != NULL)
375                                 free (fs_port);
376                         fs_port = strdup(child->values[0].value.string);
377                 }
378                 else if (strcasecmp ("Password", child->key) == 0)
379                 {
380                         if (fs_pass != NULL)
381                                 free (fs_pass);
382                         fs_pass = strdup(child->values[0].value.string);
383                 }
384                 else if (strcasecmp ("Command", child->key) == 0)
385                 {
386                         status = fs_config_add_command(child);
387                         if (status == 0)
388                                 success++;
389                         else
390                                 errors++;
391                 }
392                 /* FIXME: This plugin should have a `Interval' option. */
393                 else
394                 {
395                         WARNING ("freeswitch plugin: Option '%s' not allowed here.",
396                                         child->key);
397                         errors++;
398                 }
399         }
400
401         if ((success == 0) && (errors > 0))
402         {
403                 ERROR ("freeswitch plugin: All statements failed.");
404                 return (-1);
405         }
406
407         return (0);
408 } /* int fs_complex_config */
409
410 static void fs_submit (const fs_command_t *fc,
411         const fs_match_t *fm, const cu_match_value_t *mv)
412 {
413         value_t values[1];
414         value_list_t vl = VALUE_LIST_INIT;
415
416         values[0] = mv->value;
417
418         vl.values = values;
419         vl.values_len = 1;
420         vl.time = time (NULL);
421
422         strncpy (vl.host, hostname_g, sizeof (vl.host));
423         strncpy (vl.plugin, "freeswitch", sizeof (vl.plugin));
424         strncpy (vl.plugin_instance, fc->instance, sizeof (vl.plugin_instance));
425         strncpy (vl.type, fm->type, sizeof (vl.type));
426         strncpy (vl.type_instance, fm->instance, sizeof (vl.type_instance));
427
428         plugin_dispatch_values (&vl);
429 } /* void fs_submit */
430
431 static int fs_read_command (fs_command_t *fc)
432 {
433         fs_match_t *fm;
434         int status;
435
436         /* can't the following be done nicer ? */
437         char *line;
438         line = (char *) malloc (strlen(fc->line)+3);
439         snprintf(line, strlen(fc->line)+3, "%s\n\n", fc->line);
440         esl_send_recv(&esl_handle, line);
441
442         fc->buffer_fill = 0;
443
444         if (esl_handle.last_sr_event && esl_handle.last_sr_event->body)
445         {
446                 sfree(fc->buffer);
447                 fc->buffer = strdup(esl_handle.last_sr_event->body);
448                 fc->buffer_size = strlen(fc->buffer);
449                 fc->buffer_fill = 1;
450         }
451
452         for (fm = fc->matches; fm != NULL; fm = fm->next)
453         {
454                 cu_match_value_t *mv;
455
456                 status = match_apply (fm->match, fc->buffer);
457                 if (status != 0)
458                 {
459                         WARNING ("freeswitch plugin: match_apply failed.");
460                         continue;
461                 }
462
463                 mv = match_get_user_data (fm->match);
464                 if (mv == NULL)
465                 {
466                         WARNING ("freeswitch plugin: match_get_user_data returned NULL.");
467                         continue;
468                 }
469
470                 fs_submit (fc, fm, mv);
471         } /* for (fm = fc->matches; fm != NULL; fm = fm->next) */
472
473         return (0);
474 } /* int fs_read_command */
475
476 static int fs_read (void)
477 {
478         fs_command_t *fc;
479
480         for (fc = fs_commands_g; fc != NULL; fc = fc->next)
481                 fs_read_command (fc);
482
483         return (0);
484 } /* int fs_read */
485
486 #if 0
487 static void *msg_thread_run(esl_thread_t *me, void *obj)
488 {
489         esl_handle_t *esl_handle = (esl_handle_t *) obj;
490         thread_running = 1;
491
492         /* Maybe do some more in this loop later, like receive subscribed
493          * events, and create statistics of them see fs_cli.c function static
494          * void *msg_thread_run(), around line 198 */
495         while (thread_running && esl_handle->connected)
496         {
497                 esl_status_t status = esl_recv_event_timed(esl_handle, 10, 1, NULL);
498                 if (status == ESL_FAIL)
499                 {
500                         /* TODO FIXME
501                         DEBUG ("Disconnected [%s]\n", ESL_LOG_WARNING);
502                         */
503                         DEBUG ("Disconnected [%s]\n", "ESL_LOG_WARNING");
504                         thread_running = 0;
505                 }
506                 usleep(1000);
507         }
508
509         thread_running = 0;
510         return (NULL);
511 } */ /* void *msg_thread_run */
512 #endif
513
514 static int fs_init (void)
515 {
516         /* Set some default configuration variables */
517         if (fs_host == NULL) fs_host = FS_DEF_HOST;
518         if (fs_port == NULL) fs_port = FS_DEF_PORT;
519         if (fs_pass == NULL) fs_pass = FS_DEF_PASS;
520
521         /* Connect to FreeSWITCH over ESL */
522         DEBUG ("freeswitch plugin: making ESL connection to %s %s %s\n",
523                         fs_host, fs_port, fs_pass);
524         if (esl_connect(&esl_handle, fs_host, atoi(fs_port), fs_pass))
525         {
526                 ERROR ("freeswitch plugin: connection failed [%s]",
527                                 esl_handle.err);
528                 return (-1);
529         }
530
531         /* Start a seperate thread for incoming events here */
532         /* esl_thread_create_detached(msg_thread_run, &esl_handle); */
533
534         return(0);
535 } /* int fs_init */
536
537 static int fs_shutdown (void)
538 {
539         DEBUG ("freeswitch plugin: disconnecting");
540         if (esl_handle.connected)
541                 esl_disconnect(&esl_handle);
542         fs_command_free (fs_commands_g);
543         fs_commands_g = NULL;
544         return (0);
545 } /* int fs_shutdown */
546
547 void module_register (void)
548 {
549         plugin_register_complex_config ("freeswitch", fs_complex_config);
550         plugin_register_init ("freeswitch", fs_init);
551         plugin_register_read ("freeswitch", fs_read);
552         plugin_register_shutdown ("freeswitch", fs_shutdown);
553 } /* void module_register */