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