ascent plugin: New plugin to collect AscentEmu server statistics.
[collectd.git] / src / ascent.c
1 /**
2  * collectd - src/ascent.c
3  * Copyright (C) 2008  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  **/
21
22 #include "collectd.h"
23 #include "common.h"
24 #include "plugin.h"
25 #include "configfile.h"
26
27 #include <curl/curl.h>
28 #include <libxml/parser.h>
29
30 static char *url    = NULL;
31 static char *user   = NULL;
32 static char *pass   = NULL;
33 static char *cacert = NULL;
34
35 static CURL *curl = NULL;
36
37 static char  *ascent_buffer = NULL;
38 static size_t ascent_buffer_size = 0;
39 static size_t ascent_buffer_fill = 0;
40 static char   ascent_curl_error[CURL_ERROR_SIZE];
41
42 static const char *config_keys[] =
43 {
44   "URL",
45   "User",
46   "Password",
47   "CACert"
48 };
49 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
50
51 static size_t ascent_curl_callback (void *buf, size_t size, size_t nmemb, /* {{{ */
52     void *stream)
53 {
54   size_t len = size * nmemb;
55
56   if (len <= 0)
57     return (len);
58
59   if ((ascent_buffer_fill + len) >= ascent_buffer_size)
60   {
61     char *temp;
62
63     temp = (char *) realloc (ascent_buffer,
64         ascent_buffer_fill + len + 1);
65     if (temp == NULL)
66     {
67       ERROR ("ascent plugin: realloc failed.");
68       return (0);
69     }
70     ascent_buffer = temp;
71     ascent_buffer_size = ascent_buffer_fill + len + 1;
72   }
73
74   memcpy (ascent_buffer + ascent_buffer_fill, (char *) buf, len);
75   ascent_buffer_fill += len;
76   ascent_buffer[ascent_buffer_fill] = 0;
77
78   return (len);
79 } /* }}} size_t ascent_curl_callback */
80
81 static int ascent_xml_submit_gauge (xmlDoc *doc, xmlNode *node, /* {{{ */
82     const char *plugin_instance, const char *type, const char *type_instance)
83 {
84   char *str_ptr;
85
86   value_t values[1];
87   value_list_t vl = VALUE_LIST_INIT;
88
89   str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
90   if (str_ptr == NULL)
91   {
92     ERROR ("ascent plugin: ascent_xml_submit_gauge: xmlNodeListGetString failed.");
93     return (-1);
94   }
95
96   if (strcasecmp ("N/A", str_ptr) == 0)
97     values[0].gauge = NAN;
98   else
99   {
100     char *end_ptr = NULL;
101     values[0].gauge = strtod (str_ptr, &end_ptr);
102     if (str_ptr == end_ptr)
103     {
104       ERROR ("ascent plugin: ascent_xml_submit_gauge: strtod failed.");
105       return (-1);
106     }
107   }
108
109   vl.values = values;
110   vl.values_len = 1;
111   vl.time = time (NULL);
112   strcpy (vl.host, hostname_g);
113   strcpy (vl.plugin, "ascent");
114
115   if (plugin_instance != NULL)
116     sstrncpy (vl.plugin_instance, plugin_instance,
117         sizeof (vl.plugin_instance));
118
119   if (type_instance != NULL)
120     sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
121
122   plugin_dispatch_values (type, &vl);
123 } /* }}} int ascent_xml_submit_gauge */
124
125 static int ascent_xml_status (xmlDoc *doc, xmlNode *node) /* {{{ */
126 {
127   xmlNode *child;
128
129   for (child = node->xmlChildrenNode; child != NULL; child = child->next)
130   {
131     if ((xmlStrcmp ((const xmlChar *) "comment", child->name) == 0)
132         || (xmlStrcmp ((const xmlChar *) "text", child->name) == 0))
133       /* ignore */;
134     else if (xmlStrcmp ((const xmlChar *) "alliance", child->name) == 0)
135       ascent_xml_submit_gauge (doc, child, NULL, "players", "alliance");
136     else if (xmlStrcmp ((const xmlChar *) "horde", child->name) == 0)
137       ascent_xml_submit_gauge (doc, child, NULL, "players", "horde");
138     else if (xmlStrcmp ((const xmlChar *) "qplayers", child->name) == 0)
139       ascent_xml_submit_gauge (doc, child, NULL, "players", "queued");
140     else
141     {
142       WARNING ("ascent plugin: ascent_xml_status: Unknown tag: %s", child->name);
143     }
144   } /* for (child) */
145
146   return (0);
147 } /* }}} int ascent_xml_status */
148
149 static int ascent_xml (const char *data) /* {{{ */
150 {
151   xmlDoc *doc;
152   xmlNode *cur;
153   xmlNode *child;
154
155 #if 0
156   doc = xmlParseMemory (data, strlen (data),
157       /* URL = */ "ascent.xml",
158       /* encoding = */ NULL,
159       /* options = */ 0);
160 #else
161   doc = xmlParseMemory (data, strlen (data));
162 #endif
163   if (doc == NULL)
164   {
165     ERROR ("ascent plugin: xmlParseMemory failed.");
166     return (-1);
167   }
168
169   cur = xmlDocGetRootElement (doc);
170   if (cur == NULL)
171   {
172     ERROR ("ascent plugin: XML document is empty.");
173     xmlFreeDoc (doc);
174     return (-1);
175   }
176
177   if (xmlStrcmp ((const xmlChar *) "serverpage", cur->name) != 0)
178   {
179     ERROR ("ascent plugin: XML root element is not \"serverpage\".");
180     xmlFreeDoc (doc);
181     return (-1);
182   }
183
184   for (child = cur->xmlChildrenNode; child != NULL; child = child->next)
185   {
186     if ((xmlStrcmp ((const xmlChar *) "comment", child->name) == 0)
187         || (xmlStrcmp ((const xmlChar *) "text", child->name) == 0))
188       /* ignore */;
189     else if (xmlStrcmp ((const xmlChar *) "status", child->name) == 0)
190       ascent_xml_status (doc, child);
191     else if (xmlStrcmp ((const xmlChar *) "instances", child->name) == 0)
192       /* ignore for now */;
193     else if (xmlStrcmp ((const xmlChar *) "gms", child->name) == 0)
194       /* ignore for now */;
195     else if (xmlStrcmp ((const xmlChar *) "sessions", child->name) == 0)
196       /* ignore for now */;
197     else
198     {
199       WARNING ("ascent plugin: ascent_xml: Unknown tag: %s", child->name);
200     }
201   } /* for (child) */
202
203   xmlFreeDoc (doc);
204   return (0);
205 } /* }}} int ascent_xml */
206
207 static int config_set (char **var, const char *value) /* {{{ */
208 {
209   if (*var != NULL)
210   {
211     free (*var);
212     *var = NULL;
213   }
214
215   if ((*var = strdup (value)) == NULL)
216     return (1);
217   else
218     return (0);
219 } /* }}} int config_set */
220
221 static int ascent_config (const char *key, const char *value) /* {{{ */
222 {
223   if (strcasecmp (key, "URL") == 0)
224     return (config_set (&url, value));
225   else if (strcasecmp (key, "User") == 0)
226     return (config_set (&user, value));
227   else if (strcasecmp (key, "Password") == 0)
228     return (config_set (&pass, value));
229   else if (strcasecmp (key, "CACert") == 0)
230     return (config_set (&cacert, value));
231   else
232     return (-1);
233 } /* }}} int ascent_config */
234
235 static int ascent_init (void) /* {{{ */
236 {
237   static char credentials[1024];
238
239   if (url == NULL)
240   {
241     WARNING ("ascent plugin: ascent_init: No URL configured, "
242         "returning an error.");
243     return (-1);
244   }
245
246   if (curl != NULL)
247   {
248     curl_easy_cleanup (curl);
249   }
250
251   if ((curl = curl_easy_init ()) == NULL)
252   {
253     ERROR ("ascent plugin: ascent_init: curl_easy_init failed.");
254     return (-1);
255   }
256
257   curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, ascent_curl_callback);
258   curl_easy_setopt (curl, CURLOPT_USERAGENT, PACKAGE_NAME"/"PACKAGE_VERSION);
259   curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, ascent_curl_error);
260
261   if (user != NULL)
262   {
263     int status;
264
265     status = snprintf (credentials, sizeof (credentials), "%s:%s",
266         user, (pass == NULL) ? "" : pass);
267     if (status >= sizeof (credentials))
268     {
269       ERROR ("ascent plugin: ascent_init: Returning an error because the "
270           "credentials have been truncated.");
271       return (-1);
272     }
273     credentials[sizeof (credentials) - 1] = '\0';
274
275     curl_easy_setopt (curl, CURLOPT_USERPWD, credentials);
276   }
277
278   curl_easy_setopt (curl, CURLOPT_URL, url);
279
280   if (cacert != NULL)
281     curl_easy_setopt (curl, CURLOPT_CAINFO, cacert);
282
283   return (0);
284 } /* }}} int ascent_init */
285
286 static int ascent_read (void) /* {{{ */
287 {
288   int status;
289
290   if (curl == NULL)
291   {
292     ERROR ("ascent plugin: I don't have a CURL object.");
293     return (-1);
294   }
295
296   if (url == NULL)
297   {
298     ERROR ("ascent plugin: No URL has been configured.");
299     return (-1);
300   }
301
302   ascent_buffer_fill = 0;
303   if (curl_easy_perform (curl) != 0)
304   {
305     ERROR ("ascent plugin: curl_easy_perform failed: %s",
306         ascent_curl_error);
307     return (-1);
308   }
309
310   status = ascent_xml (ascent_buffer);
311   if (status != 0)
312     return (-1);
313   else
314     return (0);
315 } /* }}} int ascent_read */
316
317 void module_register (void)
318 {
319   plugin_register_config ("ascent", ascent_config, config_keys, config_keys_num);
320   plugin_register_init ("ascent", ascent_init);
321   plugin_register_read ("ascent", ascent_read);
322 } /* void module_register */
323
324 /* vim: set sw=2 sts=2 ts=8 et fdm=marker : */