capistrano-ext 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: