moneypools-capistrano-ext 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +38 -0
- data/MIT-LICENSE +20 -0
- data/Manifest +10 -0
- data/README +3 -0
- data/Rakefile +25 -0
- data/lib/capistrano/ext/assets/request-counter.rb +29 -0
- data/lib/capistrano/ext/monitor.rb +320 -0
- data/lib/capistrano/ext/multistage.rb +61 -0
- data/lib/capistrano/ext/version.rb +11 -0
- data/moneypools-capistrano-ext.gemspec +33 -0
- data/setup.rb +1346 -0
- metadata +84 -0
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
== (unreleased)
|
2
|
+
|
3
|
+
* Added explicit dependency on 'fileutils' [github.com/rouffj]
|
4
|
+
* Fixed load and requests_per_second to work with cap2 [Gufo Pelato]
|
5
|
+
* Allow tasks in staging files to be invoked without explicitly specifying the stage [Michael Hale]
|
6
|
+
|
7
|
+
|
8
|
+
== 1.2.1 (June 14, 2008)
|
9
|
+
|
10
|
+
* Exclude multistage:prepare from the multistage:ensure check [Matt Sanders]
|
11
|
+
* Don't clobber pre-existing stage definitions on
|
12
|
+
multistage:prepare [Matt Sanders]
|
13
|
+
* Made multistage module autodetect stages. [Jamis Buck]
|
14
|
+
|
15
|
+
|
16
|
+
== 1.2.0 (July 6, 2007)
|
17
|
+
|
18
|
+
* Added multistage as a packaged staging option. [Jamis Buck]
|
19
|
+
|
20
|
+
|
21
|
+
== 1.1.1 (May 12, 2007)
|
22
|
+
|
23
|
+
* Tweak to work with Capistrano 2 [Jamis Buck]
|
24
|
+
|
25
|
+
|
26
|
+
== 1.1.0 (September 14, 2006)
|
27
|
+
|
28
|
+
* Add uptime task [Jamis Buck]
|
29
|
+
* Fix padding on long machine names [Michael Schoen]
|
30
|
+
* Fix matching of machine names [Michael Schoen]
|
31
|
+
* Fix matching of load averages [Michael Schoen]
|
32
|
+
|
33
|
+
|
34
|
+
== 1.0.1 (March 6, 2006)
|
35
|
+
|
36
|
+
* Rename capistrano-ext
|
37
|
+
* Put request monitor script in user's home directory, instead of /tmp,
|
38
|
+
to accomodate multiple users
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2006 Jamis Buck
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest
ADDED
data/README
ADDED
@@ -0,0 +1,3 @@
|
|
1
|
+
= Capistrano Extensions
|
2
|
+
|
3
|
+
Capistrano Extensions is a collection of tasks and methods that developers may use in their own Capistrano recipe files. Although some may find the tasks and such useful in their own right, the collection is intended primarily as an example of how to write and package Capistrano extension libraries.
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
begin
|
2
|
+
require 'echoe'
|
3
|
+
rescue LoadError
|
4
|
+
abort "You'll need to have `echoe' installed to use capistrano-ext's Rakefile"
|
5
|
+
end
|
6
|
+
|
7
|
+
require "./lib/capistrano/ext/version"
|
8
|
+
|
9
|
+
version = Capistrano::Ext::Version::STRING.dup
|
10
|
+
if ENV['SNAPSHOT'].to_i == 1
|
11
|
+
version << "." << Time.now.utc.strftime("%Y%m%d%H%M%S")
|
12
|
+
end
|
13
|
+
|
14
|
+
Echoe.new('moneypools-capistrano-ext', version) do |p|
|
15
|
+
p.changelog = "CHANGELOG.rdoc"
|
16
|
+
|
17
|
+
p.author = "Jamis Buck"
|
18
|
+
p.email = "jamis@jamisbuck.org"
|
19
|
+
p.summary = "Useful task libraries and methods for Capistrano"
|
20
|
+
p.url = "http://www.capify.org"
|
21
|
+
|
22
|
+
p.need_zip = true
|
23
|
+
|
24
|
+
p.dependencies = ["capistrano >=1.0.0"]
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
def tail_lines(io)
|
4
|
+
io.each_line { |line| yield line }
|
5
|
+
if io.eof? then
|
6
|
+
sleep 0.25
|
7
|
+
io.pos = io.pos # reset eof?
|
8
|
+
retry
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
count = 0
|
13
|
+
mutex = Mutex.new
|
14
|
+
|
15
|
+
Thread.start do
|
16
|
+
loop do
|
17
|
+
sleep 1
|
18
|
+
mutex.synchronize do
|
19
|
+
puts count
|
20
|
+
count = 0
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
pattern = Regexp.new(ARGV.first)
|
26
|
+
tail_lines(STDIN) do |line|
|
27
|
+
next unless line =~ pattern
|
28
|
+
mutex.synchronize { count += 1 }
|
29
|
+
end
|
@@ -0,0 +1,320 @@
|
|
1
|
+
require 'capistrano'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module MonitorServers
|
5
|
+
LONG_TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
|
6
|
+
SHORT_TIME_FORMAT = "%H:%M:%S"
|
7
|
+
|
8
|
+
# A helper method for encapsulating the behavior of the date/time column
|
9
|
+
# in a report.
|
10
|
+
def date_column(operation, *args)
|
11
|
+
case operation
|
12
|
+
when :init
|
13
|
+
{ :width => Time.now.strftime(LONG_TIME_FORMAT).length,
|
14
|
+
:last => nil,
|
15
|
+
:rows => 0 }
|
16
|
+
when :show
|
17
|
+
state = args.first
|
18
|
+
now = Time.now
|
19
|
+
date = now.strftime(
|
20
|
+
(state[:rows] % 10 == 0 || now.day != state[:last].day) ?
|
21
|
+
LONG_TIME_FORMAT : SHORT_TIME_FORMAT)
|
22
|
+
state[:last] = now
|
23
|
+
state[:rows] += 1
|
24
|
+
"%*s" % [state[:width], date]
|
25
|
+
else
|
26
|
+
raise "unknown operation #{operation.inspect}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# A helper method for formatting table headers in a report.
|
31
|
+
def headers(*args)
|
32
|
+
0.step(args.length-1, 2) do |n|
|
33
|
+
header = args[n]
|
34
|
+
size = args[n+1]
|
35
|
+
if header == "-" || header == " "
|
36
|
+
print header * size, " "
|
37
|
+
else
|
38
|
+
print header
|
39
|
+
padding = size - header.length
|
40
|
+
print " " if padding > 0
|
41
|
+
print "-" * (padding - 1) if padding > 1
|
42
|
+
print " "
|
43
|
+
end
|
44
|
+
end
|
45
|
+
puts
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get a value from the remote environment
|
49
|
+
def remote_env(value)
|
50
|
+
result = ""
|
51
|
+
run("echo $#{value}", :once => true) do |ch, stream, data|
|
52
|
+
raise "could not get environment variable #{value}: #{data}" if stream == :err
|
53
|
+
result << data
|
54
|
+
end
|
55
|
+
result.chomp
|
56
|
+
end
|
57
|
+
|
58
|
+
# Monitor the load of the servers tied to the current task.
|
59
|
+
def load(options={})
|
60
|
+
servers = find_servers_for_task(current_task).sort
|
61
|
+
names = servers.map { |s| s.host.match(/^([^.]+)/)[1] }
|
62
|
+
time = date_column(:init)
|
63
|
+
load_column_width = "0.00".length * 3 + 2
|
64
|
+
|
65
|
+
puts "connecting..."
|
66
|
+
connect!
|
67
|
+
|
68
|
+
parser = Proc.new { |text| text.match(/average.*: (.*)$/)[1].split(/, /) }
|
69
|
+
delay = (options[:delay] || 30).to_i
|
70
|
+
|
71
|
+
running = true
|
72
|
+
trap("INT") { running = false; puts "[stopping]" }
|
73
|
+
|
74
|
+
# THE HEADER
|
75
|
+
header = Proc.new do
|
76
|
+
puts
|
77
|
+
headers("-", time[:width], *names.map { |n| [n, load_column_width] }.flatten)
|
78
|
+
end
|
79
|
+
|
80
|
+
while running
|
81
|
+
uptimes = {}
|
82
|
+
run "uptime" do |ch, stream, data|
|
83
|
+
raise "error: #{data}" if stream == :err
|
84
|
+
uptimes[ch[:host]] = parser[data.strip]
|
85
|
+
end
|
86
|
+
|
87
|
+
# redisplay the header every 40 rows
|
88
|
+
header.call if time[:rows] % 40 == 0
|
89
|
+
|
90
|
+
print(date_column(:show, time), " ")
|
91
|
+
servers.each { |server| print(uptimes[server.host].join("/"), " ") }
|
92
|
+
puts
|
93
|
+
|
94
|
+
# sleep this way, so that CTRL-C works immediately
|
95
|
+
delay.times { sleep 1; break unless running }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Monitor the number of requests per second being logged on the various
|
100
|
+
# servers.
|
101
|
+
def requests_per_second(*logs)
|
102
|
+
# extract our configurable options from the arguments
|
103
|
+
options = logs.last.is_a?(Hash) ? logs.pop : {}
|
104
|
+
request_pattern = options[:request_pattern] || "Completed in [0-9]"
|
105
|
+
sample_size = options[:sample_size] || 5
|
106
|
+
stats_to_show = options[:stats] || [0, 1, 5, 15]
|
107
|
+
num_format = options[:format] || "%4.1f"
|
108
|
+
|
109
|
+
# set up the date column formatter, and get the list of servers
|
110
|
+
time = date_column(:init)
|
111
|
+
servers = find_servers_for_task(current_task).sort
|
112
|
+
|
113
|
+
# initialize various helper variables we'll be using
|
114
|
+
mutex = Mutex.new
|
115
|
+
count = Hash.new(0)
|
116
|
+
running = false
|
117
|
+
channels = {}
|
118
|
+
|
119
|
+
windows = Hash.new { |h,k|
|
120
|
+
h[k] = {
|
121
|
+
1 => [], # last 1 minute
|
122
|
+
5 => [], # last 5 minutes
|
123
|
+
15 => [] # last 15 minutes
|
124
|
+
}
|
125
|
+
}
|
126
|
+
|
127
|
+
minute_1 = 60 / sample_size
|
128
|
+
minute_5 = 300 / sample_size
|
129
|
+
minute_15 = 900 / sample_size
|
130
|
+
|
131
|
+
# store our helper script on the servers. This script reduces the amount
|
132
|
+
# of traffic caused by tailing busy logs across the network, and also reduces
|
133
|
+
# the amount of work the client has to do.
|
134
|
+
script = "#{remote_env("HOME")}/x-request-counter.rb"
|
135
|
+
put_asset "request-counter.rb", script
|
136
|
+
|
137
|
+
# set up (but don't start) the runner thread, which accumulates request
|
138
|
+
# counts from the servers.
|
139
|
+
runner = Thread.new do Thread.stop
|
140
|
+
running = true
|
141
|
+
run("echo 0 && tail -F #{logs.join(" ")} | ruby #{script} '#{request_pattern}'") do |ch, stream, out|
|
142
|
+
channels[ch[:host]] ||= ch
|
143
|
+
puts "#{ch[:host]}: #{out}" and break if stream == :err
|
144
|
+
mutex.synchronize { count[ch[:host]] += out.to_i }
|
145
|
+
end
|
146
|
+
running = false
|
147
|
+
end
|
148
|
+
|
149
|
+
# let the runner thread get started
|
150
|
+
runner.wakeup
|
151
|
+
sleep 0.01 while !running
|
152
|
+
|
153
|
+
# trap interrupt for graceful shutdown
|
154
|
+
trap("INT") { puts "[stopping]"; channels.values.each { |ch| ch.close; ch[:status] = 0 } }
|
155
|
+
|
156
|
+
# compute the stuff we need to know for displaying the header
|
157
|
+
num_len = (num_format % 1).length
|
158
|
+
column_width = num_len * (servers.length + 1) + servers.length
|
159
|
+
abbvs = servers.map { |server| server.host.match(/^(\w+)/)[1][0,num_len] }
|
160
|
+
col_header = abbvs.map { |v| "%-*s" % [num_len, v] }.join("/")
|
161
|
+
|
162
|
+
# write both rows of the header
|
163
|
+
stat_columns = stats_to_show.map { |n|
|
164
|
+
case n
|
165
|
+
when 0 then "#{sample_size} sec"
|
166
|
+
when 1 then "1 min"
|
167
|
+
when 5 then "5 min"
|
168
|
+
when 15 then "15 min"
|
169
|
+
else raise "unknown statistic #{n.inspect}"
|
170
|
+
end
|
171
|
+
}
|
172
|
+
|
173
|
+
header = Proc.new do
|
174
|
+
puts
|
175
|
+
headers(" ", time[:width], *stat_columns.map { |v| [v, column_width] }.flatten)
|
176
|
+
headers("-", time[:width], *([col_header, column_width] * stats_to_show.length))
|
177
|
+
end
|
178
|
+
|
179
|
+
while running
|
180
|
+
# sleep for the specified sample size (5s by default)
|
181
|
+
(sample_size * 2).times { sleep(0.5); break unless running }
|
182
|
+
break unless running
|
183
|
+
|
184
|
+
# lock the counters and compute our stats at this point in time
|
185
|
+
mutex.synchronize do
|
186
|
+
totals = Hash.new { |h,k| h[k] = Hash.new(0) }
|
187
|
+
|
188
|
+
# for each server...
|
189
|
+
count.each do |k,c|
|
190
|
+
# push the latest sample onto the tracking queues
|
191
|
+
windows[k][1] = windows[k][1].push(count[k]).last(minute_1)
|
192
|
+
windows[k][5] = windows[k][5].push(count[k]).last(minute_5)
|
193
|
+
windows[k][15] = windows[k][15].push(count[k]).last(minute_15)
|
194
|
+
|
195
|
+
# compute the stats for this server (k)
|
196
|
+
totals[k][0] = count[k].to_f / sample_size
|
197
|
+
totals[k][1] = windows[k][1].inject(0) { |n,i| n + i } / (windows[k][1].length * sample_size).to_f
|
198
|
+
totals[k][5] = windows[k][5].inject(0) { |n,i| n + i } / (windows[k][5].length * sample_size).to_f
|
199
|
+
totals[k][15] = windows[k][15].inject(0) { |n,i| n + i } / (windows[k][15].length * sample_size).to_f
|
200
|
+
|
201
|
+
# add those stats to the totals per category
|
202
|
+
totals[:total][0] += totals[k][0]
|
203
|
+
totals[:total][1] += totals[k][1]
|
204
|
+
totals[:total][5] += totals[k][5]
|
205
|
+
totals[:total][15] += totals[k][15]
|
206
|
+
end
|
207
|
+
|
208
|
+
# redisplay the header every 40 rows
|
209
|
+
header.call if time[:rows] % 40 == 0
|
210
|
+
|
211
|
+
# show the stats
|
212
|
+
print(date_column(:show, time))
|
213
|
+
stats_to_show.each do |stat|
|
214
|
+
print " "
|
215
|
+
servers.each { |server| print "#{num_format}/" % totals[server][stat] }
|
216
|
+
print(num_format % totals[:total][stat])
|
217
|
+
end
|
218
|
+
puts
|
219
|
+
|
220
|
+
# reset the sample counter
|
221
|
+
count = Hash.new(0)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def put_asset(name, to)
|
227
|
+
put(File.read("#{File.dirname(__FILE__)}/assets/#{name}"), to)
|
228
|
+
end
|
229
|
+
|
230
|
+
def uptime
|
231
|
+
results = {}
|
232
|
+
|
233
|
+
puts "querying servers..."
|
234
|
+
run "uptime" do |ch, stream, out|
|
235
|
+
if stream == :err
|
236
|
+
results[ch[:host]] = { :error => "error: #{out.strip}" }
|
237
|
+
else
|
238
|
+
if out.strip =~ /(\S+)\s+up\s+(.*?),\s+(\d+) users?,\s+load averages?: (.*)/
|
239
|
+
time = $1
|
240
|
+
uptime = $2
|
241
|
+
users = $3
|
242
|
+
loads = $4
|
243
|
+
|
244
|
+
results[ch[:host]] = { :uptime => uptime.strip.gsub(/ +/, " "),
|
245
|
+
:loads => loads,
|
246
|
+
:users => users,
|
247
|
+
:time => time }
|
248
|
+
else
|
249
|
+
results[ch[:host]] = { :error => "unknown uptime format: #{out.strip}" }
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
longest_hostname = results.keys.map { |k| k.length }.max
|
255
|
+
longest_uptime = results.values.map { |v| (v[:uptime] || "").length }.max
|
256
|
+
|
257
|
+
by_role = {}
|
258
|
+
roles.each do |name, list|
|
259
|
+
by_role[name] = {}
|
260
|
+
list.each do |role|
|
261
|
+
next unless results[role.host]
|
262
|
+
by_role[name][role.host] = results.delete(role.host)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
by_role[:zzz] = results unless results.empty?
|
267
|
+
|
268
|
+
add_newline = false
|
269
|
+
by_role.keys.sort_by { |k| k.to_s }.each do |role|
|
270
|
+
results = by_role[role]
|
271
|
+
next if results.empty?
|
272
|
+
|
273
|
+
puts "\n" if add_newline
|
274
|
+
add_newline = true
|
275
|
+
|
276
|
+
results.keys.sort.each do |server|
|
277
|
+
print "[%-*s] " % [longest_hostname, server]
|
278
|
+
if results[server][:error]
|
279
|
+
puts results[server][:error]
|
280
|
+
else
|
281
|
+
puts "up %*s, load %s" % [longest_uptime, results[server][:uptime], results[server][:loads]]
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
Capistrano.plugin :monitor, MonitorServers
|
289
|
+
|
290
|
+
configuration = Capistrano::Configuration.respond_to?(:instance) ?
|
291
|
+
Capistrano::Configuration.instance(:must_exist) :
|
292
|
+
Capistrano.configuration(:must_exist)
|
293
|
+
|
294
|
+
configuration.load do
|
295
|
+
desc <<-STR
|
296
|
+
Watch the load on the servers. Display is updated every 30 seconds by default,
|
297
|
+
though you can specify a DELAY environment variable to make it update more or
|
298
|
+
less frequently.
|
299
|
+
STR
|
300
|
+
task :watch_load do
|
301
|
+
monitor.load :delay => ENV["DELAY"]
|
302
|
+
end
|
303
|
+
|
304
|
+
desc <<-STR
|
305
|
+
Watch the number of requests/sec being logged on the application servers. By
|
306
|
+
default, the "production.log" is watched, but if your log is named something
|
307
|
+
else, you can specify it in the log_name variable.
|
308
|
+
STR
|
309
|
+
task :watch_requests, :roles => :app do
|
310
|
+
monitor.requests_per_second("#{shared_path}/log/#{self[:log_name] || "production.log"}")
|
311
|
+
end
|
312
|
+
|
313
|
+
desc <<-STR
|
314
|
+
Display the current uptime and load for all servers, nicely formatted with
|
315
|
+
columns all lined up for easy scanning.
|
316
|
+
STR
|
317
|
+
task :uptime do
|
318
|
+
monitor.uptime
|
319
|
+
end
|
320
|
+
end
|