capistrano-ext 1.0.1

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,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,250 @@
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 - 1
40
+ print " ", "-" * padding if padding > 0
41
+ print " "
42
+ end
43
+ end
44
+ puts
45
+ end
46
+
47
+ # Get a value from the remote environment
48
+ def remote_env(value)
49
+ result = ""
50
+ run("echo $#{value}", :once => true) do |ch, stream, data|
51
+ raise "could not get environment variable #{value}: #{data}" if stream == :err
52
+ result << data
53
+ end
54
+ result.chomp
55
+ end
56
+
57
+ # Monitor the load of the servers tied to the current task.
58
+ def load(options={})
59
+ servers = current_task.servers.sort
60
+ names = servers.map { |s| s.match(/^(\w+)/)[1] }
61
+ time = date_column(:init)
62
+ load_column_width = "0.00".length * 3 + 2
63
+
64
+ puts "connecting..."
65
+ connect!
66
+
67
+ parser = Proc.new { |text| text.match(/averages: (.*)$/)[1].split(/, /) }
68
+ delay = (options[:delay] || 30).to_i
69
+
70
+ running = true
71
+ trap("INT") { running = false; puts "[stopping]" }
72
+
73
+ # THE HEADER
74
+ header = Proc.new do
75
+ puts
76
+ headers("-", time[:width], *names.map { |n| [n, load_column_width] }.flatten)
77
+ end
78
+
79
+ while running
80
+ uptimes = {}
81
+ run "uptime" do |ch, stream, data|
82
+ raise "error: #{data}" if stream == :err
83
+ uptimes[ch[:host]] = parser[data.strip]
84
+ end
85
+
86
+ # redisplay the header every 40 rows
87
+ header.call if time[:rows] % 40 == 0
88
+
89
+ print(date_column(:show, time), " ")
90
+ servers.each { |server| print(uptimes[server].join("/"), " ") }
91
+ puts
92
+
93
+ # sleep this way, so that CTRL-C works immediately
94
+ delay.times { sleep 1; break unless running }
95
+ end
96
+ end
97
+
98
+ # Monitor the number of requests per second being logged on the various
99
+ # servers.
100
+ def requests_per_second(*logs)
101
+ # extract our configurable options from the arguments
102
+ options = logs.last.is_a?(Hash) ? logs.pop : {}
103
+ request_pattern = options[:request_pattern] || "Completed in [0-9]"
104
+ sample_size = options[:sample_size] || 5
105
+ stats_to_show = options[:stats] || [0, 1, 5, 15]
106
+ num_format = options[:format] || "%4.1f"
107
+
108
+ # set up the date column formatter, and get the list of servers
109
+ time = date_column(:init)
110
+ servers = current_task.servers.sort
111
+
112
+ # initialize various helper variables we'll be using
113
+ mutex = Mutex.new
114
+ count = Hash.new(0)
115
+ running = false
116
+ channels = {}
117
+
118
+ windows = Hash.new { |h,k|
119
+ h[k] = {
120
+ 1 => [], # last 1 minute
121
+ 5 => [], # last 5 minutes
122
+ 15 => [] # last 15 minutes
123
+ }
124
+ }
125
+
126
+ minute_1 = 60 / sample_size
127
+ minute_5 = 300 / sample_size
128
+ minute_15 = 900 / sample_size
129
+
130
+ # store our helper script on the servers. This script reduces the amount
131
+ # of traffic caused by tailing busy logs across the network, and also reduces
132
+ # the amount of work the client has to do.
133
+ script = "#{remote_env("HOME")}/x-request-counter.rb"
134
+ put_asset "request-counter.rb", script
135
+
136
+ # set up (but don't start) the runner thread, which accumulates request
137
+ # counts from the servers.
138
+ runner = Thread.new do Thread.stop
139
+ running = true
140
+ run("echo 0 && tail -F #{logs.join(" ")} | ruby #{script} '#{request_pattern}'") do |ch, stream, out|
141
+ channels[ch[:host]] ||= ch
142
+ puts "#{ch[:host]}: #{out}" and break if stream == :err
143
+ mutex.synchronize { count[ch[:host]] += out.to_i }
144
+ end
145
+ running = false
146
+ end
147
+
148
+ # let the runner thread get started
149
+ runner.wakeup
150
+ sleep 0.01 while !running
151
+
152
+ # trap interrupt for graceful shutdown
153
+ trap("INT") { puts "[stopping]"; channels.values.each { |ch| ch.close; ch[:status] = 0 } }
154
+
155
+ # compute the stuff we need to know for displaying the header
156
+ num_len = (num_format % 1).length
157
+ column_width = num_len * (servers.length + 1) + servers.length
158
+ abbvs = servers.map { |server| server.match(/^(\w+)/)[1][0,num_len] }
159
+ col_header = abbvs.map { |v| "%-*s" % [num_len, v] }.join("/")
160
+
161
+ # write both rows of the header
162
+ stat_columns = stats_to_show.map { |n|
163
+ case n
164
+ when 0 then "#{sample_size} sec"
165
+ when 1 then "1 min"
166
+ when 5 then "5 min"
167
+ when 15 then "15 min"
168
+ else raise "unknown statistic #{n.inspect}"
169
+ end
170
+ }
171
+
172
+ header = Proc.new do
173
+ puts
174
+ headers(" ", time[:width], *stat_columns.map { |v| [v, column_width] }.flatten)
175
+ headers("-", time[:width], *([col_header, column_width] * stats_to_show.length))
176
+ end
177
+
178
+ while running
179
+ # sleep for the specified sample size (5s by default)
180
+ (sample_size * 2).times { sleep(0.5); break unless running }
181
+ break unless running
182
+
183
+ # lock the counters and compute our stats at this point in time
184
+ mutex.synchronize do
185
+ totals = Hash.new { |h,k| h[k] = Hash.new(0) }
186
+
187
+ # for each server...
188
+ count.each do |k,c|
189
+ # push the latest sample onto the tracking queues
190
+ windows[k][1] = windows[k][1].push(count[k]).last(minute_1)
191
+ windows[k][5] = windows[k][5].push(count[k]).last(minute_5)
192
+ windows[k][15] = windows[k][15].push(count[k]).last(minute_15)
193
+
194
+ # compute the stats for this server (k)
195
+ totals[k][0] = count[k].to_f / sample_size
196
+ totals[k][1] = windows[k][1].inject(0) { |n,i| n + i } / (windows[k][1].length * sample_size).to_f
197
+ totals[k][5] = windows[k][5].inject(0) { |n,i| n + i } / (windows[k][5].length * sample_size).to_f
198
+ totals[k][15] = windows[k][15].inject(0) { |n,i| n + i } / (windows[k][15].length * sample_size).to_f
199
+
200
+ # add those stats to the totals per category
201
+ totals[:total][0] += totals[k][0]
202
+ totals[:total][1] += totals[k][1]
203
+ totals[:total][5] += totals[k][5]
204
+ totals[:total][15] += totals[k][15]
205
+ end
206
+
207
+ # redisplay the header every 40 rows
208
+ header.call if time[:rows] % 40 == 0
209
+
210
+ # show the stats
211
+ print(date_column(:show, time))
212
+ stats_to_show.each do |stat|
213
+ print " "
214
+ servers.each { |server| print "#{num_format}/" % totals[server][stat] }
215
+ print(num_format % totals[:total][stat])
216
+ end
217
+ puts
218
+
219
+ # reset the sample counter
220
+ count = Hash.new(0)
221
+ end
222
+ end
223
+ end
224
+
225
+ def put_asset(name, to)
226
+ put(File.read("#{File.dirname(__FILE__)}/assets/#{name}"), to)
227
+ end
228
+ end
229
+
230
+ Capistrano.plugin :monitor, MonitorServers
231
+
232
+ Capistrano.configuration(:must_exist).load do
233
+ desc <<-STR
234
+ Watch the load on the servers. Display is updated every 30 seconds by default,
235
+ though you can specify a DELAY environment variable to make it update more or
236
+ less frequently.
237
+ STR
238
+ task :watch_load do
239
+ monitor.load :delay => ENV["DELAY"]
240
+ end
241
+
242
+ desc <<-STR
243
+ Watch the number of requests/sec being logged on the application servers. By
244
+ default, the "production.log" is watched, but if your log is named something
245
+ else, you can specify it in the log_name variable.
246
+ STR
247
+ task :watch_requests, :roles => :app do
248
+ monitor.requests_per_second("#{shared_path}/log/#{self[:log_name] || "production.log"}")
249
+ end
250
+ end
@@ -0,0 +1,11 @@
1
+ module Capistrano
2
+ module Ext
3
+ module Version #:nodoc:
4
+ MAJOR = 1
5
+ MINOR = 0
6
+ TINY = 1
7
+
8
+ STRING = [MAJOR, MINOR, TINY].join(".")
9
+ end
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: capistrano-ext
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.0.1
7
+ date: 2006-03-06 00:00:00 -07:00
8
+ summary: Capistrano Extensions is a set of useful task libraries and methods that other developers may reference in their own recipe files.
9
+ require_paths:
10
+ - lib
11
+ email: jamis@37signals.com
12
+ homepage: http://www.rubyonrails.com
13
+ rubyforge_project:
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ signing_key:
28
+ cert_chain:
29
+ authors:
30
+ - Jamis Buck
31
+ files:
32
+ - lib/capistrano
33
+ - lib/capistrano/ext
34
+ - lib/capistrano/ext/assets
35
+ - lib/capistrano/ext/monitor.rb
36
+ - lib/capistrano/ext/version.rb
37
+ - lib/capistrano/ext/assets/request-counter.rb
38
+ test_files: []
39
+ rdoc_options: []
40
+ extra_rdoc_files: []
41
+ executables: []
42
+ extensions: []
43
+ requirements: []
44
+ dependencies:
45
+ - !ruby/object:Gem::Dependency
46
+ name: capistrano
47
+ version_requirement:
48
+ version_requirements: !ruby/object:Gem::Version::Requirement
49
+ requirements:
50
+ -
51
+ - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.0.0
54
+ version: