sensu-plugins-mongodb-boutetnico 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ shell_script_path = File.join(__dir__, File.basename($PROGRAM_NAME, '.rb') + '.py')
4
+
5
+ exec shell_script_path, *ARGV
@@ -0,0 +1,269 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # metrics-mongodb-replication.rb
4
+ #
5
+ # DESCRIPTION:
6
+ #
7
+ # OUTPUT:
8
+ # metric data
9
+ #
10
+ # PLATFORMS:
11
+ # Linux
12
+ #
13
+ # DEPENDENCIES:
14
+ # gem: sensu-plugin
15
+ # gem: mongo
16
+ # gem: bson
17
+ # gem: bson_ext
18
+ #
19
+ # USAGE:
20
+ # #YELLOW
21
+ #
22
+ # NOTES::
23
+ # Basics from github.com/sensu-plugins/sensu-plugins-mongodb/bin/metrics-mongodb
24
+ #
25
+ # Replication lag is calculated by obtaining the last optime from primary and
26
+ # secondary members. The last optime of the secondary is subtracted from the
27
+ # last optime of the primary to produce the difference in seconds, minutes and hours
28
+ #
29
+ # LICENSE:
30
+ # Copyright 2016 Rycroft Solutions
31
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
32
+ # for details.
33
+ #
34
+
35
+ require 'sensu-plugin/metric/cli'
36
+ require 'mongo'
37
+ require 'date'
38
+ include Mongo
39
+
40
+ #
41
+ # Mongodb
42
+ #
43
+
44
+ class MongoDB < Sensu::Plugin::Metric::CLI::Graphite
45
+ option :host,
46
+ description: 'MongoDB host',
47
+ long: '--host HOST',
48
+ default: 'localhost'
49
+
50
+ option :port,
51
+ description: 'MongoDB port',
52
+ long: '--port PORT',
53
+ default: 27_017
54
+
55
+ option :user,
56
+ description: 'MongoDB user',
57
+ long: '--user USER',
58
+ default: nil
59
+
60
+ option :password,
61
+ description: 'MongoDB password',
62
+ long: '--password PASSWORD',
63
+ default: nil
64
+
65
+ option :ssl,
66
+ description: 'Connect using SSL',
67
+ long: '--ssl',
68
+ default: false
69
+
70
+ option :ssl_cert,
71
+ description: 'The certificate file used to identify the local connection against mongod',
72
+ long: '--ssl-cert SSL_CERT',
73
+ default: ''
74
+
75
+ option :ssl_key,
76
+ description: 'The private key used to identify the local connection against mongod',
77
+ long: '--ssl-key SSL_KEY',
78
+ default: ''
79
+
80
+ option :ssl_ca_cert,
81
+ description: 'The set of concatenated CA certificates, which are used to validate certificates passed from the other end of the connection',
82
+ long: '--ssl-ca-cert SSL_CA_CERT',
83
+ default: ''
84
+
85
+ option :ssl_verify,
86
+ description: 'Whether or not to do peer certification validation',
87
+ long: '--ssl-verify',
88
+ default: false
89
+
90
+ option :scheme,
91
+ description: 'Metric naming scheme',
92
+ long: '--scheme SCHEME',
93
+ short: '-s SCHEME',
94
+ default: "#{Socket.gethostname}.mongodb"
95
+
96
+ option :password,
97
+ description: 'MongoDB password',
98
+ long: '--password PASSWORD',
99
+ default: nil
100
+
101
+ option :debug,
102
+ description: 'Enable debug',
103
+ long: '--debug',
104
+ default: false
105
+
106
+ def get_mongo_doc(command)
107
+ rs = @db.command(command)
108
+ unless rs.successful?
109
+ return nil
110
+ end
111
+
112
+ rs.documents[0]
113
+ end
114
+
115
+ # connects to mongo and sets @db, works with MongoClient < 2.0.0
116
+ def connect_mongo_db
117
+ if Gem.loaded_specs['mongo'].version < Gem::Version.new('2.0.0')
118
+ mongo_client = MongoClient.new(host, port)
119
+ @db = mongo_client.db(db_name)
120
+ @db.authenticate(db_user, db_password) unless db_user.nil?
121
+ else
122
+ address_str = "#{config[:host]}:#{config[:port]}"
123
+ client_opts = {}
124
+ client_opts[:database] = 'admin'
125
+ unless config[:user].nil?
126
+ client_opts[:user] = config[:user]
127
+ client_opts[:password] = config[:password]
128
+ end
129
+ if config[:ssl]
130
+ client_opts[:ssl] = true
131
+ client_opts[:ssl_cert] = config[:ssl_cert]
132
+ client_opts[:ssl_key] = config[:ssl_key]
133
+ client_opts[:ssl_ca_cert] = config[:ssl_ca_cert]
134
+ client_opts[:ssl_verify] = config[:ssl_verify]
135
+ end
136
+ mongo_client = Mongo::Client.new([address_str], client_opts)
137
+ @db = mongo_client.database
138
+ end
139
+ end
140
+
141
+ def run
142
+ Mongo::Logger.logger.level = Logger::FATAL
143
+ @debug = config[:debug]
144
+ if @debug
145
+ Mongo::Logger.logger.level = Logger::DEBUG
146
+ config_debug = config.clone
147
+ config_debug[:password] = '***'
148
+ puts 'arguments:' + config_debug.inspect
149
+ end
150
+
151
+ connect_mongo_db
152
+
153
+ _result = false
154
+ # check if master
155
+ begin
156
+ @is_master = get_mongo_doc('isMaster' => 1)
157
+ unless @is_master.nil?
158
+ _result = @is_master['ok'] == 1
159
+ end
160
+ rescue StandardError => e
161
+ if @debug
162
+ puts 'Error checking isMaster:' + e.message
163
+ puts e.backtrace.inspect
164
+ end
165
+ exit(1)
166
+ end
167
+
168
+ replication_status = get_mongo_doc('replSetGetStatus' => 1)
169
+
170
+ # get the replication metrics
171
+ begin
172
+ metrics = {}
173
+ if !replication_status.nil? && replication_status['ok'] == 1
174
+ metrics.update(gather_replication_metrics(replication_status))
175
+ timestamp = Time.now.to_i
176
+ metrics.each do |k, v|
177
+ unless v.nil?
178
+ output [config[:scheme], 'replication', k].join('.'), v, timestamp
179
+ end
180
+ end
181
+ end
182
+ rescue StandardError => e
183
+ if @debug
184
+ puts 'Error checking replicationStatus:' + e.message
185
+ puts e.backtrace.inspect
186
+ end
187
+ exit(2)
188
+ end
189
+
190
+ # Get the repllication member metrics
191
+ begin
192
+ metrics = {}
193
+ replication_members = replication_status['members']
194
+ unless replication_members.nil?
195
+ state_map = {
196
+ 'PRIMARY' => 1,
197
+ 'SECONDARY' => 2
198
+ }
199
+ state_map.default = 3
200
+ replication_members.sort! { |x, y| state_map[x['stateStr']] <=> state_map[y['stateStr']] }
201
+
202
+ replication_members.each do |replication_member_details|
203
+ metrics.update(gather_replication_member_metrics(replication_member_details))
204
+ member_id = replication_member_details['_id']
205
+ timestamp = Time.now.to_i
206
+ metrics.each do |k, v|
207
+ unless v.nil?
208
+ output [config[:scheme], "member_#{member_id}", k].join('.'), v, timestamp
209
+ end
210
+ end
211
+ end
212
+ end
213
+ rescue StandardError => e
214
+ if @debug
215
+ puts 'Error checking replicationMemberStatus:' + e.message
216
+ puts e.backtrace.inspect
217
+ end
218
+ exit(2)
219
+ end
220
+
221
+ # done!
222
+ ok
223
+ end
224
+
225
+ def gather_replication_metrics(replication_status)
226
+ replication_metrics = {}
227
+
228
+ replication_metrics['replica_set'] = replication_status['set']
229
+ replication_metrics['date'] = replication_status['date']
230
+ replication_metrics['myState'] = replication_status['myState']
231
+ replication_metrics['term'] = replication_status['term']
232
+ replication_metrics['heartbeatIntervalMillis'] = replication_status['heartbeatIntervalMillis']
233
+
234
+ replication_metrics
235
+ end
236
+
237
+ def gather_replication_member_metrics(replication_member_details)
238
+ replication_member_metrics = {}
239
+
240
+ replication_member_metrics['id'] = replication_member_details['_id']
241
+ replication_member_metrics['name'] = replication_member_details['name']
242
+ replication_member_metrics['health'] = replication_member_details['health']
243
+ replication_member_metrics['state'] = replication_member_details['state']
244
+ replication_member_metrics['stateStr'] = replication_member_details['stateStr']
245
+ member_hierarchy = replication_member_details['stateStr']
246
+ if member_hierarchy == 'PRIMARY'
247
+ @primary_optime_date = replication_member_details['optimeDate']
248
+ replication_member_metrics['primary.startOptimeDate'] = @primary_optime_date
249
+ end
250
+ if member_hierarchy == 'SECONDARY'
251
+ @secondary_optime_date = replication_member_details['optimeDate']
252
+ difference_in_seconds = (@primary_optime_date - @secondary_optime_date).to_i
253
+ difference_in_minutes = ((@primary_optime_date - @secondary_optime_date) / 60).to_i
254
+ difference_in_hours = ((@primary_optime_date - @secondary_optime_date) / 3600).to_i
255
+ replication_member_metrics['secondsBehindPrimary'] = difference_in_seconds
256
+ replication_member_metrics['minutesBehindPrimary'] = difference_in_minutes
257
+ replication_member_metrics['hoursBehindPrimary'] = difference_in_hours
258
+ end
259
+ replication_member_metrics['optimeDate'] = replication_member_details['optimeDate']
260
+ replication_member_metrics['uptime'] = replication_member_details['uptime']
261
+ replication_member_metrics['lastHeartbeat'] = replication_member_details['lastHeartbeat']
262
+ replication_member_metrics['lastHeartbeatRecv'] = replication_member_details['lastHeartbeatiRecv']
263
+ replication_member_metrics['pingMs'] = replication_member_details['pingMs']
264
+ replication_member_metrics['syncingTo'] = replication_member_details['syncingTo']
265
+ replication_member_metrics['configVersion'] = replication_member_details['configVersion']
266
+
267
+ replication_member_metrics
268
+ end
269
+ end
@@ -0,0 +1,133 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # metrics-mongodb.rb
4
+ #
5
+ # DESCRIPTION:
6
+ #
7
+ # OUTPUT:
8
+ # metric data
9
+ #
10
+ # PLATFORMS:
11
+ # Linux
12
+ #
13
+ # DEPENDENCIES:
14
+ # gem: sensu-plugin
15
+ # gem: mongo
16
+ # gem: bson
17
+ # gem: bson_ext
18
+ #
19
+ # USAGE:
20
+ # #YELLOW
21
+ #
22
+ # NOTES:
23
+ # Basics from github.com/mantree/mongodb-graphite-metrics
24
+ #
25
+ # LICENSE:
26
+ # Copyright 2013 github.com/foomatty
27
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
28
+ # for details.
29
+ #
30
+
31
+ require 'sensu-plugin/metric/cli'
32
+ require 'sensu-plugins-mongodb/metrics'
33
+ require 'mongo'
34
+ include Mongo
35
+
36
+ #
37
+ # Mongodb
38
+ #
39
+
40
+ class MongoDB < Sensu::Plugin::Metric::CLI::Graphite
41
+ option :host,
42
+ description: 'MongoDB host',
43
+ long: '--host HOST',
44
+ default: 'localhost'
45
+
46
+ option :port,
47
+ description: 'MongoDB port',
48
+ long: '--port PORT',
49
+ default: 27_017
50
+
51
+ option :user,
52
+ description: 'MongoDB user',
53
+ long: '--user USER',
54
+ default: nil
55
+
56
+ option :password,
57
+ description: 'MongoDB password',
58
+ long: '--password PASSWORD',
59
+ default: nil
60
+
61
+ option :ssl,
62
+ description: 'Connect using SSL',
63
+ long: '--ssl',
64
+ default: false
65
+
66
+ option :ssl_cert,
67
+ description: 'The certificate file used to identify the local connection against mongod',
68
+ long: '--ssl-cert SSL_CERT',
69
+ default: ''
70
+
71
+ option :ssl_key,
72
+ description: 'The private key used to identify the local connection against mongod',
73
+ long: '--ssl-key SSL_KEY',
74
+ default: ''
75
+
76
+ option :ssl_ca_cert,
77
+ description: 'The set of concatenated CA certificates, which are used to validate certificates passed from the other end of the connection',
78
+ long: '--ssl-ca-cert SSL_CA_CERT',
79
+ default: ''
80
+
81
+ option :ssl_verify,
82
+ description: 'Whether or not to do peer certification validation',
83
+ long: '--ssl-verify',
84
+ default: false
85
+
86
+ option :debug,
87
+ description: 'Enable debug',
88
+ long: '--debug',
89
+ default: false
90
+
91
+ option :scheme,
92
+ description: 'Metric naming scheme',
93
+ long: '--scheme SCHEME',
94
+ short: '-s SCHEME',
95
+ default: "#{Socket.gethostname}.mongodb"
96
+
97
+ option :require_master,
98
+ description: 'Require the node to be a master node',
99
+ long: '--require-master',
100
+ default: false
101
+
102
+ option :exclude_db_sizes,
103
+ description: 'Exclude database sizes',
104
+ long: '--exclude-db-sizes',
105
+ default: false
106
+
107
+ def run
108
+ Mongo::Logger.logger.level = Logger::FATAL
109
+ @debug = config[:debug]
110
+ if @debug
111
+ Mongo::Logger.logger.level = Logger::DEBUG
112
+ config_debug = config.clone
113
+ config_debug[:password] = '***'
114
+ puts 'Arguments: ' + config_debug.inspect
115
+ end
116
+
117
+ # Get the metrics.
118
+ collector = SensuPluginsMongoDB::Metrics.new(config)
119
+ collector.connect_mongo_db('admin')
120
+ exit(1) if config[:require_master] && !collector.master?
121
+ metrics = collector.server_metrics
122
+ metrics = metrics.reject { |k, _v| k[/databaseSizes/] } if config[:exclude_db_sizes]
123
+
124
+ # Print them in graphite format.
125
+ timestamp = Time.now.to_i
126
+ metrics.each do |k, v|
127
+ output [config[:scheme], k].join('.'), v, timestamp
128
+ end
129
+
130
+ # done!
131
+ ok
132
+ end
133
+ end
@@ -0,0 +1 @@
1
+ require 'sensu-plugins-mongodb/version'
@@ -0,0 +1,399 @@
1
+ require 'mongo'
2
+ include Mongo
3
+
4
+ module SensuPluginsMongoDB
5
+ class Metrics
6
+ # Initializes a Metrics collector.
7
+ #
8
+ # @param config [Mesh]
9
+ # the config object parsed from the command line.
10
+ # Must include: :host, :port, :user, :password, :debug
11
+ def initialize(config)
12
+ @config = config
13
+ @connected = false
14
+ @db = nil
15
+ @mongo_client = nil
16
+ end
17
+
18
+ # Connects to a mongo database.
19
+ #
20
+ # @param db_name [String] the name of the db to connect to.
21
+ def connect_mongo_db(db_name)
22
+ if @connected
23
+ raise 'Already connected to a database'
24
+ end
25
+
26
+ db_user = @config[:user]
27
+ db_password = @config[:password]
28
+ @mongo_client = get_mongo_client(db_name)
29
+
30
+ if Gem.loaded_specs['mongo'].version < Gem::Version.new('2.0.0')
31
+ @db = @mongo_client.db(db_name)
32
+ @db.authenticate(db_user, db_password) unless db_user.nil?
33
+ else
34
+ @db = @mongo_client.database
35
+ end
36
+ end
37
+
38
+ # Fetches a document from the mongo db.
39
+ #
40
+ # @param command [Mesh] the command to search documents with.
41
+ # @return [Mesh, nil] the first document or nil.
42
+ def get_mongo_doc(command)
43
+ unless @connected
44
+ raise 'Cannot fetch documents before connecting.'
45
+ end
46
+ unless @db
47
+ raise 'Cannot fetch documents without a db.'
48
+ end
49
+
50
+ rs = @db.command(command)
51
+ unless rs.successful?
52
+ return nil
53
+ end
54
+
55
+ rs.documents[0]
56
+ end
57
+
58
+ # Checks if the connected node is the master node.
59
+ #
60
+ # @return [true, false] true when the node is a master node.
61
+ def master?
62
+ result = false
63
+ begin
64
+ @is_master = get_mongo_doc('isMaster' => 1)
65
+ unless @is_master.nil?
66
+ result = @is_master['ok'] == 1 && @is_master['ismaster']
67
+ end
68
+ rescue StandardError => e
69
+ if @config[:debug]
70
+ puts 'Error checking isMaster: ' + e.message
71
+ puts e.backtrace.inspect
72
+ end
73
+ end
74
+ result
75
+ end
76
+
77
+ # Fetches the status of the server (which includes the metrics).
78
+ #
79
+ # @return [Mash, nil] the document showing the server status or nil.
80
+ def server_status
81
+ status = get_mongo_doc('serverStatus' => 1)
82
+ return nil if status.nil? || status['ok'] != 1
83
+
84
+ status
85
+ rescue StandardError => e
86
+ if @debug
87
+ puts 'Error checking serverStatus: ' + e.message
88
+ puts e.backtrace.inspect
89
+ end
90
+ end
91
+
92
+ # Fetches the replicaset status of the server (which includes the metrics).
93
+ #
94
+ # @return [Mash, nil] the document showing the replicaset status or nil.
95
+ def replicaset_status
96
+ status = get_mongo_doc('replSetGetStatus' => 1)
97
+ return nil if status.nil?
98
+
99
+ status
100
+ rescue StandardError => e
101
+ if @debug
102
+ puts 'Error checking replSetGetStatus: ' + e.message
103
+ puts e.backtrace.inspect
104
+ end
105
+ end
106
+
107
+ # Fetches metrics for the server we are connected to.
108
+ #
109
+ # @return [Mash] the metrics for the server.
110
+ # rubocop:disable Metrics/AbcSize
111
+ def server_metrics
112
+ server_status = self.server_status
113
+ replicaset_status = self.replicaset_status
114
+ server_metrics = {}
115
+ # Handle versions like "2.6.11-pre" etc
116
+ mongo_version = server_status['version'].gsub(/[^0-9\.]/i, '')
117
+
118
+ server_metrics['lock.ratio'] = sprintf('%.5f', server_status['globalLock']['ratio']).to_s unless server_status['globalLock']['ratio'].nil?
119
+
120
+ # Asserts
121
+ asserts = server_status['asserts']
122
+ server_metrics['asserts.warnings'] = asserts['warning']
123
+ server_metrics['asserts.errors'] = asserts['msg']
124
+ server_metrics['asserts.regular'] = asserts['regular']
125
+ server_metrics['asserts.user'] = asserts['user']
126
+ server_metrics['asserts.rollovers'] = asserts['rollovers']
127
+
128
+ # Background flushing
129
+ if server_status.key?('backgroundFlushing')
130
+ bg_flushing = server_status['backgroundFlushing']
131
+ server_metrics['backgroundFlushing.flushes'] = bg_flushing['flushes']
132
+ server_metrics['backgroundFlushing.total_ms'] = bg_flushing['total_ms']
133
+ server_metrics['backgroundFlushing.average_ms'] = bg_flushing['average_ms']
134
+ server_metrics['backgroundFlushing.last_ms'] = bg_flushing['last_ms']
135
+ end
136
+
137
+ # Connections
138
+ connections = server_status['connections']
139
+ server_metrics['connections.current'] = connections['current']
140
+ server_metrics['connections.available'] = connections['available']
141
+ server_metrics['connections.totalCreated'] = connections['totalCreated']
142
+
143
+ # Cursors (use new metrics.cursor from mongo 2.6+)
144
+ if Gem::Version.new(mongo_version) < Gem::Version.new('2.6.0')
145
+ cursors = server_status['cursors']
146
+ server_metrics['clientCursors.size'] = cursors['clientCursors_size']
147
+ server_metrics['cursors.timedOut'] = cursors['timedOut']
148
+
149
+ # Metric names match the version 2.6+ format for standardization!
150
+ server_metrics['cursors.open.NoTimeout'] = cursors['totalNoTimeout']
151
+ server_metrics['cursors.open.pinned'] = cursors['pinned']
152
+ server_metrics['cursors.open.total'] = cursors['totalOpen']
153
+ else
154
+ cursors = server_status['metrics']['cursor']
155
+ server_metrics['cursors.timedOut'] = cursors['timedOut']
156
+ # clientCursors.size has been replaced by cursors.open.total
157
+
158
+ open = cursors['open']
159
+ server_metrics['cursors.open.noTimeout'] = open['noTimeout']
160
+ server_metrics['cursors.open.pinned'] = open['pinned']
161
+ server_metrics['cursors.open.total'] = open['total']
162
+
163
+ unless Gem::Version.new(mongo_version) < Gem::Version.new('3.0.0')
164
+ server_metrics['cursors.open.multiTarget'] = open['multiTarget']
165
+ server_metrics['cursors.open.singleTarget'] = open['singleTarget']
166
+ end
167
+ end
168
+
169
+ # Database Sizes
170
+ @mongo_client.database_names.each do |name|
171
+ @mongo_client = @mongo_client.use(name)
172
+ db = @mongo_client.database
173
+ result = db.command(dbstats: 1).documents.first
174
+ server_metrics["databaseSizes.#{name}.collections"] = result['collections']
175
+ server_metrics["databaseSizes.#{name}.objects"] = result['objects']
176
+ server_metrics["databaseSizes.#{name}.avgObjSize"] = result['avgObjSize']
177
+ server_metrics["databaseSizes.#{name}.dataSize"] = result['dataSize']
178
+ server_metrics["databaseSizes.#{name}.storageSize"] = result['storageSize']
179
+ server_metrics["databaseSizes.#{name}.numExtents"] = result['numExtents']
180
+ server_metrics["databaseSizes.#{name}.indexes"] = result['indexes']
181
+ server_metrics["databaseSizes.#{name}.indexSize"] = result['indexSize']
182
+ server_metrics["databaseSizes.#{name}.fileSize"] = result['fileSize']
183
+ server_metrics["databaseSizes.#{name}.nsSizeMB"] = result['nsSizeMB']
184
+ end
185
+ # Reset back to previous database
186
+ @mongo_client = @mongo_client.use(@db.name)
187
+
188
+ # Journaling (durability)
189
+ if server_status.key?('dur')
190
+ dur = server_status['dur']
191
+ server_metrics['journal.commits'] = dur['commits']
192
+ server_metrics['journaled_MB'] = dur['journaledMB']
193
+ server_metrics['journal.timeMs.writeToDataFiles'] = dur['timeMs']['writeToDataFiles']
194
+ server_metrics['journal.writeToDataFilesMB'] = dur['writeToDataFilesMB']
195
+ server_metrics['journal.compression'] = dur['compression']
196
+ server_metrics['journal.commitsInWriteLock'] = dur['commitsInWriteLock']
197
+ server_metrics['journal.timeMs.dt'] = dur['timeMs']['dt']
198
+ server_metrics['journal.timeMs.prepLogBuffer'] = dur['timeMs']['prepLogBuffer']
199
+ server_metrics['journal.timeMs.writeToJournal'] = dur['timeMs']['writeToJournal']
200
+ server_metrics['journal.timeMs.remapPrivateView'] = dur['timeMs']['remapPrivateView']
201
+ end
202
+
203
+ # Extra info
204
+ extra_info = server_status['extra_info']
205
+ server_metrics['mem.heap_usage_bytes'] = extra_info['heap_usage_bytes']
206
+ server_metrics['mem.pageFaults'] = extra_info['page_faults']
207
+
208
+ # Global Lock
209
+ global_lock = server_status['globalLock']
210
+ server_metrics['lock.totalTime'] = global_lock['totalTime']
211
+ server_metrics['lock.queue_total'] = global_lock['currentQueue']['total']
212
+ server_metrics['lock.queue_readers'] = global_lock['currentQueue']['readers']
213
+ server_metrics['lock.queue_writers'] = global_lock['currentQueue']['writers']
214
+ server_metrics['lock.clients_total'] = global_lock['activeClients']['total']
215
+ server_metrics['lock.clients_readers'] = global_lock['activeClients']['readers']
216
+ server_metrics['lock.clients_writers'] = global_lock['activeClients']['writers']
217
+
218
+ # Index counters
219
+ if Gem::Version.new(mongo_version) < Gem::Version.new('3.0.0')
220
+ index_counters = server_status['indexCounters']
221
+ index_counters = server_status['indexCounters']['btree'] unless server_status['indexCounters']['btree'].nil?
222
+
223
+ server_metrics['indexes.missRatio'] = sprintf('%.5f', index_counters['missRatio']).to_s
224
+ server_metrics['indexes.hits'] = index_counters['hits']
225
+ server_metrics['indexes.misses'] = index_counters['misses']
226
+ server_metrics['indexes.accesses'] = index_counters['accesses']
227
+ server_metrics['indexes.resets'] = index_counters['resets']
228
+ end
229
+
230
+ # Locks (from mongo 3.0+ only)
231
+ unless Gem::Version.new(mongo_version) < Gem::Version.new('3.0.0')
232
+ locks = server_status['locks']
233
+ lock_namespaces = %w[
234
+ Collection Global Database Metadata
235
+ MMAPV1Journal oplog
236
+ ]
237
+ lock_dimentions = %w[
238
+ acquireCount acquireWaitCount
239
+ timeAcquiringMicros deadlockCount
240
+ ]
241
+
242
+ lock_namespaces.each do |ns|
243
+ lock_dimentions.each do |dm|
244
+ next unless locks.key?(ns) && locks[ns].key?(dm)
245
+
246
+ lock = locks[ns][dm]
247
+ server_metrics["locks.#{ns}.#{dm}_r"] = lock['r'] if lock.key?('r')
248
+ server_metrics["locks.#{ns}.#{dm}_w"] = lock['r'] if lock.key?('w')
249
+ server_metrics["locks.#{ns}.#{dm}_R"] = lock['r'] if lock.key?('R')
250
+ server_metrics["locks.#{ns}.#{dm}_W"] = lock['r'] if lock.key?('W')
251
+ end
252
+ end
253
+ end
254
+
255
+ # Network
256
+ network = server_status['network']
257
+ server_metrics['network.bytesIn'] = network['bytesIn']
258
+ server_metrics['network.bytesOut'] = network['bytesOut']
259
+ server_metrics['network.numRequests'] = network['numRequests']
260
+
261
+ # Opcounters
262
+ opcounters = server_status['opcounters']
263
+ server_metrics['opcounters.insert'] = opcounters['insert']
264
+ server_metrics['opcounters.query'] = opcounters['query']
265
+ server_metrics['opcounters.update'] = opcounters['update']
266
+ server_metrics['opcounters.delete'] = opcounters['delete']
267
+ server_metrics['opcounters.getmore'] = opcounters['getmore']
268
+ server_metrics['opcounters.command'] = opcounters['command']
269
+
270
+ # Opcounters Replication
271
+ opcounters_repl = server_status['opcountersRepl']
272
+ server_metrics['opcountersRepl.insert'] = opcounters_repl['insert']
273
+ server_metrics['opcountersRepl.query'] = opcounters_repl['query']
274
+ server_metrics['opcountersRepl.update'] = opcounters_repl['update']
275
+ server_metrics['opcountersRepl.delete'] = opcounters_repl['delete']
276
+ server_metrics['opcountersRepl.getmore'] = opcounters_repl['getmore']
277
+ server_metrics['opcountersRepl.command'] = opcounters_repl['command']
278
+
279
+ # Memory
280
+ mem = server_status['mem']
281
+ server_metrics['mem.residentMb'] = mem['resident']
282
+ server_metrics['mem.virtualMb'] = mem['virtual']
283
+ server_metrics['mem.mapped'] = mem['mapped']
284
+ server_metrics['mem.mappedWithJournal'] = mem['mappedWithJournal']
285
+
286
+ # Metrics (documents)
287
+ document = server_status['metrics']['document']
288
+ server_metrics['metrics.document.deleted'] = document['deleted']
289
+ server_metrics['metrics.document.inserted'] = document['inserted']
290
+ server_metrics['metrics.document.returned'] = document['returned']
291
+ server_metrics['metrics.document.updated'] = document['updated']
292
+
293
+ # Metrics (getLastError)
294
+ get_last_error = server_status['metrics']['getLastError']
295
+ server_metrics['metrics.getLastError.wtime_num'] = get_last_error['wtime']['num']
296
+ server_metrics['metrics.getLastError.wtime_totalMillis'] = get_last_error['wtime']['totalMillis']
297
+ server_metrics['metrics.getLastError.wtimeouts'] = get_last_error['wtimeouts']
298
+
299
+ # Metrics (operation)
300
+ operation = server_status['metrics']['operation']
301
+ server_metrics['metrics.operation.fastmod'] = operation['fastmod']
302
+ server_metrics['metrics.operation.idhack'] = operation['idhack']
303
+ server_metrics['metrics.operation.scanAndOrder'] = operation['scanAndOrder']
304
+
305
+ # Metrics (operation)
306
+ query_executor = server_status['metrics']['queryExecutor']
307
+ server_metrics['metrics.queryExecutor.scanned'] = query_executor['scanned']
308
+ server_metrics['metrics.queryExecutor.scannedObjects'] = query_executor['scannedObjects']
309
+ server_metrics['metrics.record.moves'] = server_status['metrics']['record']['moves']
310
+
311
+ # Metrics (repl)
312
+ repl = server_status['metrics']['repl']
313
+ server_metrics['metrics.repl.apply.batches_num'] = repl['apply']['batches']['num']
314
+ server_metrics['metrics.repl.apply.batches_totalMillis'] = repl['apply']['batches']['totalMillis']
315
+ server_metrics['metrics.repl.apply.ops'] = repl['apply']['ops']
316
+ server_metrics['metrics.repl.buffer.count'] = repl['buffer']['count']
317
+ server_metrics['metrics.repl.buffer.maxSizeBytes'] = repl['buffer']['maxSizeBytes']
318
+ server_metrics['metrics.repl.buffer.sizeBytes'] = repl['buffer']['sizeBytes']
319
+ server_metrics['metrics.repl.network.bytes'] = repl['network']['bytes']
320
+ server_metrics['metrics.repl.network.getmores_num'] = repl['network']['getmores']['num']
321
+ server_metrics['metrics.repl.network.getmores_totalMillis'] = repl['network']['getmores']['totalMillis']
322
+ server_metrics['metrics.repl.network.ops'] = repl['network']['ops']
323
+ server_metrics['metrics.repl.network.readersCreated'] = repl['network']['readersCreated']
324
+ server_metrics['metrics.repl.preload.docs_num'] = repl['preload']['docs']['num']
325
+ server_metrics['metrics.repl.preload.docs_totalMillis'] = repl['preload']['docs']['totalMillis']
326
+ server_metrics['metrics.repl.preload.indexes_num'] = repl['preload']['indexes']['num']
327
+ server_metrics['metrics.repl.preload.indexes_totalMillis'] = repl['preload']['indexes']['totalMillis']
328
+
329
+ # Metrics (replicaset status)
330
+ # MongoDB will fail if not running with --replSet, hence the check for nil
331
+ unless replicaset_status.nil?
332
+ server_metrics['metrics.replicaset.state'] = replicaset_status['myState']
333
+ end
334
+
335
+ # Metrics (storage)
336
+ if Gem::Version.new(mongo_version) >= Gem::Version.new('2.6.0')
337
+ freelist = server_status['metrics']['storage']['freelist']
338
+ server_metrics['metrics.storage.freelist.search_bucketExhauseted'] = freelist['search']['bucketExhausted']
339
+ server_metrics['metrics.storage.freelist.search_requests'] = freelist['search']['requests']
340
+ server_metrics['metrics.storage.freelist.search_scanned'] = freelist['search']['scanned']
341
+ end
342
+
343
+ # Metrics (ttl)
344
+ ttl = server_status['metrics']['ttl']
345
+ server_metrics['metrics.ttl.deletedDocuments'] = ttl['deletedDocuments']
346
+ server_metrics['metrics.ttl.passes'] = ttl['passes']
347
+
348
+ # Return metrics map.
349
+ # MongoDB returns occasional nils and floats as {"floatApprox": x}.
350
+ # Clean up the results once here to avoid per-metric logic.
351
+ clean_metrics = {}
352
+ server_metrics.each do |k, v|
353
+ next if v.nil?
354
+
355
+ if v.is_a?(Hash) && v.key?('floatApprox')
356
+ v = v['floatApprox']
357
+ end
358
+ clean_metrics[k] = v
359
+ end
360
+ clean_metrics
361
+ end
362
+ # rubocop:enable Metrics/AbcSize
363
+
364
+ private
365
+
366
+ def get_mongo_client(db_name)
367
+ @connected = true
368
+ host = @config[:host]
369
+ port = @config[:port]
370
+ db_user = @config[:user]
371
+ db_password = @config[:password]
372
+ ssl = @config[:ssl]
373
+ ssl_cert = @config[:ssl_cert]
374
+ ssl_key = @config[:ssl_key]
375
+ ssl_ca_cert = @config[:ssl_ca_cert]
376
+ ssl_verify = @config[:ssl_verify]
377
+
378
+ if Gem.loaded_specs['mongo'].version < Gem::Version.new('2.0.0')
379
+ MongoClient.new(host, port)
380
+ else
381
+ address_str = "#{host}:#{port}"
382
+ client_opts = {}
383
+ client_opts[:database] = db_name
384
+ unless db_user.nil?
385
+ client_opts[:user] = db_user
386
+ client_opts[:password] = db_password
387
+ end
388
+ if ssl
389
+ client_opts[:ssl] = true
390
+ client_opts[:ssl_cert] = ssl_cert
391
+ client_opts[:ssl_key] = ssl_key
392
+ client_opts[:ssl_ca_cert] = ssl_ca_cert
393
+ client_opts[:ssl_verify] = ssl_verify
394
+ end
395
+ Mongo::Client.new([address_str], client_opts)
396
+ end
397
+ end
398
+ end
399
+ end