4313e4a25227bcf7440ff564acfca0788225bbaa
[collectd.git] / src / mongodb.c
1 /**
2  * collectd - src/mongo.c
3  * Copyright (C) 2010 Ryan Cox 
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  *   Ryan Cox <ryan.a.cox@gmail.com> 
20  **/
21
22 #include "collectd.h"
23 #include "common.h" 
24 #include "plugin.h" 
25
26 #include <stdio.h>
27 #include <string.h>
28 #include <stdlib.h>
29 #include <errno.h>
30
31 #if HAVE_STDINT_H
32 # define MONGO_HAVE_STDINT 1
33 #else
34 # define MONGO_USE_LONG_LONG_INT 1
35 #endif
36 #include <mongo.h>
37
38 #define MC_PLUGIN_NAME "mongo"
39 #define MC_MONGO_DEF_HOST "127.0.0.1"
40 #define MC_MONGO_DEF_PORT 27017 
41 #define MC_MONGO_DEF_DB "admin"
42 #define MC_MIN_PORT 1
43 #define MC_MAX_PORT 65535 
44 #define SUCCESS 0 
45 #define FAILURE -1 
46
47 static char *mc_user = NULL;
48 static char *mc_password = NULL;
49 static char *mc_db = NULL; 
50 static char *mc_host = NULL;
51 static int  mc_port = MC_MONGO_DEF_PORT;
52
53 static mongo_connection mc_connection;
54 static _Bool mc_have_connection = 0;
55
56 static const char *config_keys[] = {
57         "User",
58         "Password",
59         "Database",
60         "Host",
61         "Port"
62 };
63 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
64
65 static void submit (const char *type, const char *instance,
66         value_t *values, size_t values_len)
67 {
68     value_list_t v = VALUE_LIST_INIT;
69
70     v.values = values;
71     v.values_len = values_len;
72
73     sstrncpy (v.host, hostname_g, sizeof(v.host));
74     sstrncpy (v.plugin, MC_PLUGIN_NAME, sizeof(v.plugin));
75     ssnprintf (v.plugin_instance, sizeof (v.plugin_instance), "%i", mc_port);
76     sstrncpy (v.type, type, sizeof(v.type));
77
78     if (instance != NULL)
79         sstrncpy (v.type_instance, instance, sizeof (v.type_instance)); 
80
81     plugin_dispatch_values (&v);
82 }
83
84 static void submit_gauge (const char *type, const char *instance, /* {{{ */
85         gauge_t gauge)
86 {
87         value_t v;
88
89         v.gauge = gauge;
90         submit(type, instance, &v, /* values_len = */ 1);
91 } /* }}} void submit_gauge */
92
93 static void submit_derive (const char *type, const char *instance, /* {{{ */
94         derive_t derive)
95 {
96         value_t v;
97
98         v.derive = derive;
99         submit(type, instance, &v, /* values_len = */ 1);
100 } /* }}} void submit_derive */
101
102 static int handle_opcounters (bson *obj) /* {{{ */
103 {
104     bson_type type;
105     bson subobj;
106     bson_iterator i;
107     bson_iterator j;
108
109     type = bson_find (&i, obj, "opcounters");
110     if ((type != bson_object) && (type != bson_array))
111         return (EINVAL);
112
113     bson_iterator_subobject (&i, &subobj);
114
115     bson_iterator_init (&j, subobj.data);
116     while (bson_iterator_next (&j))
117     {
118         const char *key;
119         derive_t value;
120
121         type = bson_iterator_type (&j);
122         if ((type != bson_long) && (type != bson_int))
123             continue;
124
125         key = bson_iterator_key (&j);
126         if (key == NULL)
127             continue;
128
129         value = (derive_t) bson_iterator_long (&j);
130
131         submit_derive ("total_operations", key, value);
132     }
133     bson_destroy (&subobj);
134
135     return (0);
136 } /* }}} int handle_opcounters */
137
138 static void handle_mem(bson* obj) {
139     INFO("handle mem");
140     bson_iterator it;
141     if(bson_find(&it, obj, "mem")) {
142         bson subobj;
143         bson_iterator_subobject(&it, &subobj);
144         bson_iterator it2;
145         bson_iterator_init(&it2, subobj.data);
146         int64_t resident = 0;
147         int64_t virtual = 0;
148         int64_t mapped = 0;
149         while(bson_iterator_next(&it2)) {
150             if(strcmp(bson_iterator_key(&it2),"resident") == 0) {
151                 resident = bson_iterator_long(&it2);
152             }
153             if(strcmp(bson_iterator_key(&it2),"virtual") == 0) {
154                 virtual = bson_iterator_long(&it2);
155             }
156             if(strcmp(bson_iterator_key(&it2),"mapped") == 0) {
157                 mapped = bson_iterator_long(&it2);
158             }
159         }
160         value_t values[3];
161         values[0].gauge = resident;
162         values[1].gauge = virtual;
163         values[2].gauge = mapped;
164         submit_gauge("memory", "resident", resident );
165         submit_gauge("memory", "virtual", virtual );
166         submit_gauge("memory", "mapped", mapped );
167         bson_destroy(&subobj);
168     }
169 }
170
171 static void handle_connections(bson* obj) {
172     INFO("handle connections");
173     bson_iterator it;
174     if(bson_find(&it, obj, "connections")) {
175     bson subobj;
176         bson_iterator_subobject(&it, &subobj);
177         bson_iterator it2;
178         bson_iterator_init(&it2, subobj.data);
179         while(bson_iterator_next(&it2)) {
180             if(strcmp(bson_iterator_key(&it2),"current") == 0) {
181                 submit_gauge("connections", "connections", bson_iterator_int(&it2));
182                 break;
183             }
184         }
185         bson_destroy(&subobj);
186     }
187 }
188
189 static void handle_lock(bson* obj) {
190     INFO("handle lock");
191     bson_iterator it;
192     if(bson_find(&it, obj, "globalLock")) {
193         bson subobj;
194         bson_iterator_subobject(&it, &subobj);
195         bson_iterator it2;
196         bson_iterator_init(&it2, subobj.data);
197         while(bson_iterator_next(&it2)) {
198             if(strcmp(bson_iterator_key(&it2),"ratio") == 0) {
199                 submit_gauge("percent", "lock_ratio", bson_iterator_double(&it2));
200             }
201         }
202         bson_destroy(&subobj);
203     }
204 }
205
206 static void handle_index_counters(bson* obj) {
207     INFO("handle index counters");
208     bson_iterator icit;
209     if(bson_find(&icit, obj, "indexCounters")) {
210         bson oic;
211         bson_iterator_subobject(&icit, &oic);
212         if(bson_find(&icit, &oic, "btree")) {
213             bson obt;
214             bson_iterator_subobject(&icit, &obt);
215             bson_iterator bit;
216             bson_iterator_init(&bit, oic.data);
217             int accesses; 
218             int misses;
219             double ratio;
220
221             while(bson_iterator_next(&bit)) {
222                 if(strcmp(bson_iterator_key(&bit),"accesses") == 0) {
223                     accesses = bson_iterator_int(&bit); 
224                 }
225                 if(strcmp(bson_iterator_key(&bit),"misses") == 0) {
226                     misses = bson_iterator_int(&bit); 
227                 }
228             }
229
230             ratio = NAN;
231             if (misses <= accesses)
232                 ratio = ((double) misses) / ((double) accesses);
233             submit_gauge("cache_ratio", "cache_misses", ratio );
234
235             bson_destroy(&obt);
236         }
237         bson_destroy(&oic);
238     }
239 }
240
241 static void handle_stats_counts(bson* obj) {
242
243     INFO("handle stats counts");
244     bson_iterator it;
245     bson_iterator_init(&it, obj->data);
246     int64_t collections = 0;
247     int64_t objects = 0;
248     int64_t numExtents = 0;
249     int64_t indexes = 0;
250
251     while(bson_iterator_next(&it)) {
252         if(strcmp(bson_iterator_key(&it),"collections") == 0) {
253             collections = bson_iterator_long(&it); 
254         }
255         if(strcmp(bson_iterator_key(&it),"objects") == 0) {
256             objects = bson_iterator_long(&it); 
257         }
258         if(strcmp(bson_iterator_key(&it),"numExtents") == 0) {
259             numExtents = bson_iterator_long(&it); 
260         }
261         if(strcmp(bson_iterator_key(&it),"indexes") == 0) {
262             indexes = bson_iterator_long(&it); 
263         }
264     }
265
266     submit_gauge("counter","object_count",objects);
267
268     submit_gauge("counter", "collections",collections); 
269     submit_gauge("counter", "num_extents",numExtents); 
270     submit_gauge("counter", "indexes",indexes); 
271 }
272
273 static void handle_stats_sizes(bson* obj) {
274     bson_iterator it;
275     bson_iterator_init(&it, obj->data);
276     int64_t storageSize = 0;
277     int64_t dataSize = 0;
278     int64_t indexSize = 0;
279     while(bson_iterator_next(&it)) {
280         if(strcmp(bson_iterator_key(&it),"storageSize") == 0) {
281             storageSize = bson_iterator_long(&it); 
282         }
283         if(strcmp(bson_iterator_key(&it),"dataSize") == 0) {
284             dataSize = bson_iterator_long(&it); 
285         }
286         if(strcmp(bson_iterator_key(&it),"indexSize") == 0) {
287             indexSize = bson_iterator_long(&it); 
288         }
289     }
290
291     submit_gauge ("file_size", "storage", storageSize);
292     submit_gauge ("file_size", "index", indexSize);
293     submit_gauge ("file_size", "data", dataSize);
294 }
295
296 static int do_stats(void) {
297     bson obj;
298
299     /* TODO: 
300
301         change this to raw runCommand
302              db.runCommand( { dbstats : 1 } ); 
303              succeeds but is getting back all zeros !?!
304         modify bson_print to print type 18
305         repro problem w/o db name - show dbs doesn't work again
306         why does db.admin.dbstats() work fine in shell?
307         implement retries ? noticed that if db is unavailable, collectd dies
308     */
309
310     if( !mongo_simple_int_command(&mc_connection, mc_db, "dbstats", 1, &obj) ) {
311         ERROR("Mongo: failed to call stats Host [%s] Port [%d] User [%s]", 
312             mc_host, mc_port, mc_user);
313         return FAILURE;
314     }
315     
316     bson_print(&obj);
317     
318     handle_stats_sizes(&obj);
319     handle_stats_counts(&obj);
320
321
322     bson_destroy(&obj);
323
324     return SUCCESS;
325 }
326
327
328 static int do_server_status(void) {
329     bson obj;
330
331     if( !mongo_simple_int_command(&mc_connection, mc_db, "serverStatus", 1, &obj) ) {
332         ERROR("Mongo: failed to call serverStatus Host [%s] Port [%d] User [%s]", 
333             mc_host, mc_port, mc_user);
334         return FAILURE;
335     }
336
337     bson_print(&obj);
338
339     handle_opcounters(&obj);
340     handle_mem(&obj);
341     handle_connections(&obj);
342     handle_index_counters(&obj);
343     handle_lock(&obj);
344
345     bson_destroy(&obj);
346     return SUCCESS;
347 }
348
349 static int mc_read(void) {
350     DEBUG("Mongo: mongo driver read"); 
351
352     if(do_server_status() != SUCCESS) {
353         ERROR("Mongo: do server status failed"); 
354         return FAILURE;
355     }
356
357     if(do_stats() != SUCCESS) {
358         ERROR("Mongo: do stats status failed"); 
359         return FAILURE;
360     }
361
362     return SUCCESS;
363 }
364
365
366 static void config_set(char** dest, const char* src ) {
367         if( *dest ) {
368             sfree(*dest);
369         }
370                 *dest= malloc(strlen(src)+1);
371         sstrncpy(*dest,src,strlen(src)+1);
372 }
373
374 static int mc_config(const char *key, const char *value)
375
376     DEBUG("Mongo: config key [%s] value [%s]", key, value); 
377
378     if(strcasecmp("Host", key) == 0) {
379         config_set(&mc_host,value);
380     }
381     else if(strcasecmp("Port", key) == 0)
382     {
383         int tmp;
384         
385         tmp = service_name_to_port_number (value);
386         if (tmp > 0)
387             mc_port = tmp;
388         else
389         {
390             ERROR("mongodb plugin: failed to parse Port value: %s", value);
391             return (-1);
392         }
393     }
394     else if(strcasecmp("User", key) == 0) {
395         config_set(&mc_user,value);
396     }
397     else if(strcasecmp("Password", key) == 0) {
398         config_set(&mc_password,value);
399     }
400     else if(strcasecmp("Database", key) == 0) {
401         config_set(&mc_db,value);
402     }
403     else
404     {
405         ERROR ("mongodb plugin: Unknown config option: %s", key);
406         return (-1);
407     }
408
409     return SUCCESS;
410
411
412 static int mc_init(void)
413 {
414     if (mc_have_connection)
415         return (0);
416
417     DEBUG("mongo driver initializing"); 
418     if( !mc_host) {
419         DEBUG("Mongo: Host not specified. Using default [%s]",MC_MONGO_DEF_HOST); 
420         config_set(&mc_host, MC_MONGO_DEF_HOST);
421     }
422
423     if( !mc_db) {
424         DEBUG("Mongo: Database not specified. Using default [%s]",MC_MONGO_DEF_DB); 
425         config_set(&mc_db, MC_MONGO_DEF_DB);
426     }
427
428     mongo_connection_options opts;
429     sstrncpy(opts.host, mc_host, sizeof(opts.host));
430     opts.port = mc_port; 
431
432     if(mongo_connect(&mc_connection, &opts )){
433         ERROR("Mongo: driver failed to connect. Host [%s] Port [%d] User [%s]", 
434                 mc_host, mc_port, mc_user);
435         return FAILURE;     
436     }
437
438     mc_have_connection = 1;
439     return SUCCESS;
440
441
442 static int mc_shutdown(void)
443 {
444     DEBUG("Mongo: driver shutting down"); 
445
446     if (mc_have_connection) {
447         mongo_disconnect (&mc_connection);
448         mongo_destroy (&mc_connection);
449         mc_have_connection = 0;
450     }
451
452     sfree(mc_user);
453     sfree(mc_password);
454     sfree(mc_db);
455     sfree(mc_host);
456
457     return (0);
458 }
459
460
461 void module_register(void)  {       
462     plugin_register_config (MC_PLUGIN_NAME, mc_config,
463             config_keys, config_keys_num);
464     plugin_register_read (MC_PLUGIN_NAME, mc_read);
465     plugin_register_init (MC_PLUGIN_NAME, mc_init);
466     plugin_register_shutdown (MC_PLUGIN_NAME, mc_shutdown);
467 }
468
469 /* vim: set sw=4 sts=4 et fdm=marker : */