sensu-plugins-mongodb-wt 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ bin_dir = File.expand_path(File.dirname(__FILE__))
4
+ shell_script_path = File.join(bin_dir, File.basename($PROGRAM_NAME, '.rb') + '.py')
5
+
6
+ exec shell_script_path, *ARGV
@@ -0,0 +1,268 @@
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
+ rs.documents[0]
112
+ end
113
+
114
+ # connects to mongo and sets @db, works with MongoClient < 2.0.0
115
+ def connect_mongo_db
116
+ if Gem.loaded_specs['mongo'].version < Gem::Version.new('2.0.0')
117
+ mongo_client = MongoClient.new(host, port)
118
+ @db = mongo_client.db(db_name)
119
+ @db.authenticate(db_user, db_password) unless db_user.nil?
120
+ else
121
+ address_str = "#{config[:host]}:#{config[:port]}"
122
+ client_opts = {}
123
+ client_opts[:database] = 'admin'
124
+ unless config[:user].nil?
125
+ client_opts[:user] = config[:user]
126
+ client_opts[:password] = config[:password]
127
+ end
128
+ if config[:ssl]
129
+ client_opts[:ssl] = true
130
+ client_opts[:ssl_cert] = config[:ssl_cert]
131
+ client_opts[:ssl_key] = config[:ssl_key]
132
+ client_opts[:ssl_ca_cert] = config[:ssl_ca_cert]
133
+ client_opts[:ssl_verify] = config[:ssl_verify]
134
+ end
135
+ mongo_client = Mongo::Client.new([address_str], client_opts)
136
+ @db = mongo_client.database
137
+ end
138
+ end
139
+
140
+ def run
141
+ Mongo::Logger.logger.level = Logger::FATAL
142
+ @debug = config[:debug]
143
+ if @debug
144
+ Mongo::Logger.logger.level = Logger::DEBUG
145
+ config_debug = config.clone
146
+ config_debug[:password] = '***'
147
+ puts 'arguments:' + config_debug.inspect
148
+ end
149
+
150
+ connect_mongo_db
151
+
152
+ _result = false
153
+ # check if master
154
+ begin
155
+ @is_master = get_mongo_doc('isMaster' => 1)
156
+ unless @is_master.nil?
157
+ _result = @is_master['ok'] == 1
158
+ end
159
+ rescue StandardError => e
160
+ if @debug
161
+ puts 'Error checking isMaster:' + e.message
162
+ puts e.backtrace.inspect
163
+ end
164
+ exit(1)
165
+ end
166
+
167
+ replication_status = get_mongo_doc('replSetGetStatus' => 1)
168
+
169
+ # get the replication metrics
170
+ begin
171
+ metrics = {}
172
+ if !replication_status.nil? && replication_status['ok'] == 1
173
+ metrics.update(gather_replication_metrics(replication_status))
174
+ timestamp = Time.now.to_i
175
+ metrics.each do |k, v|
176
+ unless v.nil?
177
+ output [config[:scheme], 'replication', k].join('.'), v, timestamp
178
+ end
179
+ end
180
+ end
181
+ rescue StandardError => e
182
+ if @debug
183
+ puts 'Error checking replicationStatus:' + e.message
184
+ puts e.backtrace.inspect
185
+ end
186
+ exit(2)
187
+ end
188
+
189
+ # Get the repllication member metrics
190
+ begin
191
+ metrics = {}
192
+ replication_members = replication_status['members']
193
+ unless replication_members.nil?
194
+ state_map = {
195
+ 'PRIMARY' => 1,
196
+ 'SECONDARY' => 2
197
+ }
198
+ state_map.default = 3
199
+ replication_members.sort! { |x, y| state_map[x['stateStr']] <=> state_map[y['stateStr']] }
200
+
201
+ replication_members.each do |replication_member_details|
202
+ metrics.update(gather_replication_member_metrics(replication_member_details))
203
+ member_id = replication_member_details['_id']
204
+ timestamp = Time.now.to_i
205
+ metrics.each do |k, v|
206
+ unless v.nil?
207
+ output [config[:scheme], "member_#{member_id}", k].join('.'), v, timestamp
208
+ end
209
+ end
210
+ end
211
+ end
212
+ rescue StandardError => e
213
+ if @debug
214
+ puts 'Error checking replicationMemberStatus:' + e.message
215
+ puts e.backtrace.inspect
216
+ end
217
+ exit(2)
218
+ end
219
+
220
+ # done!
221
+ ok
222
+ end
223
+
224
+ def gather_replication_metrics(replication_status)
225
+ replication_metrics = {}
226
+
227
+ replication_metrics['replica_set'] = replication_status['set']
228
+ replication_metrics['date'] = replication_status['date']
229
+ replication_metrics['myState'] = replication_status['myState']
230
+ replication_metrics['term'] = replication_status['term']
231
+ replication_metrics['heartbeatIntervalMillis'] = replication_status['heartbeatIntervalMillis']
232
+
233
+ replication_metrics
234
+ end
235
+
236
+ def gather_replication_member_metrics(replication_member_details)
237
+ replication_member_metrics = {}
238
+
239
+ replication_member_metrics['id'] = replication_member_details['_id']
240
+ replication_member_metrics['name'] = replication_member_details['name']
241
+ replication_member_metrics['health'] = replication_member_details['health']
242
+ replication_member_metrics['state'] = replication_member_details['state']
243
+ replication_member_metrics['stateStr'] = replication_member_details['stateStr']
244
+ member_hierarchy = replication_member_details['stateStr']
245
+ if member_hierarchy == 'PRIMARY'
246
+ @primary_optime_date = replication_member_details['optimeDate']
247
+ replication_member_metrics['primary.startOptimeDate'] = @primary_optime_date
248
+ end
249
+ if member_hierarchy == 'SECONDARY'
250
+ @secondary_optime_date = replication_member_details['optimeDate']
251
+ difference_in_seconds = (@primary_optime_date - @secondary_optime_date).to_i
252
+ difference_in_minutes = ((@primary_optime_date - @secondary_optime_date) / 60).to_i
253
+ difference_in_hours = ((@primary_optime_date - @secondary_optime_date) / 3600).to_i
254
+ replication_member_metrics['secondsBehindPrimary'] = difference_in_seconds
255
+ replication_member_metrics['minutesBehindPrimary'] = difference_in_minutes
256
+ replication_member_metrics['hoursBehindPrimary'] = difference_in_hours
257
+ end
258
+ replication_member_metrics['optimeDate'] = replication_member_details['optimeDate']
259
+ replication_member_metrics['uptime'] = replication_member_details['uptime']
260
+ replication_member_metrics['lastHeartbeat'] = replication_member_details['lastHeartbeat']
261
+ replication_member_metrics['lastHeartbeatRecv'] = replication_member_details['lastHeartbeatiRecv']
262
+ replication_member_metrics['pingMs'] = replication_member_details['pingMs']
263
+ replication_member_metrics['syncingTo'] = replication_member_details['syncingTo']
264
+ replication_member_metrics['configVersion'] = replication_member_details['configVersion']
265
+
266
+ replication_member_metrics
267
+ end
268
+ 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,449 @@
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
+
29
+ if Gem.loaded_specs['mongo'].version < Gem::Version.new('2.0.0')
30
+ @mongo_client = get_mongo_client(db_name)
31
+ @db = @mongo_client.db(db_name)
32
+ @db.authenticate(db_user, db_password) unless db_user.nil?
33
+ else
34
+ @mongo_client = get_mongo_client(db_name)
35
+ @db = @mongo_client.database
36
+ end
37
+ end
38
+
39
+ # Fetches a document from the mongo db.
40
+ #
41
+ # @param command [Mesh] the command to search documents with.
42
+ # @return [Mesh, nil] the first document or nil.
43
+ def get_mongo_doc(command)
44
+ unless @connected
45
+ raise 'Cannot fetch documents before connecting.'
46
+ end
47
+ unless @db
48
+ raise 'Cannot fetch documents without a db.'
49
+ end
50
+
51
+ rs = @db.command(command)
52
+ unless rs.successful?
53
+ return nil
54
+ end
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
+ return status
84
+ rescue StandardError => e
85
+ if @debug
86
+ puts 'Error checking serverStatus: ' + e.message
87
+ puts e.backtrace.inspect
88
+ end
89
+ end
90
+
91
+ # Fetches the replicaset status of the server (which includes the metrics).
92
+ #
93
+ # @return [Mash, nil] the document showing the replicaset status or nil.
94
+ def replicaset_status
95
+ status = get_mongo_doc('replSetGetStatus' => 1)
96
+ return nil if status.nil?
97
+ return status
98
+ rescue StandardError => e
99
+ if @debug
100
+ puts 'Error checking replSetGetStatus: ' + e.message
101
+ puts e.backtrace.inspect
102
+ end
103
+ end
104
+
105
+ # Fetches metrics for the server we are connected to.
106
+ #
107
+ # @return [Mash] the metrics for the server.
108
+ # rubocop:disable Metrics/AbcSize
109
+ def server_metrics
110
+ server_status = self.server_status
111
+ replicaset_status = self.replicaset_status
112
+ server_metrics = {}
113
+ # Handle versions like "2.6.11-pre" etc
114
+ mongo_version = server_status['version'].gsub(/[^0-9\.]/i, '')
115
+
116
+ server_metrics['lock.ratio'] = sprintf('%.5f', server_status['globalLock']['ratio']).to_s unless server_status['globalLock']['ratio'].nil?
117
+
118
+ # Asserts
119
+ asserts = server_status['asserts']
120
+ server_metrics['asserts.warnings'] = asserts['warning']
121
+ server_metrics['asserts.errors'] = asserts['msg']
122
+ server_metrics['asserts.regular'] = asserts['regular']
123
+ server_metrics['asserts.user'] = asserts['user']
124
+ server_metrics['asserts.rollovers'] = asserts['rollovers']
125
+
126
+ # Background flushing
127
+ if server_status.key?('backgroundFlushing')
128
+ bg_flushing = server_status['backgroundFlushing']
129
+ server_metrics['backgroundFlushing.flushes'] = bg_flushing['flushes']
130
+ server_metrics['backgroundFlushing.total_ms'] = bg_flushing['total_ms']
131
+ server_metrics['backgroundFlushing.average_ms'] = bg_flushing['average_ms']
132
+ server_metrics['backgroundFlushing.last_ms'] = bg_flushing['last_ms']
133
+ end
134
+
135
+ # Connections
136
+ connections = server_status['connections']
137
+ server_metrics['connections.current'] = connections['current']
138
+ server_metrics['connections.available'] = connections['available']
139
+ server_metrics['connections.totalCreated'] = connections['totalCreated']
140
+
141
+ # Cursors (use new metrics.cursor from mongo 2.6+)
142
+ if Gem::Version.new(mongo_version) < Gem::Version.new('2.6.0')
143
+ cursors = server_status['cursors']
144
+ server_metrics['clientCursors.size'] = cursors['clientCursors_size']
145
+ server_metrics['cursors.timedOut'] = cursors['timedOut']
146
+
147
+ # Metric names match the version 2.6+ format for standardization!
148
+ server_metrics['cursors.open.NoTimeout'] = cursors['totalNoTimeout']
149
+ server_metrics['cursors.open.pinned'] = cursors['pinned']
150
+ server_metrics['cursors.open.total'] = cursors['totalOpen']
151
+ else
152
+ cursors = server_status['metrics']['cursor']
153
+ server_metrics['cursors.timedOut'] = cursors['timedOut']
154
+ # clientCursors.size has been replaced by cursors.open.total
155
+
156
+ open = cursors['open']
157
+ server_metrics['cursors.open.noTimeout'] = open['noTimeout']
158
+ server_metrics['cursors.open.pinned'] = open['pinned']
159
+ server_metrics['cursors.open.total'] = open['total']
160
+
161
+ unless Gem::Version.new(mongo_version) < Gem::Version.new('3.0.0')
162
+ server_metrics['cursors.open.multiTarget'] = open['multiTarget']
163
+ server_metrics['cursors.open.singleTarget'] = open['singleTarget']
164
+ end
165
+ end
166
+
167
+ # Database Sizes
168
+ @mongo_client.database_names.each do |name|
169
+ @mongo_client = @mongo_client.use(name)
170
+ db = @mongo_client.database
171
+ result = db.command(dbstats: 1).documents.first
172
+ server_metrics["databaseSizes.#{name}.collections"] = result['collections']
173
+ server_metrics["databaseSizes.#{name}.objects"] = result['objects']
174
+ server_metrics["databaseSizes.#{name}.avgObjSize"] = result['avgObjSize']
175
+ server_metrics["databaseSizes.#{name}.dataSize"] = result['dataSize']
176
+ server_metrics["databaseSizes.#{name}.storageSize"] = result['storageSize']
177
+ server_metrics["databaseSizes.#{name}.numExtents"] = result['numExtents']
178
+ server_metrics["databaseSizes.#{name}.indexes"] = result['indexes']
179
+ server_metrics["databaseSizes.#{name}.indexSize"] = result['indexSize']
180
+ server_metrics["databaseSizes.#{name}.fileSize"] = result['fileSize']
181
+ server_metrics["databaseSizes.#{name}.nsSizeMB"] = result['nsSizeMB']
182
+ end
183
+ # Reset back to previous database
184
+ @mongo_client = @mongo_client.use(@db.name)
185
+
186
+ # Journaling (durability)
187
+ if server_status.key?('dur')
188
+ dur = server_status['dur']
189
+ server_metrics['journal.commits'] = dur['commits']
190
+ server_metrics['journaled_MB'] = dur['journaledMB']
191
+ server_metrics['journal.timeMs.writeToDataFiles'] = dur['timeMs']['writeToDataFiles']
192
+ server_metrics['journal.writeToDataFilesMB'] = dur['writeToDataFilesMB']
193
+ server_metrics['journal.compression'] = dur['compression']
194
+ server_metrics['journal.commitsInWriteLock'] = dur['commitsInWriteLock']
195
+ server_metrics['journal.timeMs.dt'] = dur['timeMs']['dt']
196
+ server_metrics['journal.timeMs.prepLogBuffer'] = dur['timeMs']['prepLogBuffer']
197
+ server_metrics['journal.timeMs.writeToJournal'] = dur['timeMs']['writeToJournal']
198
+ server_metrics['journal.timeMs.remapPrivateView'] = dur['timeMs']['remapPrivateView']
199
+ end
200
+
201
+ # Extra info
202
+ extra_info = server_status['extra_info']
203
+ server_metrics['mem.heap_usage_bytes'] = extra_info['heap_usage_bytes']
204
+ server_metrics['mem.pageFaults'] = extra_info['page_faults']
205
+
206
+ # Global Lock
207
+ global_lock = server_status['globalLock']
208
+ server_metrics['lock.totalTime'] = global_lock['totalTime']
209
+ server_metrics['lock.queue_total'] = global_lock['currentQueue']['total']
210
+ server_metrics['lock.queue_readers'] = global_lock['currentQueue']['readers']
211
+ server_metrics['lock.queue_writers'] = global_lock['currentQueue']['writers']
212
+ server_metrics['lock.clients_total'] = global_lock['activeClients']['total']
213
+ server_metrics['lock.clients_readers'] = global_lock['activeClients']['readers']
214
+ server_metrics['lock.clients_writers'] = global_lock['activeClients']['writers']
215
+
216
+ # Index counters
217
+ if Gem::Version.new(mongo_version) < Gem::Version.new('3.0.0')
218
+ index_counters = server_status['indexCounters']
219
+ index_counters = server_status['indexCounters']['btree'] unless server_status['indexCounters']['btree'].nil?
220
+
221
+ server_metrics['indexes.missRatio'] = sprintf('%.5f', index_counters['missRatio']).to_s
222
+ server_metrics['indexes.hits'] = index_counters['hits']
223
+ server_metrics['indexes.misses'] = index_counters['misses']
224
+ server_metrics['indexes.accesses'] = index_counters['accesses']
225
+ server_metrics['indexes.resets'] = index_counters['resets']
226
+ end
227
+
228
+ # Locks (from mongo 3.0+ only)
229
+ unless Gem::Version.new(mongo_version) < Gem::Version.new('3.0.0')
230
+ locks = server_status['locks']
231
+ lock_namespaces = %w(
232
+ Collection Global Database Metadata
233
+ MMAPV1Journal oplog
234
+ )
235
+ lock_dimentions = %w(
236
+ acquireCount acquireWaitCount
237
+ timeAcquiringMicros deadlockCount
238
+ )
239
+
240
+ lock_namespaces.each do |ns|
241
+ lock_dimentions.each do |dm|
242
+ next unless locks.key?(ns) && locks[ns].key?(dm)
243
+ lock = locks[ns][dm]
244
+ server_metrics["locks.#{ns}.#{dm}_r"] = lock['r'] if lock.key?('r')
245
+ server_metrics["locks.#{ns}.#{dm}_w"] = lock['r'] if lock.key?('w')
246
+ server_metrics["locks.#{ns}.#{dm}_R"] = lock['r'] if lock.key?('R')
247
+ server_metrics["locks.#{ns}.#{dm}_W"] = lock['r'] if lock.key?('W')
248
+ end
249
+ end
250
+ end
251
+
252
+ # Network
253
+ network = server_status['network']
254
+ server_metrics['network.bytesIn'] = network['bytesIn']
255
+ server_metrics['network.bytesOut'] = network['bytesOut']
256
+ server_metrics['network.numRequests'] = network['numRequests']
257
+
258
+ # Opcounters
259
+ opcounters = server_status['opcounters']
260
+ server_metrics['opcounters.insert'] = opcounters['insert']
261
+ server_metrics['opcounters.query'] = opcounters['query']
262
+ server_metrics['opcounters.update'] = opcounters['update']
263
+ server_metrics['opcounters.delete'] = opcounters['delete']
264
+ server_metrics['opcounters.getmore'] = opcounters['getmore']
265
+ server_metrics['opcounters.command'] = opcounters['command']
266
+
267
+ # Opcounters Replication
268
+ opcounters_repl = server_status['opcountersRepl']
269
+ server_metrics['opcountersRepl.insert'] = opcounters_repl['insert']
270
+ server_metrics['opcountersRepl.query'] = opcounters_repl['query']
271
+ server_metrics['opcountersRepl.update'] = opcounters_repl['update']
272
+ server_metrics['opcountersRepl.delete'] = opcounters_repl['delete']
273
+ server_metrics['opcountersRepl.getmore'] = opcounters_repl['getmore']
274
+ server_metrics['opcountersRepl.command'] = opcounters_repl['command']
275
+
276
+ # Memory
277
+ mem = server_status['mem']
278
+ server_metrics['mem.residentMb'] = mem['resident']
279
+ server_metrics['mem.virtualMb'] = mem['virtual']
280
+ server_metrics['mem.mapped'] = mem['mapped']
281
+ server_metrics['mem.mappedWithJournal'] = mem['mappedWithJournal']
282
+
283
+ # Metrics (documents)
284
+ document = server_status['metrics']['document']
285
+ server_metrics['metrics.document.deleted'] = document['deleted']
286
+ server_metrics['metrics.document.inserted'] = document['inserted']
287
+ server_metrics['metrics.document.returned'] = document['returned']
288
+ server_metrics['metrics.document.updated'] = document['updated']
289
+
290
+ # Metrics (getLastError)
291
+ get_last_error = server_status['metrics']['getLastError']
292
+ server_metrics['metrics.getLastError.wtime_num'] = get_last_error['wtime']['num']
293
+ server_metrics['metrics.getLastError.wtime_totalMillis'] = get_last_error['wtime']['totalMillis']
294
+ server_metrics['metrics.getLastError.wtimeouts'] = get_last_error['wtimeouts']
295
+
296
+ # Metrics (operation)
297
+ operation = server_status['metrics']['operation']
298
+ server_metrics['metrics.operation.fastmod'] = operation['fastmod']
299
+ server_metrics['metrics.operation.idhack'] = operation['idhack']
300
+ server_metrics['metrics.operation.scanAndOrder'] = operation['scanAndOrder']
301
+
302
+ # Metrics (operation)
303
+ query_executor = server_status['metrics']['queryExecutor']
304
+ server_metrics['metrics.queryExecutor.scanned'] = query_executor['scanned']
305
+ server_metrics['metrics.queryExecutor.scannedObjects'] = query_executor['scannedObjects']
306
+ server_metrics['metrics.record.moves'] = server_status['metrics']['record']['moves']
307
+
308
+ # Metrics (repl)
309
+ repl = server_status['metrics']['repl']
310
+ server_metrics['metrics.repl.apply.batches_num'] = repl['apply']['batches']['num']
311
+ server_metrics['metrics.repl.apply.batches_totalMillis'] = repl['apply']['batches']['totalMillis']
312
+ server_metrics['metrics.repl.apply.ops'] = repl['apply']['ops']
313
+ server_metrics['metrics.repl.buffer.count'] = repl['buffer']['count']
314
+ server_metrics['metrics.repl.buffer.maxSizeBytes'] = repl['buffer']['maxSizeBytes']
315
+ server_metrics['metrics.repl.buffer.sizeBytes'] = repl['buffer']['sizeBytes']
316
+ server_metrics['metrics.repl.network.bytes'] = repl['network']['bytes']
317
+ server_metrics['metrics.repl.network.getmores_num'] = repl['network']['getmores']['num']
318
+ server_metrics['metrics.repl.network.getmores_totalMillis'] = repl['network']['getmores']['totalMillis']
319
+ server_metrics['metrics.repl.network.ops'] = repl['network']['ops']
320
+ server_metrics['metrics.repl.network.readersCreated'] = repl['network']['readersCreated']
321
+ server_metrics['metrics.repl.preload.docs_num'] = repl['preload']['docs']['num']
322
+ server_metrics['metrics.repl.preload.docs_totalMillis'] = repl['preload']['docs']['totalMillis']
323
+ server_metrics['metrics.repl.preload.indexes_num'] = repl['preload']['indexes']['num']
324
+ server_metrics['metrics.repl.preload.indexes_totalMillis'] = repl['preload']['indexes']['totalMillis']
325
+
326
+ # Metrics (replicaset status)
327
+ # MongoDB will fail if not running with --replSet, hence the check for nil
328
+ unless replicaset_status.nil?
329
+ server_metrics['metrics.replicaset.state'] = replicaset_status['myState']
330
+ end
331
+
332
+ # Metrics (storage)
333
+ if Gem::Version.new(mongo_version) >= Gem::Version.new('2.6.0')
334
+ freelist = server_status['metrics']['storage']['freelist']
335
+ server_metrics['metrics.storage.freelist.search_bucketExhauseted'] = freelist['search']['bucketExhausted']
336
+ server_metrics['metrics.storage.freelist.search_requests'] = freelist['search']['requests']
337
+ server_metrics['metrics.storage.freelist.search_scanned'] = freelist['search']['scanned']
338
+ end
339
+
340
+ # Metrics (ttl)
341
+ ttl = server_status['metrics']['ttl']
342
+ server_metrics['metrics.ttl.deletedDocuments'] = ttl['deletedDocuments']
343
+ server_metrics['metrics.ttl.passes'] = ttl['passes']
344
+
345
+ # Metrics (wired_tiger)
346
+ if server_status.key?('wiredTiger')
347
+
348
+ # block-manager
349
+ wired_tiger_block_manager = server_status['wiredTiger']['block-manager']
350
+ server_metrics['metrics.wired_tiger.block_manager.blocks_read'] = wired_tiger_block_manager['blocks read']
351
+ server_metrics['metrics.wired_tiger.block_manager.blocks_written'] = wired_tiger_block_manager['blocks written']
352
+ server_metrics['metrics.wired_tiger.block_manager.mapped_blocks_read'] = wired_tiger_block_manager['mapped blocks read']
353
+
354
+ # cache
355
+ wired_tiger_cache = server_status['wiredTiger']['cache']
356
+ server_metrics['metrics.wired_tiger.cache.bytes_currently_in_the_cache'] = wired_tiger_cache['bytes currently in the cache']
357
+ server_metrics['metrics.wired_tiger.cache.bytes_read_into_cache'] = wired_tiger_cache['bytes read into cache']
358
+ server_metrics['metrics.wired_tiger.cache.bytes_written_from_cache'] = wired_tiger_cache['bytes written from cache']
359
+ server_metrics['metrics.wired_tiger.cache.eviction_server_evicting_pages'] = wired_tiger_cache['eviction server evicting pages']
360
+ server_metrics['metrics.wired_tiger.cache.maximum_bytes_configured'] = wired_tiger_cache['maximum bytes configured']
361
+ server_metrics['metrics.wired_tiger.cache.modified_pages_evicted'] = wired_tiger_cache['modified pages evicted']
362
+ server_metrics['metrics.wired_tiger.cache.pages_currently_held_in_the_cache'] = wired_tiger_cache['pages currently held in the cache']
363
+ server_metrics['metrics.wired_tiger.cache.tracked_dirty_bytes_in_the_cache'] = wired_tiger_cache['tracked dirty bytes in the cache']
364
+ server_metrics['metrics.wired_tiger.cache.tracked_dirty_pages_in_the_cache'] = wired_tiger_cache['tracked dirty pages in the cache']
365
+ server_metrics['metrics.wired_tiger.cache.unmodified_pages_evicted'] = wired_tiger_cache['unmodified pages evicted']
366
+
367
+ # concurrentTransactions
368
+ wired_tiger_cc_tx = server_status['wiredTiger']['concurrentTransactions']
369
+ server_metrics['metrics.wired_tiger.concurrent_transaction.write.out'] = wired_tiger_cc_tx['write']['out']
370
+ server_metrics['metrics.wired_tiger.concurrent_transaction.write.available'] = wired_tiger_cc_tx['write']['available']
371
+ server_metrics['metrics.wired_tiger.concurrent_transaction.write.totalTickets'] = wired_tiger_cc_tx['write']['totalTickets']
372
+ server_metrics['metrics.wired_tiger.concurrent_transaction.read.out'] = wired_tiger_cc_tx['read']['out']
373
+ server_metrics['metrics.wired_tiger.concurrent_transaction.read.available'] = wired_tiger_cc_tx['read']['available']
374
+ server_metrics['metrics.wired_tiger.concurrent_transaction.read.totalTickets'] = wired_tiger_cc_tx['read']['totalTickets']
375
+
376
+ # log
377
+ wired_tiger_log = server_status['wiredTiger']['log']
378
+ server_metrics['metrics.wired_tiger.log.log_flush_operations'] = wired_tiger_log['log flush operations']
379
+ server_metrics['metrics.wired_tiger.log.log_bytes_written'] = wired_tiger_log['log bytes written']
380
+ server_metrics['metrics.wired_tiger.log.log_records_compressed'] = wired_tiger_log['log records compressed']
381
+ server_metrics['metrics.wired_tiger.log.log_records_not_compressed'] = wired_tiger_log['log records not compressed']
382
+ server_metrics['metrics.wired_tiger.log.log_sync_operations'] = wired_tiger_log['log sync operations']
383
+ server_metrics['metrics.wired_tiger.log.log_write_operations'] = wired_tiger_log['log write operations']
384
+
385
+ # session
386
+ wired_tiger_sessions = server_status['wiredTiger']['session']
387
+ server_metrics['metrics.wired_tiger.session.open_session_count'] = wired_tiger_sessions['open session count']
388
+
389
+ # transaction
390
+ wired_tiger_tx = server_status['wiredTiger']['transaction']
391
+ server_metrics['metrics.wired_tiger.transaction.transaction_checkpoint_max_time_msecs'] = wired_tiger_tx['transaction checkpoint max time (msecs)']
392
+ server_metrics['metrics.wired_tiger.transaction.transaction_checkpoint_min_time_msecs'] = wired_tiger_tx['transaction checkpoint min time (msecs)']
393
+ server_metrics['metrics.wired_tiger.transaction.transaction_checkpoint_most_recent_time_msecs'] = wired_tiger_tx[
394
+ 'transaction checkpoint most recent time (msecs)'
395
+ ]
396
+ server_metrics['metrics.wired_tiger.transaction.transactions_committed'] = wired_tiger_tx['transactions committed']
397
+ server_metrics['metrics.wired_tiger.transaction.transactions_rolled_back'] = wired_tiger_tx['transactions rolled back']
398
+ end
399
+
400
+ # Return metrics map.
401
+ # MongoDB returns occasional nils and floats as {"floatApprox": x}.
402
+ # Clean up the results once here to avoid per-metric logic.
403
+ clean_metrics = {}
404
+ server_metrics.each do |k, v|
405
+ next if v.nil?
406
+ if v.is_a?(Hash) && v.key?('floatApprox')
407
+ v = v['floatApprox']
408
+ end
409
+ clean_metrics[k] = v
410
+ end
411
+ clean_metrics
412
+ end
413
+
414
+ private
415
+
416
+ def get_mongo_client(db_name)
417
+ @connected = true
418
+ host = @config[:host]
419
+ port = @config[:port]
420
+ db_user = @config[:user]
421
+ db_password = @config[:password]
422
+ ssl = @config[:ssl]
423
+ ssl_cert = @config[:ssl_cert]
424
+ ssl_key = @config[:ssl_key]
425
+ ssl_ca_cert = @config[:ssl_ca_cert]
426
+ ssl_verify = @config[:ssl_verify]
427
+
428
+ if Gem.loaded_specs['mongo'].version < Gem::Version.new('2.0.0')
429
+ MongoClient.new(host, port)
430
+ else
431
+ address_str = "#{host}:#{port}"
432
+ client_opts = {}
433
+ client_opts[:database] = db_name
434
+ unless db_user.nil?
435
+ client_opts[:user] = db_user
436
+ client_opts[:password] = db_password
437
+ end
438
+ if ssl
439
+ client_opts[:ssl] = true
440
+ client_opts[:ssl_cert] = ssl_cert
441
+ client_opts[:ssl_key] = ssl_key
442
+ client_opts[:ssl_ca_cert] = ssl_ca_cert
443
+ client_opts[:ssl_verify] = ssl_verify
444
+ end
445
+ Mongo::Client.new([address_str], client_opts)
446
+ end
447
+ end
448
+ end
449
+ end