├── LICENSE.txt ├── README.md ├── mongodb.py └── types.db /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Sebastien Estienne 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | collectd-mongodb is a [collectd](http://www.collectd.org/) plugin that collects statistics from a MongoDB server. 4 | 5 | This plugin is a direct port of the MongoDB C plugin that will be part of collectd 5.1, it works with Collectd 4.9.x and 4.10.x. 6 | 7 | # Requirements 8 | 9 | * Collectd 4.9 or later (for the Python plugin) 10 | * Python 2.4 or later 11 | * Python MongoDB driver 2.4 or later (https://github.com/mongodb/mongo-python-driver) 12 | 13 | # Configuration 14 | 15 | The plugin has some configuration options even though none are mandatory. This is done by passing parameters via the config section in your Collectd config. The following parameters are recognized: 16 | 17 | * User - the username for authentication 18 | * Password - the password for authentication 19 | * Host - hostname or IP address of the mongodb server defaults to 127.0.0.1 20 | * Port - the port of the mongodb server defaults to 27017 21 | * Database - the databases you want to monitor defaults to "admin". You can provide more than one database. Note that the first database _must_ be "admin", as it is used to perform a serverStatus() 22 | 23 | The following is an example Collectd configuration for this plugin: 24 | 25 | 26 | Globals true 27 | 28 | 29 | 30 | # mongodb.py is at path /opt/collectd-plugins/mongodb.py 31 | ModulePath "/opt/collectd-plugins/" 32 | 33 | Import "mongodb" 34 | 35 | Host "127.0.0.1" 36 | Password "password" 37 | Database "admin" "db-prod" "db-dev" 38 | 39 | 40 | 41 | The data-sets in types.db need to be added to the types.db file given by the collectd.conf TypesDB directive. See the types.db(5) man page for more information. 42 | 43 | If you're monitoring a secured MongoDB deployment, declaring a user with minimal read-only roles is a good practice, such as : 44 | 45 | 46 | db.createUser( { 47 | user: "collectd", 48 | pwd: "collectd", 49 | roles: [ { role: "readAnyDatabase", db: "admin" }, { role: "clusterMonitor", db: "admin" } ] 50 | }); 51 | 52 | -------------------------------------------------------------------------------- /mongodb.py: -------------------------------------------------------------------------------- 1 | # 2 | # Plugin to collectd statistics from MongoDB 3 | # 4 | 5 | import collectd 6 | from pymongo import MongoClient 7 | from pymongo.read_preferences import ReadPreference 8 | from distutils.version import LooseVersion as V 9 | 10 | 11 | class MongoDB(object): 12 | 13 | def __init__(self): 14 | self.plugin_name = "mongo" 15 | self.mongo_host = "127.0.0.1" 16 | self.mongo_port = 27017 17 | self.mongo_db = ["admin", ] 18 | self.mongo_user = None 19 | self.mongo_password = None 20 | 21 | self.lockTotalTime = None 22 | self.lockTime = None 23 | self.accesses = None 24 | self.misses = None 25 | 26 | def submit(self, type, instance, value, db=None): 27 | if db: 28 | plugin_instance = '%s-%s' % (self.mongo_port, db) 29 | else: 30 | plugin_instance = str(self.mongo_port) 31 | v = collectd.Values() 32 | v.plugin = self.plugin_name 33 | v.plugin_instance = plugin_instance 34 | v.type = type 35 | v.type_instance = instance 36 | v.values = [value, ] 37 | v.dispatch() 38 | 39 | def get_db_and_collection_stats(self): 40 | con = MongoClient(host=self.mongo_host, port=self.mongo_port, read_preference=ReadPreference.SECONDARY) 41 | db = con[self.mongo_db[0]] 42 | if self.mongo_user and self.mongo_password: 43 | db.authenticate(self.mongo_user, self.mongo_password) 44 | server_status = db.command('serverStatus') 45 | 46 | version = server_status['version'] 47 | at_least_2_4 = V(version) >= V('2.4.0') 48 | 49 | # operations 50 | for k, v in server_status['opcounters'].items(): 51 | self.submit('total_operations', k, v) 52 | 53 | # memory 54 | for t in ['resident', 'virtual', 'mapped']: 55 | self.submit('memory', t, server_status['mem'][t]) 56 | 57 | # connections 58 | self.submit('connections', 'current', server_status['connections']['current']) 59 | if 'available' in server_status['connections']: 60 | self.submit('connections', 'available', server_status['connections']['available']) 61 | if 'totalCreated' in server_status['connections']: 62 | self.submit('connections', 'totalCreated', server_status['connections']['totalCreated']) 63 | 64 | # network 65 | if 'network' in server_status: 66 | for t in ['bytesIn', 'bytesOut', 'numRequests']: 67 | self.submit('bytes', t, server_status['network'][t]) 68 | 69 | # locks 70 | if 'lockTime' in server_status['globalLock']: 71 | if self.lockTotalTime is not None and self.lockTime is not None: 72 | if self.lockTime == server_status['globalLock']['lockTime']: 73 | value = 0.0 74 | else: 75 | value = float(server_status['globalLock']['lockTime'] - self.lockTime) * 100.0 / float(server_status['globalLock']['totalTime'] - self.lockTotalTime) 76 | self.submit('percent', 'lock_ratio', value) 77 | 78 | self.lockTime = server_status['globalLock']['lockTime'] 79 | self.lockTotalTime = server_status['globalLock']['totalTime'] 80 | 81 | # indexes 82 | if 'indexCounters' in server_status: 83 | accesses = None 84 | misses = None 85 | index_counters = server_status['indexCounters'] if at_least_2_4 else server_status['indexCounters']['btree'] 86 | 87 | if self.accesses is not None: 88 | accesses = index_counters['accesses'] - self.accesses 89 | if accesses < 0: 90 | accesses = None 91 | misses = (index_counters['misses'] or 0) - (self.misses or 0) 92 | if misses < 0: 93 | misses = None 94 | if accesses and misses is not None: 95 | self.submit('cache_ratio', 'cache_misses', int(misses * 100 / float(accesses))) 96 | else: 97 | self.submit('cache_ratio', 'cache_misses', 0) 98 | self.accesses = index_counters['accesses'] 99 | self.misses = index_counters['misses'] 100 | 101 | for mongo_db in self.mongo_db: 102 | db = con[mongo_db] 103 | if self.mongo_user and self.mongo_password: 104 | con[self.mongo_db[0]].authenticate(self.mongo_user, self.mongo_password) 105 | db_stats = db.command('dbstats') 106 | 107 | # stats counts 108 | self.submit('counter', 'object_count', db_stats['objects'], mongo_db) 109 | self.submit('counter', 'collections', db_stats['collections'], mongo_db) 110 | self.submit('counter', 'num_extents', db_stats['numExtents'], mongo_db) 111 | self.submit('counter', 'indexes', db_stats['indexes'], mongo_db) 112 | 113 | # stats sizes 114 | self.submit('file_size', 'storage', db_stats['storageSize'], mongo_db) 115 | self.submit('file_size', 'index', db_stats['indexSize'], mongo_db) 116 | self.submit('file_size', 'data', db_stats['dataSize'], mongo_db) 117 | 118 | # collection stats 119 | collections = db.collection_names() 120 | for collection in collections: 121 | collection_stats = db.command('collstats', collection) 122 | if 'wiredTiger' in collection_stats: 123 | if 'cursor' in collection_stats['wiredTiger']: 124 | for k, v in collection_stats['wiredTiger']['cursor'].items(): 125 | self.submit('collection_stats', (collection + '-' + k), v, mongo_db) 126 | 127 | con.close() 128 | 129 | def config(self, obj): 130 | for node in obj.children: 131 | if node.key == 'Port': 132 | self.mongo_port = int(node.values[0]) 133 | elif node.key == 'Host': 134 | self.mongo_host = node.values[0] 135 | elif node.key == 'User': 136 | self.mongo_user = node.values[0] 137 | elif node.key == 'Password': 138 | self.mongo_password = node.values[0] 139 | elif node.key == 'Database': 140 | self.mongo_db = node.values 141 | else: 142 | collectd.warning("mongodb plugin: Unkown configuration key %s" % node.key) 143 | 144 | mongodb = MongoDB() 145 | collectd.register_read(mongodb.get_db_and_collection_stats) 146 | collectd.register_config(mongodb.config) 147 | -------------------------------------------------------------------------------- /types.db: -------------------------------------------------------------------------------- 1 | cache_ratio value:GAUGE:0:100 2 | connections value:COUNTER:0:U 3 | counter value:COUNTER:U:U 4 | file_size bytes:GAUGE:0:U 5 | memory value:GAUGE:0:281474976710656 6 | percent percent:GAUGE:0:100.1 7 | total_operations value:DERIVE:0:U 8 | collection_stats value:COUNTER:U:U 9 | --------------------------------------------------------------------------------