kcluster 1.1.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.
Files changed (3) hide show
  1. data/bin/kcluster +255 -0
  2. data/lib/nothing.rb +0 -0
  3. metadata +83 -0
data/bin/kcluster ADDED
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.push(File.dirname($0))
4
+ require 'optparse'
5
+ require 'socket'
6
+ require 'net/http'
7
+ require 'json'
8
+
9
+ # FIXME this really only works inside twitter. it would be nice to support
10
+ # a general serverset interface (or opensource colony).
11
+ $colony_host = ENV["COLONY_HOST"] || "colony.smf1.twitter.com"
12
+ $colony_port = ENV["COLONY_PORT"] || 9080
13
+
14
+ $options = {
15
+ :config_filename => ENV['HOME'] + "/.kestrel_cluster",
16
+ :server_list => [],
17
+ :port => 22133,
18
+ :verbose => false,
19
+ }
20
+
21
+ def verbose(s)
22
+ puts s if $options[:verbose]
23
+ end
24
+
25
+ def fetch_stats(host, port, data)
26
+ verbose "--- Fetching stats from #{host}:#{port}"
27
+ sock = TCPSocket.open(host, port)
28
+ sock.puts("stats")
29
+ done = false
30
+ while !done && line = sock.gets.chomp
31
+ if (line == 'END') then
32
+ done = true
33
+ elsif line =~ /STAT queue_([\w\+\-]+) (\d+)/
34
+ key = $1
35
+ value = $2.to_i
36
+ (stat, queue_name) = case key
37
+ when /([\w\+\-]+)_total_items/ then [:total_items, $1]
38
+ when /([\w\+\-]+)_expired_items/ then [:expired_items, $1]
39
+ when /([\w\+\-]+)_mem_items/ then [:mem_items, $1]
40
+ when /([\w\+\-]+)_items/ then [:items, $1]
41
+ when /([\w\+\-]+)_mem_bytes/ then [:mem_bytes, $1]
42
+ when /([\w\+\-]+)_bytes/ then [:bytes, $1]
43
+ when /([\w\+\-]+)_age/ then [:age, $1]
44
+ end
45
+
46
+ if (queue_name)
47
+ queue_name = queue_name.split('+', 2).first if $options[:rollup_fanouts]
48
+
49
+ if (stat == :age)
50
+ data[:min_age][queue_name] = value if value < data[:min_age][queue_name]
51
+ data[:max_age][queue_name] = value if value > data[:max_age][queue_name]
52
+ else
53
+ data[stat][queue_name] += value
54
+ end
55
+ end
56
+ end
57
+ end
58
+ sock.close
59
+ end
60
+
61
+ def fetch_all
62
+ data = Hash.new do |h, data_type|
63
+ h[data_type] = Hash.new do |h, k|
64
+ h[k] = (data_type == :min_age) ? 2**31 : 0
65
+ end
66
+ end
67
+ $options[:server_list].each do |server|
68
+ begin
69
+ fetch_stats(server, $options[:port], data)
70
+ rescue => e
71
+ puts "Could not connect to host #{server}: #{e}"
72
+ end
73
+ end
74
+ data
75
+ end
76
+
77
+ def sort_data(data)
78
+ rv = {}
79
+ data.each { |key, v| rv[key] = v.sort_by { |h| [ h[1], h[0] ] } }
80
+ rv
81
+ end
82
+
83
+ def report(data, key)
84
+ puts ""
85
+ format = "%14s %s\n"
86
+ printf(format, key, "queue")
87
+ printf(format, "============", "====================")
88
+ stats = data[key] || {}
89
+ stats.each { |queue, value| printf("%14d %s\n", value, queue) }
90
+ end
91
+
92
+ def report_all(data, keys)
93
+ keys.each { |key| report(data, key) }
94
+ puts ""
95
+ end
96
+
97
+ def delete_all(queue_name)
98
+ $options[:server_list].each do |server|
99
+ print "--- Deleting queue #{queue_name} from #{server}:#{$options[:port]} ... "
100
+ STDOUT.flush
101
+ sock = TCPSocket.open(server, $options[:port])
102
+ sock.puts("delete " + queue_name)
103
+ puts sock.readline.chomp
104
+ sock.close
105
+ end
106
+ puts "Done."
107
+ end
108
+
109
+ def flush_all(queue_name)
110
+ $options[:server_list].each do |server|
111
+ print "--- Flushing queue #{queue_name} from #{server}:#{$options[:port]} ... "
112
+ STDOUT.flush
113
+ sock = TCPSocket.open(server, $options[:port])
114
+ sock.puts("flush " + queue_name)
115
+ puts sock.readline.chomp
116
+ sock.close
117
+ end
118
+ puts "Done."
119
+ end
120
+
121
+ def keep_unchanged_data(last, current)
122
+ rv = {}
123
+ current.keys.each { |key| rv[key] = keep_unchanged_hash(last[key], current[key]) }
124
+ rv
125
+ end
126
+
127
+ def keep_unchanged_hash(last, current)
128
+ rv = {}
129
+ last.each do |key, value|
130
+ rv[key] = value if current[key] == value
131
+ end
132
+ rv
133
+ end
134
+
135
+ # dump queues that appear to have neither :items nor :total_items changing.
136
+ def find_stale(rounds)
137
+ puts "Check 1..."
138
+ last = fetch_all
139
+ (1...rounds).each do |i|
140
+ sleep 5
141
+ puts "Check #{i + 1}..."
142
+ current = fetch_all
143
+ last = keep_unchanged_data(last, current)
144
+ end
145
+
146
+ stale = (last[:total_items].keys & last[:items].keys).sort!
147
+ sorted = sort_data(last)
148
+
149
+ puts ""
150
+ puts "stale queue prediction"
151
+ puts "======================"
152
+ printf("%11s %11s %s\n", "total_items", "items", "queue")
153
+ printf("%11s %11s %s\n", "-----------", "-----------", "--------------------")
154
+ stale.each do |queue_name|
155
+ items = last[:items][queue_name]
156
+ total_items = last[:total_items][queue_name]
157
+ printf("%11d %11d %s\n", total_items, items, queue_name)
158
+ end
159
+ end
160
+
161
+
162
+ # ----------
163
+
164
+ parser = OptionParser.new do |opts|
165
+ opts.banner = "Usage: #{$0} [options] <command>"
166
+ opts.separator "Example: #{$0} -f kestrel max_age"
167
+
168
+ opts.on("-f", "--file=FILENAME", "load kestrel server list from file (use '-' for stdin) (default: #{$options[:config_filename]})") do |filename|
169
+ $options[:config_filename] = filename
170
+ end
171
+ opts.on("-p", "--port=N", "use port (default: #{$options[:port]})") do |port|
172
+ $options[:port] = port.to_i
173
+ end
174
+ opts.on("-r", "--role=ROLE", "load kestrel server list from colony audubon role (requires colony)") do |role|
175
+ $options[:role] = role
176
+ end
177
+ opts.on("-v", "--verbose", "print verbose debugging info as we work") do
178
+ $options[:verbose] = true
179
+ end
180
+ opts.on("-F", "--rollup-fanouts", "roll up stats for fanout queues into a single count") do
181
+ $options[:rollup_fanouts] = true
182
+ end
183
+
184
+ opts.separator ""
185
+ opts.separator "Commands:"
186
+ opts.separator " all show all of the stats summaries below (items, etc)"
187
+ opts.separator " items show item counts per queue"
188
+ opts.separator " bytes show byte counts per queue"
189
+ opts.separator " mem show in-memory byte counts per queue"
190
+ opts.separator " min_age show minimum age (msec) per queue"
191
+ opts.separator " max_age show maximum age (msec) per queue"
192
+ opts.separator ""
193
+ opts.separator " delete <name> remove a queue across the cluster"
194
+ opts.separator " flush <name> drain a queue across the cluster"
195
+ opts.separator " stale [rounds] try to detect stale queues (default: 5 rounds)"
196
+ opts.separator ""
197
+ end
198
+
199
+ parser.parse!(ARGV)
200
+
201
+ if ARGV.size < 1
202
+ puts
203
+ puts parser
204
+ exit 1
205
+ end
206
+
207
+ if $options[:role]
208
+ data = Net::HTTP.get URI.parse("http://#{$colony_host}:#{$colony_port}/query?q=mo+audubon.role.#{$options[:role]}")
209
+ $options[:server_list] = JSON.parse(data).values.flatten
210
+ else
211
+ server_file = $options[:config_filename] == "-" ? STDIN : File.open($options[:config_filename], "r")
212
+ $options[:server_list] = server_file.readlines.map { |line| line.chomp }
213
+ server_file.close
214
+ end
215
+
216
+ command = ARGV[0].downcase
217
+
218
+ if command == "all"
219
+ $show = [ :items, :bytes, :mem_bytes, :min_age, :max_age ]
220
+ elsif command == "items"
221
+ $show = [ :items ]
222
+ elsif command == "bytes"
223
+ $show = [ :bytes ]
224
+ elsif command == "mem"
225
+ $show = [ :mem_bytes ]
226
+ elsif command == "min_age"
227
+ $show = [ :min_age ]
228
+ elsif command == "max_age"
229
+ $show = [ :max_age ]
230
+ elsif command == "delete" && ARGV.size == 2
231
+ queue_name = ARGV[1]
232
+ print "Really delete #{queue_name}? (yes/no) "
233
+ STDOUT.flush
234
+ delete_all(queue_name) if STDIN.gets.chomp == 'yes'
235
+ exit 0
236
+ elsif command == "flush" && ARGV.size == 2
237
+ queue_name = ARGV[1]
238
+ print "Really flush #{queue_name}? (yes/no) "
239
+ STDOUT.flush
240
+ flush_all(queue_name) if STDIN.gets.chomp == 'yes'
241
+ exit 0
242
+ elsif command == "stale"
243
+ rounds = ARGV[1] ? ARGV[1].to_i : 5
244
+ find_stale(rounds)
245
+ exit 0
246
+ end
247
+
248
+ if $show
249
+ report_all(sort_data(fetch_all), $show)
250
+ exit 0
251
+ else
252
+ puts
253
+ puts parser
254
+ exit 1
255
+ end
data/lib/nothing.rb ADDED
File without changes
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kcluster
3
+ version: !ruby/object:Gem::Version
4
+ hash: 19
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 1
9
+ - 0
10
+ version: 1.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Robey Pointer
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-03-02 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: json
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ description: |
35
+ A handy little CLI tool for browsing quick stats across your kestrel cluster.
36
+ Kcluster collects stats from each machine in a cluster, then summarizes them
37
+ into one table.
38
+
39
+ email: robeypointer@gmail.com
40
+ executables:
41
+ - kcluster
42
+ extensions: []
43
+
44
+ extra_rdoc_files: []
45
+
46
+ files:
47
+ - lib/nothing.rb
48
+ - bin/kcluster
49
+ homepage: http://robey.github.com/kestrel
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options: []
54
+
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 3
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ requirements: []
76
+
77
+ rubyforge_project:
78
+ rubygems_version: 1.8.15
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: Kestrel cluster management tool
82
+ test_files: []
83
+