mongodb.chart.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. # -*- coding: utf-8 -*-
  2. # Description: mongodb netdata python.d module
  3. # Author: l2isbad
  4. from copy import deepcopy
  5. from datetime import datetime
  6. from sys import exc_info
  7. try:
  8. from pymongo import MongoClient, ASCENDING, DESCENDING
  9. from pymongo.errors import PyMongoError
  10. PYMONGO = True
  11. except ImportError:
  12. PYMONGO = False
  13. from bases.FrameworkServices.SimpleService import SimpleService
  14. # default module values (can be overridden per job in `config`)
  15. # update_every = 2
  16. priority = 60000
  17. retries = 60
  18. REPL_SET_STATES = [
  19. ('1', 'primary'),
  20. ('8', 'down'),
  21. ('2', 'secondary'),
  22. ('3', 'recovering'),
  23. ('5', 'startup2'),
  24. ('4', 'fatal'),
  25. ('7', 'arbiter'),
  26. ('6', 'unknown'),
  27. ('9', 'rollback'),
  28. ('10', 'removed'),
  29. ('0', 'startup')]
  30. def multiply_by_100(value):
  31. return value * 100
  32. DEFAULT_METRICS = [
  33. ('opcounters.delete', None, None),
  34. ('opcounters.update', None, None),
  35. ('opcounters.insert', None, None),
  36. ('opcounters.query', None, None),
  37. ('opcounters.getmore', None, None),
  38. ('globalLock.activeClients.readers', 'activeClients_readers', None),
  39. ('globalLock.activeClients.writers', 'activeClients_writers', None),
  40. ('connections.available', 'connections_available', None),
  41. ('connections.current', 'connections_current', None),
  42. ('mem.mapped', None, None),
  43. ('mem.resident', None, None),
  44. ('mem.virtual', None, None),
  45. ('globalLock.currentQueue.readers', 'currentQueue_readers', None),
  46. ('globalLock.currentQueue.writers', 'currentQueue_writers', None),
  47. ('asserts.msg', None, None),
  48. ('asserts.regular', None, None),
  49. ('asserts.user', None, None),
  50. ('asserts.warning', None, None),
  51. ('extra_info.page_faults', None, None),
  52. ('metrics.record.moves', None, None),
  53. ('backgroundFlushing.average_ms', None, multiply_by_100),
  54. ('backgroundFlushing.last_ms', None, multiply_by_100),
  55. ('backgroundFlushing.flushes', None, multiply_by_100),
  56. ('metrics.cursor.timedOut', None, None),
  57. ('metrics.cursor.open.total', 'cursor_total', None),
  58. ('metrics.cursor.open.noTimeout', None, None),
  59. ('cursors.timedOut', None, None),
  60. ('cursors.totalOpen', 'cursor_total', None)
  61. ]
  62. DUR = [
  63. ('dur.commits', None, None),
  64. ('dur.journaledMB', None, multiply_by_100)
  65. ]
  66. WIREDTIGER = [
  67. ('wiredTiger.concurrentTransactions.read.available', 'wiredTigerRead_available', None),
  68. ('wiredTiger.concurrentTransactions.read.out', 'wiredTigerRead_out', None),
  69. ('wiredTiger.concurrentTransactions.write.available', 'wiredTigerWrite_available', None),
  70. ('wiredTiger.concurrentTransactions.write.out', 'wiredTigerWrite_out', None),
  71. ('wiredTiger.cache.bytes currently in the cache', None, None),
  72. ('wiredTiger.cache.tracked dirty bytes in the cache', None, None),
  73. ('wiredTiger.cache.maximum bytes configured', None, None),
  74. ('wiredTiger.cache.unmodified pages evicted', 'unmodified', None),
  75. ('wiredTiger.cache.modified pages evicted', 'modified', None)
  76. ]
  77. TCMALLOC = [
  78. ('tcmalloc.generic.current_allocated_bytes', None, None),
  79. ('tcmalloc.generic.heap_size', None, None),
  80. ('tcmalloc.tcmalloc.central_cache_free_bytes', None, None),
  81. ('tcmalloc.tcmalloc.current_total_thread_cache_bytes', None, None),
  82. ('tcmalloc.tcmalloc.pageheap_free_bytes', None, None),
  83. ('tcmalloc.tcmalloc.pageheap_unmapped_bytes', None, None),
  84. ('tcmalloc.tcmalloc.thread_cache_free_bytes', None, None),
  85. ('tcmalloc.tcmalloc.transfer_cache_free_bytes', None, None)
  86. ]
  87. COMMANDS = [
  88. ('metrics.commands.count.total', 'count_total', None),
  89. ('metrics.commands.createIndexes.total', 'createIndexes_total', None),
  90. ('metrics.commands.delete.total', 'delete_total', None),
  91. ('metrics.commands.eval.total', 'eval_total', None),
  92. ('metrics.commands.findAndModify.total', 'findAndModify_total', None),
  93. ('metrics.commands.insert.total', 'insert_total', None),
  94. ('metrics.commands.delete.total', 'delete_total', None),
  95. ('metrics.commands.count.failed', 'count_failed', None),
  96. ('metrics.commands.createIndexes.failed', 'createIndexes_failed', None),
  97. ('metrics.commands.delete.failed', 'delete_failed', None),
  98. ('metrics.commands.eval.failed', 'eval_failed', None),
  99. ('metrics.commands.findAndModify.failed', 'findAndModify_failed', None),
  100. ('metrics.commands.insert.failed', 'insert_failed', None),
  101. ('metrics.commands.delete.failed', 'delete_failed', None)
  102. ]
  103. LOCKS = [
  104. ('locks.Collection.acquireCount.R', 'Collection_R', None),
  105. ('locks.Collection.acquireCount.r', 'Collection_r', None),
  106. ('locks.Collection.acquireCount.W', 'Collection_W', None),
  107. ('locks.Collection.acquireCount.w', 'Collection_w', None),
  108. ('locks.Database.acquireCount.R', 'Database_R', None),
  109. ('locks.Database.acquireCount.r', 'Database_r', None),
  110. ('locks.Database.acquireCount.W', 'Database_W', None),
  111. ('locks.Database.acquireCount.w', 'Database_w', None),
  112. ('locks.Global.acquireCount.R', 'Global_R', None),
  113. ('locks.Global.acquireCount.r', 'Global_r', None),
  114. ('locks.Global.acquireCount.W', 'Global_W', None),
  115. ('locks.Global.acquireCount.w', 'Global_w', None),
  116. ('locks.Metadata.acquireCount.R', 'Metadata_R', None),
  117. ('locks.Metadata.acquireCount.w', 'Metadata_w', None),
  118. ('locks.oplog.acquireCount.r', 'oplog_r', None),
  119. ('locks.oplog.acquireCount.w', 'oplog_w', None)
  120. ]
  121. DBSTATS = [
  122. 'dataSize',
  123. 'indexSize',
  124. 'storageSize',
  125. 'objects'
  126. ]
  127. # charts order (can be overridden if you want less charts, or different order)
  128. ORDER = ['read_operations', 'write_operations', 'active_clients', 'journaling_transactions',
  129. 'journaling_volume', 'background_flush_average', 'background_flush_last', 'background_flush_rate',
  130. 'wiredtiger_read', 'wiredtiger_write', 'cursors', 'connections', 'memory', 'page_faults',
  131. 'queued_requests', 'record_moves', 'wiredtiger_cache', 'wiredtiger_pages_evicted', 'asserts',
  132. 'locks_collection', 'locks_database', 'locks_global', 'locks_metadata', 'locks_oplog',
  133. 'dbstats_objects', 'tcmalloc_generic', 'tcmalloc_metrics', 'command_total_rate', 'command_failed_rate']
  134. CHARTS = {
  135. 'read_operations': {
  136. 'options': [None, 'Received read requests', 'requests/s', 'throughput metrics',
  137. 'mongodb.read_operations', 'line'],
  138. 'lines': [
  139. ['query', None, 'incremental'],
  140. ['getmore', None, 'incremental']
  141. ]},
  142. 'write_operations': {
  143. 'options': [None, 'Received write requests', 'requests/s', 'throughput metrics',
  144. 'mongodb.write_operations', 'line'],
  145. 'lines': [
  146. ['insert', None, 'incremental'],
  147. ['update', None, 'incremental'],
  148. ['delete', None, 'incremental']
  149. ]},
  150. 'active_clients': {
  151. 'options': [None, 'Clients with read or write operations in progress or queued', 'clients',
  152. 'throughput metrics', 'mongodb.active_clients', 'line'],
  153. 'lines': [
  154. ['activeClients_readers', 'readers', 'absolute'],
  155. ['activeClients_writers', 'writers', 'absolute']
  156. ]},
  157. 'journaling_transactions': {
  158. 'options': [None, 'Transactions that have been written to the journal', 'commits',
  159. 'database performance', 'mongodb.journaling_transactions', 'line'],
  160. 'lines': [
  161. ['commits', None, 'absolute']
  162. ]},
  163. 'journaling_volume': {
  164. 'options': [None, 'Volume of data written to the journal', 'MB', 'database performance',
  165. 'mongodb.journaling_volume', 'line'],
  166. 'lines': [
  167. ['journaledMB', 'volume', 'absolute', 1, 100]
  168. ]},
  169. 'background_flush_average': {
  170. 'options': [None, 'Average time taken by flushes to execute', 'ms', 'database performance',
  171. 'mongodb.background_flush_average', 'line'],
  172. 'lines': [
  173. ['average_ms', 'time', 'absolute', 1, 100]
  174. ]},
  175. 'background_flush_last': {
  176. 'options': [None, 'Time taken by the last flush operation to execute', 'ms', 'database performance',
  177. 'mongodb.background_flush_last', 'line'],
  178. 'lines': [
  179. ['last_ms', 'time', 'absolute', 1, 100]
  180. ]},
  181. 'background_flush_rate': {
  182. 'options': [None, 'Flushes rate', 'flushes', 'database performance', 'mongodb.background_flush_rate', 'line'],
  183. 'lines': [
  184. ['flushes', 'flushes', 'incremental', 1, 1]
  185. ]},
  186. 'wiredtiger_read': {
  187. 'options': [None, 'Read tickets in use and remaining', 'tickets', 'database performance',
  188. 'mongodb.wiredtiger_read', 'stacked'],
  189. 'lines': [
  190. ['wiredTigerRead_available', 'available', 'absolute', 1, 1],
  191. ['wiredTigerRead_out', 'inuse', 'absolute', 1, 1]
  192. ]},
  193. 'wiredtiger_write': {
  194. 'options': [None, 'Write tickets in use and remaining', 'tickets', 'database performance',
  195. 'mongodb.wiredtiger_write', 'stacked'],
  196. 'lines': [
  197. ['wiredTigerWrite_available', 'available', 'absolute', 1, 1],
  198. ['wiredTigerWrite_out', 'inuse', 'absolute', 1, 1]
  199. ]},
  200. 'cursors': {
  201. 'options': [None, 'Currently openned cursors, cursors with timeout disabled and timed out cursors',
  202. 'cursors', 'database performance', 'mongodb.cursors', 'stacked'],
  203. 'lines': [
  204. ['cursor_total', 'openned', 'absolute', 1, 1],
  205. ['noTimeout', None, 'absolute', 1, 1],
  206. ['timedOut', None, 'incremental', 1, 1]
  207. ]},
  208. 'connections': {
  209. 'options': [None, 'Currently connected clients and unused connections', 'connections',
  210. 'resource utilization', 'mongodb.connections', 'stacked'],
  211. 'lines': [
  212. ['connections_available', 'unused', 'absolute', 1, 1],
  213. ['connections_current', 'connected', 'absolute', 1, 1]
  214. ]},
  215. 'memory': {
  216. 'options': [None, 'Memory metrics', 'MB', 'resource utilization', 'mongodb.memory', 'stacked'],
  217. 'lines': [
  218. ['virtual', None, 'absolute', 1, 1],
  219. ['resident', None, 'absolute', 1, 1],
  220. ['nonmapped', None, 'absolute', 1, 1],
  221. ['mapped', None, 'absolute', 1, 1]
  222. ]},
  223. 'page_faults': {
  224. 'options': [None, 'Number of times MongoDB had to fetch data from disk', 'request/s',
  225. 'resource utilization', 'mongodb.page_faults', 'line'],
  226. 'lines': [
  227. ['page_faults', None, 'incremental', 1, 1]
  228. ]},
  229. 'queued_requests': {
  230. 'options': [None, 'Currently queued read and wrire requests', 'requests', 'resource saturation',
  231. 'mongodb.queued_requests', 'line'],
  232. 'lines': [
  233. ['currentQueue_readers', 'readers', 'absolute', 1, 1],
  234. ['currentQueue_writers', 'writers', 'absolute', 1, 1]
  235. ]},
  236. 'record_moves': {
  237. 'options': [None, 'Number of times documents had to be moved on-disk', 'number',
  238. 'resource saturation', 'mongodb.record_moves', 'line'],
  239. 'lines': [
  240. ['moves', None, 'incremental', 1, 1]
  241. ]},
  242. 'asserts': {
  243. 'options': [None, 'Number of message, warning, regular, corresponding to errors generated'
  244. ' by users assertions raised', 'number', 'errors (asserts)', 'mongodb.asserts', 'line'],
  245. 'lines': [
  246. ['msg', None, 'incremental', 1, 1],
  247. ['warning', None, 'incremental', 1, 1],
  248. ['regular', None, 'incremental', 1, 1],
  249. ['user', None, 'incremental', 1, 1]
  250. ]},
  251. 'wiredtiger_cache': {
  252. 'options': [None, 'The percentage of the wiredTiger cache that is in use and cache with dirty bytes',
  253. 'percent', 'resource utilization', 'mongodb.wiredtiger_cache', 'stacked'],
  254. 'lines': [
  255. ['wiredTiger_percent_clean', 'inuse', 'absolute', 1, 1000],
  256. ['wiredTiger_percent_dirty', 'dirty', 'absolute', 1, 1000]
  257. ]},
  258. 'wiredtiger_pages_evicted': {
  259. 'options': [None, 'Pages evicted from the cache',
  260. 'pages', 'resource utilization', 'mongodb.wiredtiger_pages_evicted', 'stacked'],
  261. 'lines': [
  262. ['unmodified', None, 'absolute', 1, 1],
  263. ['modified', None, 'absolute', 1, 1]
  264. ]},
  265. 'dbstats_objects': {
  266. 'options': [None, 'Number of documents in the database among all the collections', 'documents',
  267. 'storage size metrics', 'mongodb.dbstats_objects', 'stacked'],
  268. 'lines': [
  269. ]},
  270. 'tcmalloc_generic': {
  271. 'options': [None, 'Tcmalloc generic metrics', 'MB', 'tcmalloc', 'mongodb.tcmalloc_generic', 'stacked'],
  272. 'lines': [
  273. ['current_allocated_bytes', 'allocated', 'absolute', 1, 1048576],
  274. ['heap_size', 'heap_size', 'absolute', 1, 1048576]
  275. ]},
  276. 'tcmalloc_metrics': {
  277. 'options': [None, 'Tcmalloc metrics', 'KB', 'tcmalloc', 'mongodb.tcmalloc_metrics', 'stacked'],
  278. 'lines': [
  279. ['central_cache_free_bytes', 'central_cache_free', 'absolute', 1, 1024],
  280. ['current_total_thread_cache_bytes', 'current_total_thread_cache', 'absolute', 1, 1024],
  281. ['pageheap_free_bytes', 'pageheap_free', 'absolute', 1, 1024],
  282. ['pageheap_unmapped_bytes', 'pageheap_unmapped', 'absolute', 1, 1024],
  283. ['thread_cache_free_bytes', 'thread_cache_free', 'absolute', 1, 1024],
  284. ['transfer_cache_free_bytes', 'transfer_cache_free', 'absolute', 1, 1024]
  285. ]},
  286. 'command_total_rate': {
  287. 'options': [None, 'Commands total rate', 'commands/s', 'commands', 'mongodb.command_total_rate', 'stacked'],
  288. 'lines': [
  289. ['count_total', 'count', 'incremental', 1, 1],
  290. ['createIndexes_total', 'createIndexes', 'incremental', 1, 1],
  291. ['delete_total', 'delete', 'incremental', 1, 1],
  292. ['eval_total', 'eval', 'incremental', 1, 1],
  293. ['findAndModify_total', 'findAndModify', 'incremental', 1, 1],
  294. ['insert_total', 'insert', 'incremental', 1, 1],
  295. ['update_total', 'update', 'incremental', 1, 1]
  296. ]},
  297. 'command_failed_rate': {
  298. 'options': [None, 'Commands failed rate', 'commands/s', 'commands', 'mongodb.command_failed_rate', 'stacked'],
  299. 'lines': [
  300. ['count_failed', 'count', 'incremental', 1, 1],
  301. ['createIndexes_failed', 'createIndexes', 'incremental', 1, 1],
  302. ['delete_failed', 'delete', 'incremental', 1, 1],
  303. ['eval_failed', 'eval', 'incremental', 1, 1],
  304. ['findAndModify_failed', 'findAndModify', 'incremental', 1, 1],
  305. ['insert_failed', 'insert', 'incremental', 1, 1],
  306. ['update_failed', 'update', 'incremental', 1, 1]
  307. ]},
  308. 'locks_collection': {
  309. 'options': [None, 'Collection lock. Number of times the lock was acquired in the specified mode',
  310. 'locks', 'locks metrics', 'mongodb.locks_collection', 'stacked'],
  311. 'lines': [
  312. ['Collection_R', 'shared', 'incremental'],
  313. ['Collection_W', 'exclusive', 'incremental'],
  314. ['Collection_r', 'intent_shared', 'incremental'],
  315. ['Collection_w', 'intent_exclusive', 'incremental']
  316. ]},
  317. 'locks_database': {
  318. 'options': [None, 'Database lock. Number of times the lock was acquired in the specified mode',
  319. 'locks', 'locks metrics', 'mongodb.locks_database', 'stacked'],
  320. 'lines': [
  321. ['Database_R', 'shared', 'incremental'],
  322. ['Database_W', 'exclusive', 'incremental'],
  323. ['Database_r', 'intent_shared', 'incremental'],
  324. ['Database_w', 'intent_exclusive', 'incremental']
  325. ]},
  326. 'locks_global': {
  327. 'options': [None, 'Global lock. Number of times the lock was acquired in the specified mode',
  328. 'locks', 'locks metrics', 'mongodb.locks_global', 'stacked'],
  329. 'lines': [
  330. ['Global_R', 'shared', 'incremental'],
  331. ['Global_W', 'exclusive', 'incremental'],
  332. ['Global_r', 'intent_shared', 'incremental'],
  333. ['Global_w', 'intent_exclusive', 'incremental']
  334. ]},
  335. 'locks_metadata': {
  336. 'options': [None, 'Metadata lock. Number of times the lock was acquired in the specified mode',
  337. 'locks', 'locks metrics', 'mongodb.locks_metadata', 'stacked'],
  338. 'lines': [
  339. ['Metadata_R', 'shared', 'incremental'],
  340. ['Metadata_w', 'intent_exclusive', 'incremental']
  341. ]},
  342. 'locks_oplog': {
  343. 'options': [None, 'Lock on the oplog. Number of times the lock was acquired in the specified mode',
  344. 'locks', 'locks metrics', 'mongodb.locks_oplog', 'stacked'],
  345. 'lines': [
  346. ['oplog_r', 'intent_shared', 'incremental'],
  347. ['oplog_w', 'intent_exclusive', 'incremental']
  348. ]}
  349. }
  350. class Service(SimpleService):
  351. def __init__(self, configuration=None, name=None):
  352. SimpleService.__init__(self, configuration=configuration, name=name)
  353. self.order = ORDER[:]
  354. self.definitions = deepcopy(CHARTS)
  355. self.user = self.configuration.get('user')
  356. self.password = self.configuration.get('pass')
  357. self.host = self.configuration.get('host', '127.0.0.1')
  358. self.port = self.configuration.get('port', 27017)
  359. self.timeout = self.configuration.get('timeout', 100)
  360. self.metrics_to_collect = deepcopy(DEFAULT_METRICS)
  361. self.connection = None
  362. self.do_replica = None
  363. self.databases = list()
  364. def check(self):
  365. if not PYMONGO:
  366. self.error('Pymongo module is needed to use mongodb.chart.py')
  367. return False
  368. self.connection, server_status, error = self._create_connection()
  369. if error:
  370. self.error(error)
  371. return False
  372. self.build_metrics_to_collect_(server_status)
  373. try:
  374. data = self._get_data()
  375. except (LookupError, SyntaxError, AttributeError):
  376. self.error('Type: %s, error: %s' % (str(exc_info()[0]), str(exc_info()[1])))
  377. return False
  378. if isinstance(data, dict) and data:
  379. self._data_from_check = data
  380. self.create_charts_(server_status)
  381. return True
  382. self.error('_get_data() returned no data or type is not <dict>')
  383. return False
  384. def build_metrics_to_collect_(self, server_status):
  385. self.do_replica = 'repl' in server_status
  386. if 'dur' in server_status:
  387. self.metrics_to_collect.extend(DUR)
  388. if 'tcmalloc' in server_status:
  389. self.metrics_to_collect.extend(TCMALLOC)
  390. if 'commands' in server_status['metrics']:
  391. self.metrics_to_collect.extend(COMMANDS)
  392. if 'wiredTiger' in server_status:
  393. self.metrics_to_collect.extend(WIREDTIGER)
  394. if 'Collection' in server_status['locks']:
  395. self.metrics_to_collect.extend(LOCKS)
  396. def create_charts_(self, server_status):
  397. if 'dur' not in server_status:
  398. self.order.remove('journaling_transactions')
  399. self.order.remove('journaling_volume')
  400. if 'backgroundFlushing' not in server_status:
  401. self.order.remove('background_flush_average')
  402. self.order.remove('background_flush_last')
  403. self.order.remove('background_flush_rate')
  404. if 'wiredTiger' not in server_status:
  405. self.order.remove('wiredtiger_write')
  406. self.order.remove('wiredtiger_read')
  407. self.order.remove('wiredtiger_cache')
  408. if 'tcmalloc' not in server_status:
  409. self.order.remove('tcmalloc_generic')
  410. self.order.remove('tcmalloc_metrics')
  411. if 'commands' not in server_status['metrics']:
  412. self.order.remove('command_total_rate')
  413. self.order.remove('command_failed_rate')
  414. if 'Collection' not in server_status['locks']:
  415. self.order.remove('locks_collection')
  416. self.order.remove('locks_database')
  417. self.order.remove('locks_global')
  418. self.order.remove('locks_metadata')
  419. if 'oplog' not in server_status['locks']:
  420. self.order.remove('locks_oplog')
  421. for dbase in self.databases:
  422. self.order.append('_'.join([dbase, 'dbstats']))
  423. self.definitions['_'.join([dbase, 'dbstats'])] = {
  424. 'options': [None, '%s: size of all documents, indexes, extents' % dbase, 'KB',
  425. 'storage size metrics', 'mongodb.dbstats', 'line'],
  426. 'lines': [
  427. ['_'.join([dbase, 'dataSize']), 'documents', 'absolute', 1, 1024],
  428. ['_'.join([dbase, 'indexSize']), 'indexes', 'absolute', 1, 1024],
  429. ['_'.join([dbase, 'storageSize']), 'extents', 'absolute', 1, 1024]
  430. ]}
  431. self.definitions['dbstats_objects']['lines'].append(['_'.join([dbase, 'objects']), dbase, 'absolute'])
  432. if self.do_replica:
  433. def create_lines(hosts, string):
  434. lines = list()
  435. for host in hosts:
  436. dim_id = '_'.join([host, string])
  437. lines.append([dim_id, host, 'absolute', 1, 1000])
  438. return lines
  439. def create_state_lines(states):
  440. lines = list()
  441. for state, description in states:
  442. dim_id = '_'.join([host, 'state', state])
  443. lines.append([dim_id, description, 'absolute', 1, 1])
  444. return lines
  445. all_hosts = server_status['repl']['hosts'] + server_status['repl'].get('arbiters', list())
  446. this_host = server_status['repl']['me']
  447. other_hosts = [host for host in all_hosts if host != this_host]
  448. if 'local' in self.databases:
  449. self.order.append('oplog_window')
  450. self.definitions['oplog_window'] = {
  451. 'options': [None, 'Interval of time between the oldest and the latest entries in the oplog',
  452. 'seconds', 'replication and oplog', 'mongodb.oplog_window', 'line'],
  453. 'lines': [['timeDiff', 'window', 'absolute', 1, 1000]]}
  454. # Create "heartbeat delay" chart
  455. self.order.append('heartbeat_delay')
  456. self.definitions['heartbeat_delay'] = {
  457. 'options': [None, 'Time when last heartbeat was received'
  458. ' from the replica set member (lastHeartbeatRecv)',
  459. 'seconds ago', 'replication and oplog', 'mongodb.replication_heartbeat_delay', 'stacked'],
  460. 'lines': create_lines(other_hosts, 'heartbeat_lag')}
  461. # Create "optimedate delay" chart
  462. self.order.append('optimedate_delay')
  463. self.definitions['optimedate_delay'] = {
  464. 'options': [None, 'Time when last entry from the oplog was applied (optimeDate)',
  465. 'seconds ago', 'replication and oplog', 'mongodb.replication_optimedate_delay', 'stacked'],
  466. 'lines': create_lines(all_hosts, 'optimedate')}
  467. # Create "replica set members state" chart
  468. for host in all_hosts:
  469. chart_name = '_'.join([host, 'state'])
  470. self.order.append(chart_name)
  471. self.definitions[chart_name] = {
  472. 'options': [None, 'Replica set member (%s) current state' % host, 'state',
  473. 'replication and oplog', 'mongodb.replication_state', 'line'],
  474. 'lines': create_state_lines(REPL_SET_STATES)}
  475. def _get_raw_data(self):
  476. raw_data = dict()
  477. raw_data.update(self.get_server_status() or dict())
  478. raw_data.update(self.get_db_stats() or dict())
  479. raw_data.update(self.get_repl_set_get_status() or dict())
  480. raw_data.update(self.get_get_replication_info() or dict())
  481. return raw_data or None
  482. def get_server_status(self):
  483. raw_data = dict()
  484. try:
  485. raw_data['serverStatus'] = self.connection.admin.command('serverStatus')
  486. except PyMongoError:
  487. return None
  488. else:
  489. return raw_data
  490. def get_db_stats(self):
  491. if not self.databases:
  492. return None
  493. raw_data = dict()
  494. raw_data['dbStats'] = dict()
  495. try:
  496. for dbase in self.databases:
  497. raw_data['dbStats'][dbase] = self.connection[dbase].command('dbStats')
  498. return raw_data
  499. except PyMongoError:
  500. return None
  501. def get_repl_set_get_status(self):
  502. if not self.do_replica:
  503. return None
  504. raw_data = dict()
  505. try:
  506. raw_data['replSetGetStatus'] = self.connection.admin.command('replSetGetStatus')
  507. return raw_data
  508. except PyMongoError:
  509. return None
  510. def get_get_replication_info(self):
  511. if not (self.do_replica and 'local' in self.databases):
  512. return None
  513. raw_data = dict()
  514. raw_data['getReplicationInfo'] = dict()
  515. try:
  516. raw_data['getReplicationInfo']['ASCENDING'] = self.connection.local.oplog.rs.find().sort(
  517. "$natural", ASCENDING).limit(1)[0]
  518. raw_data['getReplicationInfo']['DESCENDING'] = self.connection.local.oplog.rs.find().sort(
  519. "$natural", DESCENDING).limit(1)[0]
  520. return raw_data
  521. except PyMongoError:
  522. return None
  523. def _get_data(self):
  524. """
  525. :return: dict
  526. """
  527. raw_data = self._get_raw_data()
  528. if not raw_data:
  529. return None
  530. to_netdata = dict()
  531. serverStatus = raw_data['serverStatus']
  532. dbStats = raw_data.get('dbStats')
  533. replSetGetStatus = raw_data.get('replSetGetStatus')
  534. getReplicationInfo = raw_data.get('getReplicationInfo')
  535. utc_now = datetime.utcnow()
  536. # serverStatus
  537. for metric, new_name, func in self.metrics_to_collect:
  538. value = serverStatus
  539. for key in metric.split('.'):
  540. try:
  541. value = value[key]
  542. except KeyError:
  543. break
  544. if not isinstance(value, dict) and key:
  545. to_netdata[new_name or key] = value if not func else func(value)
  546. to_netdata['nonmapped'] = to_netdata['virtual'] - serverStatus['mem'].get('mappedWithJournal',
  547. to_netdata['mapped'])
  548. if to_netdata.get('maximum bytes configured'):
  549. maximum = to_netdata['maximum bytes configured']
  550. to_netdata['wiredTiger_percent_clean'] = int(to_netdata['bytes currently in the cache']
  551. * 100 / maximum * 1000)
  552. to_netdata['wiredTiger_percent_dirty'] = int(to_netdata['tracked dirty bytes in the cache']
  553. * 100 / maximum * 1000)
  554. # dbStats
  555. if dbStats:
  556. for dbase in dbStats:
  557. for metric in DBSTATS:
  558. key = '_'.join([dbase, metric])
  559. to_netdata[key] = dbStats[dbase][metric]
  560. # replSetGetStatus
  561. if replSetGetStatus:
  562. other_hosts = list()
  563. members = replSetGetStatus['members']
  564. unix_epoch = datetime(1970, 1, 1, 0, 0)
  565. for member in members:
  566. if not member.get('self'):
  567. other_hosts.append(member)
  568. # Replica set time diff between current time and time when last entry from the oplog was applied
  569. if member.get('optimeDate', unix_epoch) != unix_epoch:
  570. member_optimedate = member['name'] + '_optimedate'
  571. to_netdata.update({member_optimedate: int(delta_calculation(delta=utc_now - member['optimeDate'],
  572. multiplier=1000))})
  573. # Replica set members state
  574. member_state = member['name'] + '_state'
  575. for elem in REPL_SET_STATES:
  576. state = elem[0]
  577. to_netdata.update({'_'.join([member_state, state]): 0})
  578. to_netdata.update({'_'.join([member_state, str(member['state'])]): member['state']})
  579. # Heartbeat lag calculation
  580. for other in other_hosts:
  581. if other['lastHeartbeatRecv'] != unix_epoch:
  582. node = other['name'] + '_heartbeat_lag'
  583. to_netdata[node] = int(delta_calculation(delta=utc_now - other['lastHeartbeatRecv'],
  584. multiplier=1000))
  585. if getReplicationInfo:
  586. first_event = getReplicationInfo['ASCENDING']['ts'].as_datetime()
  587. last_event = getReplicationInfo['DESCENDING']['ts'].as_datetime()
  588. to_netdata['timeDiff'] = int(delta_calculation(delta=last_event - first_event, multiplier=1000))
  589. return to_netdata
  590. def _create_connection(self):
  591. conn_vars = {'host': self.host, 'port': self.port}
  592. if hasattr(MongoClient, 'server_selection_timeout'):
  593. conn_vars.update({'serverselectiontimeoutms': self.timeout})
  594. try:
  595. connection = MongoClient(**conn_vars)
  596. if self.user and self.password:
  597. connection.admin.authenticate(name=self.user, password=self.password)
  598. # elif self.user:
  599. # connection.admin.authenticate(name=self.user, mechanism='MONGODB-X509')
  600. server_status = connection.admin.command('serverStatus')
  601. except PyMongoError as error:
  602. return None, None, str(error)
  603. else:
  604. try:
  605. self.databases = connection.database_names()
  606. except PyMongoError as error:
  607. self.info('Can\'t collect databases: %s' % str(error))
  608. return connection, server_status, None
  609. def delta_calculation(delta, multiplier=1):
  610. if hasattr(delta, 'total_seconds'):
  611. return delta.total_seconds() * multiplier
  612. return (delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10 ** 6) / 10.0 ** 6 * multiplier