hq-mongodb-check-collection-size 0.0.0

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,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