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