1 # -*- coding: utf-8 -*-
2 # Description: mongodb netdata python.d module
5 from base import SimpleService
6 from copy import deepcopy
8 from pymongo import MongoClient
9 from pymongo.errors import PyMongoError
14 # default module values (can be overridden per job in `config`)
32 # charts order (can be overridden if you want less charts, or different order)
33 ORDER = ['read_operations', 'write_operations', 'active_clients', 'journaling_transactions',
34 'journaling_volume', 'background_flush_average', 'background_flush_last', 'background_flush_rate',
35 'wiredtiger_read', 'wiredtiger_write', 'cursors', 'connections', 'memory', 'page_faults',
36 'queued_requests', 'record_moves', 'wiredtiger_cache', 'wiredtiger_pages_evicted', 'asserts',
37 'dbstats_objects', 'tcmalloc_generic', 'tcmalloc_metrics', 'command_total_rate', 'command_failed_rate']
41 'options': [None, "Received read requests", "requests/s", 'throughput metrics',
42 'mongodb.read_operations', 'line'],
44 ['readWriteOper_query', 'query', 'incremental'],
45 ['readWriteOper_getmore', 'getmore', 'incremental']
48 'options': [None, "Received write requests", "requests/s", 'throughput metrics',
49 'mongodb.write_operations', 'line'],
51 ['readWriteOper_insert', 'insert', 'incremental'],
52 ['readWriteOper_update', 'update', 'incremental'],
53 ['readWriteOper_delete', 'delete', 'incremental']
56 'options': [None, "Clients with read or write operations in progress or queued", "clients",
57 'throughput metrics', 'mongodb.active_clients', 'line'],
59 ['activeClients_readers', 'readers', 'absolute'],
60 ['activeClients_writers', 'writers', 'absolute']
62 'journaling_transactions': {
63 'options': [None, "Transactions that have been written to the journal", "commits",
64 'database performance', 'mongodb.journaling_transactions', 'line'],
66 ['journalTrans_commits', 'commits', 'absolute']
68 'journaling_volume': {
69 'options': [None, "Volume of data written to the journal", "MB", 'database performance',
70 'mongodb.journaling_volume', 'line'],
72 ['journalTrans_journaled', 'volume', 'absolute', 1, 100]
74 'background_flush_average': {
75 'options': [None, "Average time taken by flushes to execute", "ms", 'database performance',
76 'mongodb.background_flush_average', 'line'],
78 ['background_flush_average', 'time', 'absolute', 1, 100]
80 'background_flush_last': {
81 'options': [None, "Time taken by the last flush operation to execute", "ms", 'database performance',
82 'mongodb.background_flush_last', 'line'],
84 ['background_flush_last', 'time', 'absolute', 1, 100]
86 'background_flush_rate': {
87 'options': [None, "Flushes rate", "flushes", 'database performance', 'mongodb.background_flush_rate', 'line'],
89 ['background_flush_rate', 'flushes', 'incremental', 1, 1]
92 'options': [None, "Read tickets in use and remaining", "tickets", 'database performance',
93 'mongodb.wiredtiger_read', 'stacked'],
95 ['wiredTigerRead_available', 'available', 'absolute', 1, 1],
96 ['wiredTigerRead_out', 'inuse', 'absolute', 1, 1]
99 'options': [None, "Write tickets in use and remaining", "tickets", 'database performance',
100 'mongodb.wiredtiger_write', 'stacked'],
102 ['wiredTigerWrite_available', 'available', 'absolute', 1, 1],
103 ['wiredTigerWrite_out', 'inuse', 'absolute', 1, 1]
106 'options': [None, "Currently openned cursors, cursors with timeout disabled and timed out cursors",
107 "cursors", 'database performance', 'mongodb.cursors', 'stacked'],
109 ['cursor_total', 'openned', 'absolute', 1, 1],
110 ['cursor_noTimeout', 'notimeout', 'absolute', 1, 1],
111 ['cursor_timedOut', 'timedout', 'incremental', 1, 1]
114 'options': [None, "Currently connected clients and unused connections", "connections",
115 'resource utilization', 'mongodb.connections', 'stacked'],
117 ['connections_available', 'unused', 'absolute', 1, 1],
118 ['connections_current', 'connected', 'absolute', 1, 1]
121 'options': [None, "Memory metrics", "MB", 'resource utilization', 'mongodb.memory', 'stacked'],
123 ['memory_virtual', 'virtual', 'absolute', 1, 1],
124 ['memory_resident', 'resident', 'absolute', 1, 1],
125 ['memory_mapped', 'mapped', 'absolute', 1, 1]
128 'options': [None, "Number of times MongoDB had to fetch data from disk", "request/s",
129 'resource utilization', 'mongodb.page_faults', 'line'],
131 ['page_faults', 'page_faults', 'incremental', 1, 1]
134 'options': [None, "Currently queued read and wrire requests", "requests", 'resource saturation',
135 'mongodb.queued_requests', 'line'],
137 ['currentQueue_readers', 'readers', 'absolute', 1, 1],
138 ['currentQueue_writers', 'writers', 'absolute', 1, 1]
141 'options': [None, "Number of times documents had to be moved on-disk", "number",
142 'resource saturation', 'mongodb.record_moves', 'line'],
144 ['record_moves', 'moves', 'incremental', 1, 1]
147 'options': [None, "Number of message, warning, regular, corresponding to errors generated"
148 " by users assertions raised", "number", 'errors (asserts)', 'mongodb.asserts', 'line'],
150 ['errors_msg', 'msg', 'incremental', 1, 1],
151 ['errors_warning', 'warning', 'incremental', 1, 1],
152 ['errors_regular', 'regular', 'incremental', 1, 1],
153 ['errors_user', 'user', 'incremental', 1, 1]
155 'wiredtiger_cache': {
156 'options': [None, "Amount of space taken by cached data and by dirty data in the cache",
157 "KB", 'resource utilization', 'mongodb.wiredtiger_cache', 'stacked'],
159 ['wiredTiger_bytes_in_cache', 'cached', 'absolute', 1, 1024],
160 ['wiredTiger_dirty_in_cache', 'dirty', 'absolute', 1, 1024]
162 'wiredtiger_pages_evicted': {
163 'options': [None, "Pages evicted from the cache",
164 "pages", 'resource utilization', 'mongodb.wiredtiger_pages_evicted', 'stacked'],
166 ['wiredTiger_unmodified_pages_evicted', 'unmodified', 'absolute', 1, 1],
167 ['wiredTiger_modified_pages_evicted', 'modified', 'absolute', 1, 1]
170 'options': [None, "Number of documents in the database among all the collections", "documents",
171 'storage size metrics', 'mongodb.dbstats_objects', 'stacked'],
174 'tcmalloc_generic': {
175 'options': [None, "Tcmalloc generic metrics", "MB", 'tcmalloc', 'mongodb.tcmalloc_generic', 'stacked'],
177 ['current_allocated_bytes', 'allocated', 'absolute', 1, 1048576],
178 ['heap_size', 'heap_size', 'absolute', 1, 1048576]
180 'tcmalloc_metrics': {
181 'options': [None, "Tcmalloc metrics", "KB", 'tcmalloc', 'mongodb.tcmalloc_metrics', 'stacked'],
183 ['central_cache_free_bytes', 'central_cache_free', 'absolute', 1, 1024],
184 ['current_total_thread_cache_bytes', 'current_total_thread_cache', 'absolute', 1, 1024],
185 ['pageheap_free_bytes', 'pageheap_free', 'absolute', 1, 1024],
186 ['pageheap_unmapped_bytes', 'pageheap_unmapped', 'absolute', 1, 1024],
187 ['thread_cache_free_bytes', 'thread_cache_free', 'absolute', 1, 1024],
188 ['transfer_cache_free_bytes', 'transfer_cache_free', 'absolute', 1, 1024]
190 'command_total_rate': {
191 'options': [None, "Commands total rate", "commands/s", 'commands', 'mongodb.command_total_rate', 'stacked'],
193 ['count_total', 'count', 'incremental', 1, 1],
194 ['createIndexes_total', 'createIndexes', 'incremental', 1, 1],
195 ['delete_total', 'delete', 'incremental', 1, 1],
196 ['eval_total', 'eval', 'incremental', 1, 1],
197 ['findAndModify_total', 'findAndModify', 'incremental', 1, 1],
198 ['insert_total', 'insert', 'incremental', 1, 1],
199 ['update_total', 'update', 'incremental', 1, 1]
201 'command_failed_rate': {
202 'options': [None, "Commands failed rate", "commands/s", 'commands', 'mongodb.command_failed_rate', 'stacked'],
204 ['count_failed', 'count', 'incremental', 1, 1],
205 ['createIndexes_failed', 'createIndexes', 'incremental', 1, 1],
206 ['delete_dailed', 'delete', 'incremental', 1, 1],
207 ['eval_failed', 'eval', 'incremental', 1, 1],
208 ['findAndModify_failed', 'findAndModify', 'incremental', 1, 1],
209 ['insert_failed', 'insert', 'incremental', 1, 1],
210 ['update_failed', 'update', 'incremental', 1, 1]
215 class Service(SimpleService):
216 def __init__(self, configuration=None, name=None):
217 SimpleService.__init__(self, configuration=configuration, name=name)
218 self.user = self.configuration.get('user')
219 self.password = self.configuration.get('pass')
220 self.host = self.configuration.get('host', '127.0.0.1')
221 self.port = self.configuration.get('port', 27017)
222 self.timeout = self.configuration.get('timeout', 100)
226 self.error('Pymongo module is needed to use mongodb.chart.py')
229 self.connection, server_status, error = self._create_connection()
234 self.repl = 'repl' in server_status
235 self.databases = self.connection.database_names()
236 self._create_charts(server_status)
240 def _create_charts(self, server_status):
242 self.order = ORDER[:]
243 self.definitions = deepcopy(CHARTS)
246 for elem in ['dur', 'backgroundFlushing', 'wiredTiger', 'tcmalloc', 'cursor', 'commands']:
247 self.ss[elem] = in_server_status(elem, server_status)
249 if not self.ss['dur']:
250 self.order.remove('journaling_transactions')
251 self.order.remove('journaling_volume')
253 if not self.ss['backgroundFlushing']:
254 self.order.remove('background_flush_average')
255 self.order.remove('background_flush_last')
257 if not self.ss['cursor']:
258 self.order.remove('cursors')
260 if not self.ss['wiredTiger']:
261 self.order.remove('wiredtiger_write')
262 self.order.remove('wiredtiger_read')
263 self.order.remove('wiredtiger_cache')
265 if not self.ss['tcmalloc']:
266 self.order.remove('tcmalloc_generic')
267 self.order.remove('tcmalloc_metrics')
269 if not self.ss['commands']:
270 self.order.remove('command_total_rate')
271 self.order.remove('command_failed_rate')
273 for dbase in self.databases:
274 self.order.append('_'.join([dbase, 'dbstats']))
275 self.definitions['_'.join([dbase, 'dbstats'])] = {
276 'options': [None, "%s: size of all documents, indexes, extents" % dbase, "KB",
277 'storage size metrics', 'mongodb.dbstats', 'line'],
279 ['_'.join([dbase, 'dataSize']), 'documents', 'absolute', 1, 1024],
280 ['_'.join([dbase, 'indexSize']), 'indexes', 'absolute', 1, 1024],
281 ['_'.join([dbase, 'storageSize']), 'extents', 'absolute', 1, 1024]
283 self.definitions['dbstats_objects']['lines'].append(['_'.join([dbase, 'objects']), dbase, 'absolute'])
285 if server_status.get('repl'):
286 hosts = server_status['repl']['hosts']
288 chart_name = '_'.join([host, 'state'])
289 self.order.append(chart_name)
290 self.definitions[chart_name] = {
291 'options': [None, "%s state" % host, "state",
292 'replication', 'mongodb.replication_state', 'line'],
295 for state, description in REPLSET_STATES:
296 self.definitions[chart_name]['lines'].append(['_'.join([host, 'state', state]), description, 'absolute', 1, 1])
300 def _get_raw_data(self):
303 raw_data.update(self.get_serverstatus_() or dict())
304 raw_data.update(self.get_dbstats_() or dict())
305 raw_data.update(self.get_replsetgetstatus_() or dict())
307 return raw_data or None
309 def get_serverstatus_(self):
312 raw_data['serverStatus'] = self.connection.admin.command('serverStatus')
318 def get_dbstats_(self):
320 raw_data['dbStats'] = dict()
322 for dbase in self.databases:
323 raw_data['dbStats'][dbase] = self.connection[dbase].command('dbStats')
329 def get_replsetgetstatus_(self):
335 raw_data['replSetGetStatus'] = self.connection.admin.command('replSetGetStatus')
345 raw_data = self._get_raw_data()
351 serverStatus = raw_data['serverStatus']
352 dbStats = raw_data['dbStats']
353 replSetGetStatus = raw_data.get('replSetGetStatus')
356 to_netdata.update(update_dict_key(serverStatus['opcounters'], 'readWriteOper'))
357 to_netdata.update(update_dict_key(serverStatus['globalLock']['activeClients'], 'activeClients'))
358 to_netdata.update(update_dict_key(serverStatus['connections'], 'connections'))
359 to_netdata.update(update_dict_key(serverStatus['mem'], 'memory'))
360 to_netdata.update(update_dict_key(serverStatus['globalLock']['currentQueue'], 'currentQueue'))
361 to_netdata.update(update_dict_key(serverStatus['asserts'], 'errors'))
362 to_netdata['page_faults'] = serverStatus['extra_info']['page_faults']
363 to_netdata['record_moves'] = serverStatus['metrics']['record']['moves']
366 to_netdata['journalTrans_commits'] = serverStatus['dur']['commits']
367 to_netdata['journalTrans_journaled'] = int(serverStatus['dur']['journaledMB'] * 100)
369 if self.ss['backgroundFlushing']:
370 to_netdata['background_flush_average'] = int(serverStatus['backgroundFlushing']['average_ms'] * 100)
371 to_netdata['background_flush_last'] = int(serverStatus['backgroundFlushing']['last_ms'] * 100)
372 to_netdata['background_flush_rate'] = serverStatus['backgroundFlushing']['flushes']
374 if self.ss['cursor']:
375 to_netdata['cursor_timedOut'] = serverStatus['metrics']['cursor']['timedOut']
376 to_netdata.update(update_dict_key(serverStatus['metrics']['cursor']['open'], 'cursor'))
378 if self.ss['wiredTiger']:
379 wired_tiger = serverStatus['wiredTiger']
380 to_netdata.update(update_dict_key(serverStatus['wiredTiger']['concurrentTransactions']['read'],
382 to_netdata.update(update_dict_key(serverStatus['wiredTiger']['concurrentTransactions']['write'],
384 to_netdata['wiredTiger_bytes_in_cache'] = wired_tiger['cache']['bytes currently in the cache']
385 to_netdata['wiredTiger_dirty_in_cache'] = wired_tiger['cache']['tracked dirty bytes in the cache']
386 to_netdata['wiredTiger_unmodified_pages_evicted'] = wired_tiger['cache']['unmodified pages evicted']
387 to_netdata['wiredTiger_modified_pages_evicted'] = wired_tiger['cache']['modified pages evicted']
389 if self.ss['tcmalloc']:
390 to_netdata.update(serverStatus['tcmalloc']['generic'])
391 to_netdata.update(dict([(k, v) for k, v in serverStatus['tcmalloc']['tcmalloc'].items()
392 if int_or_float(v)]))
394 if self.ss['commands']:
395 for elem in ['count', 'createIndexes', 'delete', 'eval', 'findAndModify', 'insert', 'update']:
396 to_netdata.update(update_dict_key(serverStatus['metrics']['commands'][elem], elem))
399 for dbase in dbStats:
400 to_netdata.update(update_dict_key(dbStats[dbase], dbase))
404 members = replSetGetStatus['members']
405 for member in members:
406 for elem in REPLSET_STATES:
408 to_netdata.update({'_'.join([member['name'], 'state', state]): 0})
409 to_netdata.update({'_'.join([member['name'], 'state', str(member['state'])]): member['state']})
413 def _create_connection(self):
414 conn_vars = {'host': self.host, 'port': self.port}
415 if hasattr(MongoClient, 'server_selection_timeout'):
416 conn_vars.update({'serverselectiontimeoutms': self.timeout})
418 connection = MongoClient(**conn_vars)
419 if self.user and self.password:
420 connection.admin.authenticate(name=self.user, password=self.password)
422 # connection.admin.authenticate(name=self.user, mechanism='MONGODB-X509')
423 server_status = connection.admin.command('serverStatus')
424 except PyMongoError as error:
425 return None, None, str(error)
427 return connection, server_status, None
430 def update_dict_key(collection, string):
431 return dict([('_'.join([string, k]), int(round(v))) for k, v in collection.items() if int_or_float(v)])
434 def int_or_float(value):
435 return isinstance(value, (int, float))
438 def in_server_status(elem, server_status):
439 return elem in server_status or elem in server_status['metrics']