hq-mongodb-check-collection-size 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "hq/mongodb/check-collection-size/script"
4
+
5
+ script = HQ::MongoDB::CheckCollectionSize::Script.new
6
+ script.args = ARGV
7
+ script.main
8
+ exit script.status
@@ -0,0 +1,21 @@
1
+ Feature: The script can be run
2
+
3
+ Scenario: Run with basic options
4
+
5
+ When I run the following:
6
+ """
7
+ hq-mongodb-check-collection-size
8
+ --total-warning 2g
9
+ --total-critical 10g
10
+ --unsharded-warning 500m
11
+ --unsharded-critical 1g
12
+ --efficiency-warning 0.8
13
+ --efficiency-critical 0.5
14
+ --efficiency-size 100m
15
+ """
16
+
17
+ Then the exit status should be 0
18
+ And the output should match:
19
+ """
20
+ MongoDB collection size OK: biggest is (.+)
21
+ """
File without changes
@@ -0,0 +1,26 @@
1
+ When /^I run the following:$/ do
2
+ |command_string|
3
+
4
+ command_string.gsub! "\n", " "
5
+
6
+ @output = `bundle exec #{command_string}`
7
+
8
+ @status = $?.exitstatus
9
+
10
+ end
11
+
12
+ Then /^the exit status should be (\d+)$/ do
13
+ |status_str|
14
+
15
+ @status.should == status_str.to_i
16
+
17
+ end
18
+
19
+ Then /^the output should match:$/ do
20
+ |output_re_str|
21
+
22
+ output_re = /^#{output_re_str}$/
23
+
24
+ @output.strip.should match output_re
25
+
26
+ end
@@ -0,0 +1,481 @@
1
+ require "mongo"
2
+
3
+ require "hq/tools/check-script"
4
+ require "hq/tools/future"
5
+ require "hq/tools/getopt"
6
+ require "hq/tools/thread-pool"
7
+
8
+ module HQ
9
+ module MongoDB
10
+ module CheckCollectionSize
11
+
12
+ class Script < Tools::CheckScript
13
+
14
+ def initialize
15
+ super
16
+ @name = "MongoDB collection size"
17
+ end
18
+
19
+ def process_args
20
+
21
+ @opts, @args =
22
+ Tools::Getopt.process ARGV, [
23
+
24
+ { :name => :timeout,
25
+ :default => 10,
26
+ :regex => /[0-9]+(\.[0-9]+)?/,
27
+ :convert => :to_f },
28
+
29
+ { :name => :hostname,
30
+ :default => "localhost" },
31
+
32
+ { :name => :port,
33
+ :default => 27017,
34
+ :convert => :to_i },
35
+
36
+ { :name => :total_warning,
37
+ :convert => method(:decode_bytes),
38
+ :required => true },
39
+
40
+ { :name => :total_critical,
41
+ :convert => method(:decode_bytes),
42
+ :required => true },
43
+
44
+ { :name => :unsharded_warning,
45
+ :convert => method(:decode_bytes),
46
+ :required => true },
47
+
48
+ { :name => :unsharded_critical,
49
+ :convert => method(:decode_bytes),
50
+ :required => true },
51
+
52
+ { :name => :efficiency_warning,
53
+ :convert => :to_i,
54
+ :required => true },
55
+
56
+ { :name => :efficiency_critical,
57
+ :convert => :to_i,
58
+ :required => true },
59
+
60
+ { :name => :efficiency_size,
61
+ :convert => method(:decode_bytes),
62
+ :required => true },
63
+
64
+ { :name => :verbose,
65
+ :boolean => true },
66
+
67
+ { :name => :threads,
68
+ :convert => :to_i,
69
+ :default => 20 },
70
+
71
+ { :name => :breakdown,
72
+ :boolean => true }
73
+
74
+ ]
75
+
76
+ @args.empty? \
77
+ or raise "Extra args on command line"
78
+
79
+ end
80
+
81
+ def get_namespace_stats db_name, namespace_name
82
+
83
+ mongo =
84
+ Thread.current[:mongo]
85
+
86
+ db =
87
+ mongo.db(db_name)
88
+
89
+ stats =
90
+ db.command({
91
+ "collStats" => namespace_name
92
+ })
93
+
94
+ return {
95
+ :data_size => stats["size"],
96
+ :storage_size => stats["storageSize"],
97
+ }
98
+
99
+ end
100
+
101
+ def get_collection_stats db_name, coll_name
102
+
103
+ mongo =
104
+ Thread.current[:mongo]
105
+
106
+ db =
107
+ mongo.db(db_name)
108
+
109
+ # get collection stats
110
+
111
+ coll_stats =
112
+ get_namespace_stats \
113
+ db_name,
114
+ coll_name
115
+
116
+ # get index stats
117
+
118
+ prefix_size =
119
+ db_name.length + 1 + coll_name.length + 1
120
+
121
+ index_names =
122
+ db["system.indexes"] \
123
+ .find({
124
+ "ns" => "#{db_name}.#{coll_name}",
125
+ })
126
+ .map { |row| row["name"] }
127
+
128
+ index_stats = Hash[
129
+ index_names.map do
130
+ |index_name|
131
+ [
132
+ index_name,
133
+ get_namespace_stats(
134
+ db_name,
135
+ "#{coll_name}.$#{index_name}")
136
+ ]
137
+ end
138
+ ]
139
+
140
+ # work out overall figures
141
+
142
+ total_stats =
143
+ index_stats.values.reduce coll_stats do
144
+ |memo, object|
145
+ {
146
+ :data_size =>
147
+ memo[:data_size] + object[:data_size],
148
+ :storage_size =>
149
+ memo[:storage_size] + object[:storage_size],
150
+ }
151
+ end
152
+
153
+ # get sharding info
154
+
155
+ config_db =
156
+ mongo.db("config")
157
+
158
+ collection_row =
159
+ config_db["collections"]
160
+ .find_one({
161
+ "_id" => "#{db_name}.#{coll_name}"
162
+ })
163
+
164
+ sharding_enabled =
165
+ collection_row && collection_row["key"] ? true : false
166
+
167
+ # and return
168
+
169
+ return {
170
+ :total => total_stats,
171
+ :collection => coll_stats,
172
+ :indexes => index_stats,
173
+ :sharding_enabled => sharding_enabled,
174
+ }
175
+
176
+ end
177
+
178
+ def perform_checks
179
+
180
+ # connect
181
+
182
+ mongo =
183
+ Mongo::Connection.new \
184
+ @opts[:hostname],
185
+ @opts[:port]
186
+
187
+ # stats to collect
188
+
189
+ @biggest = 0
190
+
191
+ @warning_count = 0
192
+ @critical_count = 0
193
+ @ok_count = 0
194
+ @error_count = 0
195
+
196
+ # thread pool
197
+
198
+ @thread_pool =
199
+ Tools::ThreadPool.new
200
+
201
+ @thread_pool.init_hook do
202
+
203
+ Thread.current[:mongo] =
204
+ Mongo::Connection.new \
205
+ @opts[:hostname],
206
+ @opts[:port]
207
+
208
+ end
209
+
210
+ @thread_pool.start \
211
+ @opts[:threads]
212
+
213
+ # get collection names (in parallel)
214
+
215
+ collection_name_futures =
216
+ mongo.database_names.map do
217
+ |database_name|
218
+
219
+ Tools::Future.new @thread_pool, database_name do
220
+ |database_name|
221
+
222
+ mongo =
223
+ Thread.current[:mongo]
224
+
225
+ mongo.db(database_name)
226
+ .collection_names
227
+ .map {
228
+ |collection_name|
229
+ [ database_name, collection_name ]
230
+ }
231
+
232
+ end
233
+
234
+ end
235
+
236
+ collection_names =
237
+ collection_name_futures
238
+ .map {
239
+ |future|
240
+ future.get
241
+ }
242
+ .flatten(1)
243
+
244
+ # analyse collections (in parallel)
245
+
246
+ collection_stat_futures = Hash[
247
+ collection_names.map do
248
+ |database_name, collection_name|
249
+
250
+ future =
251
+ Tools::Future.new \
252
+ @thread_pool,
253
+ database_name,
254
+ collection_name do
255
+ |database_name, collection_name|
256
+ get_collection_stats(
257
+ database_name,
258
+ collection_name)
259
+ end
260
+
261
+ [
262
+ "#{database_name}.#{collection_name}",
263
+ future,
264
+ ]
265
+
266
+ end
267
+ ]
268
+
269
+ collection_stat_futures.each do
270
+ |full_collection_name, future|
271
+
272
+ stats = future.get
273
+
274
+ analyse_collection \
275
+ full_collection_name,
276
+ stats
277
+
278
+ end
279
+
280
+ # results
281
+
282
+ critical "#{@critical_count} critical" \
283
+ if @critical_count > 0
284
+
285
+ warning "#{@warning_count} warning" \
286
+ if @warning_count > 0
287
+
288
+ unknown "#{@error_count} errors" \
289
+ if @error_count > 0
290
+
291
+ message "biggest is #{byte_size @biggest}"
292
+
293
+ end
294
+
295
+ def analyse_collection full_collection_name, stats
296
+
297
+ # work out total efficiency
298
+
299
+ total_data_size =
300
+ stats[:total][:data_size]
301
+
302
+ total_storage_size =
303
+ stats[:total][:storage_size]
304
+
305
+ total_efficiency =
306
+ total_data_size * 100 / total_storage_size
307
+
308
+ sharding_enabled =
309
+ stats[:sharding_enabled]
310
+
311
+ # work out critical and warning status
312
+
313
+ critical = false
314
+ warning = false
315
+
316
+ if total_data_size >= @opts[:total_critical]
317
+ critical = true
318
+ elsif total_data_size >= @opts[:total_warning]
319
+ warning = true
320
+ end
321
+
322
+ if sharding_enabled == false
323
+ if total_data_size >= @opts[:unsharded_critical]
324
+ critical = true
325
+ elsif total_data_size >= @opts[:unsharded_warning]
326
+ warning = true
327
+ end
328
+ end
329
+
330
+ if total_data_size >= @opts[:efficiency_size]
331
+ if total_efficiency < @opts[:efficiency_critical]
332
+ total_critical = true
333
+ elsif total_efficiency < @opts[:efficiency_warning]
334
+ warning = true
335
+ end
336
+ end
337
+
338
+ # update counters
339
+
340
+ if critical
341
+ @critical_count += 1
342
+ elsif warning
343
+ @warning_count += 1
344
+ else
345
+ @ok_count += 1
346
+ end
347
+
348
+ @biggest = total_data_size \
349
+ if total_data_size > @biggest
350
+
351
+ # output message(s)
352
+
353
+ if @opts[:verbose] && (critical || warning)
354
+
355
+ sharding_string =
356
+ if sharding_enabled
357
+ "sharded"
358
+ else
359
+ "unsharded"
360
+ end
361
+
362
+ status_string =
363
+ if critical
364
+ "*** CRITICAL ***"
365
+ elsif warning
366
+ "warning"
367
+ else
368
+ "ok"
369
+ end
370
+
371
+ print_n \
372
+ "%s %s %d%% %s %s" % [
373
+ "#{full_collection_name}",
374
+ byte_size(total_storage_size),
375
+ total_efficiency,
376
+ sharding_string,
377
+ status_string,
378
+ ]
379
+
380
+ if @opts[:breakdown]
381
+
382
+ collection_data_size =
383
+ stats[:collection][:data_size]
384
+
385
+ collection_storage_size =
386
+ stats[:collection][:storage_size]
387
+
388
+ collection_efficiency =
389
+ collection_data_size * 100 / collection_storage_size
390
+
391
+ print_n \
392
+ " data %s %d%%" % [
393
+ byte_size(collection_storage_size),
394
+ collection_efficiency,
395
+ ]
396
+
397
+ stats[:indexes].each do
398
+ |index_name,
399
+ index_stats|
400
+
401
+ index_data_size =
402
+ index_stats[:data_size]
403
+
404
+ index_storage_size =
405
+ index_stats[:storage_size]
406
+
407
+ index_efficiency =
408
+ index_data_size * 100 / index_storage_size
409
+
410
+ print_n \
411
+ " %s %s %d%%" % [
412
+ index_name,
413
+ byte_size(index_storage_size),
414
+ index_efficiency,
415
+ ]
416
+
417
+ end
418
+
419
+ end
420
+
421
+ end
422
+
423
+ end
424
+
425
+ def print_n str
426
+ puts "#{str}\n"
427
+ end
428
+
429
+ def byte_size bytes
430
+
431
+ size = bytes.to_f
432
+
433
+ series = [
434
+ "bytes",
435
+ "kilobytes",
436
+ "megabytes",
437
+ "gigabytes",
438
+ "terabytes",
439
+ ]
440
+
441
+ while size >= 1024
442
+ size /= 1024
443
+ series.shift
444
+ end
445
+
446
+ unit = series.shift
447
+
448
+ return "%.3g %s" % [ size, unit ]
449
+
450
+ end
451
+
452
+ def decode_bytes string
453
+
454
+ case string
455
+
456
+ when /^([0-9]+)b?$/
457
+
458
+ when /^([0-9]+)k$/
459
+ $1.to_i * 1024
460
+
461
+ when /^([0-9]+)m$/
462
+ $1.to_i * 1024 * 1024
463
+
464
+ when /^([0-9]+)g$/
465
+ $1.to_i * 1024 * 1024 * 1024
466
+
467
+ when /^([0-9]+)t$/
468
+ $1.to_i * 1024 * 1024 * 1024 * 1024
469
+
470
+ else
471
+ raise "Invalid size: #{string}"
472
+
473
+ end
474
+
475
+ end
476
+
477
+ end
478
+
479
+ end
480
+ end
481
+ end
metadata ADDED
@@ -0,0 +1,183 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hq-mongodb-check-collection-size
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - James Pharaoh
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bson_ext
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.8.5
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.8.5
30
+ - !ruby/object:Gem::Dependency
31
+ name: hq-tools
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 0.8.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 0.8.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: mongo
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: 1.8.5
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.8.5
62
+ - !ruby/object:Gem::Dependency
63
+ name: cucumber
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: 1.3.1
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 1.3.1
78
+ - !ruby/object:Gem::Dependency
79
+ name: json
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: 1.7.7
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: 1.7.7
94
+ - !ruby/object:Gem::Dependency
95
+ name: rspec
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: 2.13.0
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: 2.13.0
110
+ - !ruby/object:Gem::Dependency
111
+ name: rspec_junit_formatter
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: 0.1.6
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: 0.1.6
126
+ - !ruby/object:Gem::Dependency
127
+ name: simplecov
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: 0.7.1
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: 0.7.1
142
+ description: HQ MongoDB script to check collection sizes, as a nagios/icinga plugin
143
+ or from the command line to generate reports
144
+ email:
145
+ - james@phsys.co.uk
146
+ executables:
147
+ - hq-mongodb-check-collection-size
148
+ extensions: []
149
+ extra_rdoc_files: []
150
+ files:
151
+ - lib/hq/mongodb/check-collection-size/script.rb
152
+ - features/runs.feature
153
+ - features/support/steps.rb
154
+ - features/support/env.rb
155
+ - bin/hq-mongodb-check-collection-size
156
+ homepage: https://github.com/jamespharaoh/hq-mongodb-check-collection-size
157
+ licenses: []
158
+ post_install_message:
159
+ rdoc_options: []
160
+ require_paths:
161
+ - lib
162
+ required_ruby_version: !ruby/object:Gem::Requirement
163
+ none: false
164
+ requirements:
165
+ - - ! '>='
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ required_rubygems_version: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: 1.3.6
174
+ requirements: []
175
+ rubyforge_project: hq-mongodb-check-collection-size
176
+ rubygems_version: 1.8.23
177
+ signing_key:
178
+ specification_version: 3
179
+ summary: HQ MongoDB check collection size
180
+ test_files:
181
+ - features/runs.feature
182
+ - features/support/steps.rb
183
+ - features/support/env.rb