7d72063538f45e1c40260f2a7ec793c7bb0f5e7f
[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 <stdio.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <errno.h>
26 #include <mongo/mongo.h>
27 #include "collectd.h"
28 #include "common.h" 
29 #include "plugin.h" 
30
31 #define MC_PLUGIN_NAME "mongo"
32 #define MC_MONGO_DEF_HOST "127.0.0.1"
33 #define MC_MONGO_DEF_PORT 27017 
34 #define MC_MONGO_DEF_DB "admin"
35 #define MC_MIN_PORT 1
36 #define MC_MAX_PORT 65535 
37 #define SUCCESS 0 
38 #define FAILURE -1 
39
40 static char *mc_user = NULL;
41 static char *mc_password = NULL;
42 static char *mc_db = NULL; 
43 static char *mc_host = NULL;
44 static int  mc_port = MC_MONGO_DEF_PORT;
45
46 static mongo_connection conn[1];
47
48 static const char *config_keys[] = {
49         "User",
50         "Password",
51         "Database",
52         "Host",
53         "Port"
54 };
55
56 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
57
58 static void submit(const char *type, const char *instance, value_t *values, size_t values_len) {
59     INFO("Submiting [%d] for type [%s]",values_len,type);
60         value_list_t v = VALUE_LIST_INIT;
61
62         v.values = values;
63         v.values_len = values_len;
64
65     if( instance != NULL ) {
66        sstrncpy(v.type_instance,instance,sizeof(v.type_instance)); 
67     }
68
69     sstrncpy(v.host, hostname_g, sizeof(v.host));
70     sstrncpy(v.plugin, MC_PLUGIN_NAME, sizeof(v.plugin));
71     char port[12];
72     sprintf(port, "%d",mc_port);
73     sstrncpy(v.plugin_instance, port, sizeof(v.plugin_instance));
74     sstrncpy(v.type, type, sizeof(v.type));
75
76     plugin_dispatch_values(&v);
77 }
78
79 static void submit_counter(const char *type, const char *instance, int64_t counter ) {
80         value_t v[1];
81         v[0].counter = counter;
82         submit(type, instance, v, STATIC_ARRAY_SIZE (v));
83 }
84
85 static void submit_gauge(const char *type, const char *instance, gauge_t gauge ) {
86         value_t v[1];
87         v[0].gauge = gauge;
88         submit(type, instance, v, STATIC_ARRAY_SIZE (v));
89 }
90
91
92 static void handle_opcounters(bson* obj) {
93     INFO("handle op counters");
94     bson_iterator it;
95     if(bson_find(&it, obj, "opcounters")) {
96         bson subobj;
97         bson_iterator_subobject(&it, &subobj);
98         bson_iterator it2;
99         bson_iterator_init(&it2, subobj.data);
100
101         int64_t insert = 0;
102         int64_t query = 0;
103         int64_t delete = 0;
104         int64_t getmore = 0;
105         int64_t command = 0;
106
107         while(bson_iterator_next(&it2)){
108             if(strcmp(bson_iterator_key(&it2),"insert") == 0) {
109                 insert = bson_iterator_long(&it2);
110             }
111             if(strcmp(bson_iterator_key(&it2),"query") == 0) {
112                 query = bson_iterator_long(&it2);
113             }
114             if(strcmp(bson_iterator_key(&it2),"delete") == 0) {
115                 delete = bson_iterator_long(&it2);
116             }
117             if(strcmp(bson_iterator_key(&it2),"getmore") == 0) {
118                 getmore = bson_iterator_long(&it2);
119             }
120             if(strcmp(bson_iterator_key(&it2),"command") == 0) {
121                 command = bson_iterator_long(&it2);
122             }
123         }
124
125         bson_destroy(&subobj);
126
127         submit_gauge("mongo_counter", "indert", insert );
128         submit_gauge("mongo_counter", "query", query );
129         submit_gauge("mongo_counter", "delete", delete );
130         submit_gauge("mongo_counter", "getmore", getmore );
131         submit_gauge("mongo_counter", "command", command );
132         submit_gauge("mongo_counter", "insert", insert );
133     }
134 }
135
136 static void handle_mem(bson* obj) {
137     INFO("handle mem");
138     bson_iterator it;
139     if(bson_find(&it, obj, "mem")) {
140         bson subobj;
141         bson_iterator_subobject(&it, &subobj);
142         bson_iterator it2;
143         bson_iterator_init(&it2, subobj.data);
144         int64_t resident = 0;
145         int64_t virtual = 0;
146         int64_t mapped = 0;
147         while(bson_iterator_next(&it2)) {
148             if(strcmp(bson_iterator_key(&it2),"resident") == 0) {
149                 resident = bson_iterator_long(&it2);
150             }
151             if(strcmp(bson_iterator_key(&it2),"virtual") == 0) {
152                 virtual = bson_iterator_long(&it2);
153             }
154             if(strcmp(bson_iterator_key(&it2),"mapped") == 0) {
155                 mapped = bson_iterator_long(&it2);
156             }
157         }
158         value_t values[3];
159         values[0].gauge = resident;
160         values[1].gauge = virtual;
161         values[2].gauge = mapped;
162         submit_gauge("memory", "resident", resident );
163         submit_gauge("memory", "virtual", virtual );
164         submit_gauge("memory", "mapped", mapped );
165         bson_destroy(&subobj);
166     }
167 }
168
169 static void handle_connections(bson* obj) {
170     INFO("handle connections");
171     bson_iterator it;
172     if(bson_find(&it, obj, "connections")) {
173     bson subobj;
174         bson_iterator_subobject(&it, &subobj);
175         bson_iterator it2;
176         bson_iterator_init(&it2, subobj.data);
177         while(bson_iterator_next(&it2)) {
178             if(strcmp(bson_iterator_key(&it2),"current") == 0) {
179                 submit_gauge("connections", "connections", bson_iterator_int(&it2));
180                 break;
181             }
182         }
183         bson_destroy(&subobj);
184     }
185 }
186
187 static void handle_lock(bson* obj) {
188     INFO("handle lock");
189     bson_iterator it;
190     if(bson_find(&it, obj, "globalLock")) {
191         bson subobj;
192         bson_iterator_subobject(&it, &subobj);
193         bson_iterator it2;
194         bson_iterator_init(&it2, subobj.data);
195         while(bson_iterator_next(&it2)) {
196             if(strcmp(bson_iterator_key(&it2),"ratio") == 0) {
197                 submit_gauge("percent", "lock_ratio", bson_iterator_double(&it2));
198             }
199         }
200         bson_destroy(&subobj);
201     }
202 }
203
204 static void handle_index_counters(bson* obj) {
205     INFO("handle index counters");
206     bson_iterator icit;
207     if(bson_find(&icit, obj, "indexCounters")) {
208         bson oic;
209         bson_iterator_subobject(&icit, &oic);
210         bson_iterator bit;
211         if(bson_find(&icit, &oic, "btree")) {
212             bson obt;
213             bson_iterator_subobject(&icit, &obt);
214             bson_iterator bit;
215             bson_iterator_init(&bit, oic.data);
216             int accesses; 
217             int misses;
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             double ratio = 0;
228             if( misses > 0 ) {
229                 double ratio = accesses/misses;
230             }
231             submit_gauge("cache_ratio", "cache_misses", ratio );
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     value_t values[3];
289     submit_gauge("file_size", "storage",storageSize );
290     submit_gauge("file_size", "index",indexSize );
291     submit_gauge("file_size", "data",dataSize );
292 }
293
294 static int do_stats(void) {
295     bson obj;
296
297     /* TODO: 
298
299         change this to raw runCommand
300              db.runCommand( { dbstats : 1 } ); 
301              succeeds but is getting back all zeros !?!
302         modify bson_print to print type 18
303         repro problem w/o db name - show dbs doesn't work again
304         why does db.admin.dbstats() work fine in shell?
305         implement retries ? noticed that if db is unavailable, collectd dies
306     */
307
308     if( !mongo_simple_int_command(conn, mc_db, "dbstats", 1, &obj) ) {
309         ERROR("Mongo: failed to call stats Host [%s] Port [%d] User [%s]", 
310             mc_host, mc_port, mc_user);
311         return FAILURE;
312     }
313     
314     bson_print(&obj);
315     
316     handle_stats_sizes(&obj);
317     handle_stats_counts(&obj);
318
319
320     bson_destroy(&obj);
321
322     return SUCCESS;
323 }
324
325
326 static int do_server_status(void) {
327     bson obj;
328
329     if( !mongo_simple_int_command(conn, mc_db, "serverStatus", 1, &obj) ) {
330         ERROR("Mongo: failed to call serverStatus Host [%s] Port [%d] User [%s]", 
331             mc_host, mc_port, mc_user);
332         return FAILURE;
333     }
334
335     bson_print(&obj);
336
337     handle_opcounters(&obj);
338     handle_mem(&obj);
339     handle_connections(&obj);
340     handle_index_counters(&obj);
341     handle_lock(&obj);
342
343     bson_destroy(&obj);
344     return SUCCESS;
345 }
346
347 static int mc_read(void) {
348     DEBUG("Mongo: mongo driver read"); 
349
350     if(do_server_status() != SUCCESS) {
351         ERROR("Mongo: do server status failed"); 
352         return FAILURE;
353     }
354
355     if(do_stats() != SUCCESS) {
356         ERROR("Mongo: do stats status failed"); 
357         return FAILURE;
358     }
359
360     return SUCCESS;
361 }
362
363
364 static void config_set(char** dest, const char* src ) {
365         if( *dest ) {
366             sfree(*dest);
367         }
368                 *dest= malloc(strlen(src)+1);
369         sstrncpy(*dest,src,strlen(src)+1);
370 }
371
372 static int mc_config(const char *key, const char *value) { 
373
374     DEBUG("Mongo: config key [%s] value [%s]", key, value); 
375         if(strcasecmp("Host", key) == 0) {
376         config_set(&mc_host,value);
377         return SUCCESS;
378         }
379
380         if(strcasecmp("Port", key) == 0) {
381         long l = strtol(value,NULL,10);
382
383         if((errno == ERANGE && (l == LONG_MAX || l == LONG_MIN))
384             || (errno != 0 && l == 0)) {
385             ERROR("Mongo: failed to parse Port value [%s]", value);
386             return FAILURE;
387         }
388
389         if( l < MC_MIN_PORT || l > MC_MAX_PORT ) {
390             ERROR("Mongo: failed Port value [%s] outside range [1..65535]", value);
391             return FAILURE;
392         }
393
394                 mc_port = l;
395         return SUCCESS;
396         }
397
398         if(strcasecmp("User", key) == 0) {
399         config_set(&mc_user,value);
400         return SUCCESS;
401         }
402
403         if(strcasecmp("Password", key) == 0) {
404         config_set(&mc_password,value);
405         return SUCCESS;
406         }
407
408         if(strcasecmp("Database", key) == 0) {
409         config_set(&mc_db,value);
410         return SUCCESS;
411         }
412
413     return SUCCESS;
414
415
416 static int mc_init(void) {
417
418     DEBUG("mongo driver initializing"); 
419     if( !mc_host) {
420         DEBUG("Mongo: Host not specified. Using default [%s]",MC_MONGO_DEF_HOST); 
421         config_set(&mc_host, MC_MONGO_DEF_HOST);
422     }
423
424     if( !mc_db) {
425         DEBUG("Mongo: Database not specified. Using default [%s]",MC_MONGO_DEF_DB); 
426         config_set(&mc_db, MC_MONGO_DEF_DB);
427     }
428
429     mongo_connection_options opts;
430     sstrncpy(opts.host, mc_host, sizeof(opts.host));
431     opts.port = mc_port; 
432
433     if(mongo_connect( conn , &opts )){
434         ERROR("Mongo: driver failed to connect. Host [%s] Port [%d] User [%s]", 
435                 mc_host, mc_port, mc_user);
436         return FAILURE;     
437     }
438
439     return SUCCESS;
440
441
442 static int mc_shutdown(void) {
443     DEBUG("Mongo: driver shutting down"); 
444
445     if( conn ) {
446         mongo_disconnect(conn);
447         mongo_destroy(conn);
448     }
449     if( mc_user ) {
450         sfree(mc_user);
451     }
452
453     if( mc_password ) {
454         sfree(mc_password);
455     }
456
457     if( mc_db ) {
458         sfree(mc_db);
459     }
460
461     if( mc_host ) {
462         sfree(mc_host);
463     }
464 }
465
466
467 void module_register(void)  {       
468     plugin_register_config(MC_PLUGIN_NAME, mc_config,config_keys,config_keys_num);
469     plugin_register_read(MC_PLUGIN_NAME, mc_read);
470     plugin_register_init(MC_PLUGIN_NAME, mc_init);
471     plugin_register_shutdown(MC_PLUGIN_NAME, mc_shutdown);
472     DEBUG("Mongo: driver registered"); 
473 }