daemonizer 0.4.7 → 0.4.8

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.7
1
+ 0.4.8
data/daemonizer.gemspec CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{daemonizer}
8
- s.version = "0.4.7"
8
+ s.version = "0.4.8"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Gleb Pomykalov"]
@@ -37,6 +37,7 @@ Gem::Specification.new do |s|
37
37
  "lib/daemonizer/handler.rb",
38
38
  "lib/daemonizer/option.rb",
39
39
  "lib/daemonizer/process_manager.rb",
40
+ "lib/daemonizer/stats.rb",
40
41
  "lib/daemonizer/worker.rb",
41
42
  "lib/daemonizer/worker_pool.rb"
42
43
  ]
@@ -14,6 +14,7 @@ module Daemonizer
14
14
  autoload :Handler, __p('handler')
15
15
  autoload :FakeHandler, __p('handler')
16
16
  autoload :Option, __p('option')
17
+ autoload :Stats, __p('stats')
17
18
 
18
19
  include Errors
19
20
  end
@@ -77,7 +77,6 @@ module Daemonizer
77
77
  return true
78
78
  end
79
79
 
80
-
81
80
  desc "restart", "Restart pool"
82
81
  def restart(pool_name = nil)
83
82
  invoke :stop, pool_name
@@ -99,6 +98,14 @@ module Daemonizer
99
98
  return true
100
99
  end
101
100
 
101
+ desc "stat", "Pools statistics"
102
+ def stat(pool_name = nil)
103
+ statistics = Daemonizer::Stats::MemoryStats.new
104
+ Daemonizer.find_pools(pool_name).each do |pool|
105
+ statistics.print(pool.name.to_s)
106
+ end
107
+ end
108
+
102
109
  private
103
110
  def control_pools_loop(pool_name, message = nil, debug = false, &block)
104
111
  Daemonizer.find_pools(pool_name).each do |pool|
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/env ruby
2
+ # Phusion Passenger - http://www.modrails.com/
3
+ # Copyright (c) 2008, 2009 Phusion
4
+ #
5
+ # "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui.
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the "Software"), to deal
9
+ # in the Software without restriction, including without limitation the rights
10
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ # copies of the Software, and to permit persons to whom the Software is
12
+ # furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in
15
+ # all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ # THE SOFTWARE.
24
+ #
25
+ #
26
+ # * Modified by Gleb Pomykalov for daemonizer - http://daemonizer.org
27
+ #
28
+ #
29
+
30
+ # ANSI color codes
31
+ RESET = "\e[0m"
32
+ BOLD = "\e[1m"
33
+ WHITE = "\e[37m"
34
+ YELLOW = "\e[33m"
35
+ BLUE_BG = "\e[44m"
36
+
37
+ module Daemonizer; end
38
+ module Daemonizer::Stats
39
+ # Container for tabular data.
40
+ class Table
41
+ def initialize(column_names)
42
+ @column_names = column_names
43
+ @rows = []
44
+ end
45
+
46
+ def add_row(values)
47
+ @rows << values.to_a
48
+ end
49
+
50
+ def add_rows(list_of_rows)
51
+ list_of_rows.each do |row|
52
+ add_row(row)
53
+ end
54
+ end
55
+
56
+ def remove_column(name)
57
+ i = @column_names.index(name)
58
+ @column_names.delete_at(i)
59
+ @rows.each do |row|
60
+ row.delete_at(i)
61
+ end
62
+ end
63
+
64
+ def to_s(title = nil)
65
+ max_column_widths = [1] * @column_names.size
66
+ (@rows + [@column_names]).each do |row|
67
+ row.each_with_index do |value, i|
68
+ max_column_widths[i] = [value.to_s.size, max_column_widths[i]].max
69
+ end
70
+ end
71
+
72
+ format_string = max_column_widths.map{ |i| "%#{-i}s" }.join(" ")
73
+ header = sprintf(format_string, *@column_names).rstrip << "\n"
74
+ if title
75
+ free_space = header.size - title.size - 2
76
+ if free_space <= 0
77
+ left_bar_size = 3
78
+ right_bar_size = 3
79
+ else
80
+ left_bar_size = free_space / 2
81
+ right_bar_size = free_space - left_bar_size
82
+ end
83
+ result = "#{BLUE_BG}#{BOLD}#{YELLOW}\n"
84
+ result << "#{"-" * left_bar_size} #{title} #{"-" * right_bar_size}\n"
85
+ if !@rows.empty?
86
+ result << WHITE
87
+ result << header
88
+ end
89
+ else
90
+ result = header.dup
91
+ end
92
+ if @rows.empty?
93
+ result << RESET
94
+ else
95
+ result << ("-" * header.size) << "#{RESET}\n"
96
+ @rows.each do |row|
97
+ result << sprintf(format_string, *row).rstrip << "\n"
98
+ end
99
+ end
100
+ result
101
+ end
102
+ end
103
+
104
+ class MemoryStats
105
+ class Process
106
+ attr_accessor :pid
107
+ attr_accessor :ppid
108
+ attr_accessor :threads
109
+ attr_accessor :vm_size # in KB
110
+ attr_accessor :rss # in KB
111
+ attr_accessor :name
112
+ attr_accessor :private_dirty_rss # in KB
113
+
114
+ def vm_size_in_mb
115
+ return sprintf("%.1f MB", vm_size / 1024.0)
116
+ end
117
+
118
+ def rss_in_mb
119
+ return sprintf("%.1f MB", rss / 1024.0)
120
+ end
121
+
122
+ def private_dirty_rss_in_mb
123
+ if private_dirty_rss.is_a?(Numeric)
124
+ return sprintf("%.1f MB", private_dirty_rss / 1024.0)
125
+ else
126
+ return "?"
127
+ end
128
+ end
129
+
130
+ def to_a
131
+ return [pid, ppid, vm_size_in_mb, private_dirty_rss_in_mb, rss_in_mb, name]
132
+ end
133
+ end
134
+
135
+ def print(pool_name)
136
+ puts
137
+ pool_processes = list_processes(:match =>
138
+ /(#{pool_name} worker: instance \d{1,}|#{pool_name} monitor)/)
139
+ print_process_list("#{pool_name} processes", pool_processes, :show_ppid => false)
140
+
141
+ if RUBY_PLATFORM !~ /linux/
142
+ puts
143
+ puts "*** WARNING: The private dirty RSS can only be displayed " <<
144
+ "on Linux. You're currently using '#{RUBY_PLATFORM}'."
145
+ elsif ::Process.uid != 0 && pool_processes.any?{ |p| p.private_dirty_rss.nil? }
146
+ puts
147
+ puts "*** WARNING: Please run this tool as root. Otherwise the " <<
148
+ "private dirty RSS of processes cannot be determined."
149
+ end
150
+ end
151
+
152
+ # Returns a list of Process objects that match the given search criteria.
153
+ #
154
+ # # Search by executable path.
155
+ # list_processes(:exe => '/usr/sbin/apache2')
156
+ #
157
+ # # Search by executable name.
158
+ # list_processes(:name => 'ruby1.8')
159
+ #
160
+ # # Search by process name.
161
+ # list_processes(:match => 'Passenger FrameworkSpawner')
162
+ def list_processes(options)
163
+ if options[:exe]
164
+ name = options[:exe].sub(/.*\/(.*)/, '\1')
165
+ if RUBY_PLATFORM =~ /linux/
166
+ ps = "ps -C '#{name}'"
167
+ else
168
+ ps = "ps -A"
169
+ options[:match] = Regexp.new(Regexp.escape(name))
170
+ end
171
+ elsif options[:name]
172
+ if RUBY_PLATFORM =~ /linux/
173
+ ps = "ps -C '#{options[:name]}'"
174
+ else
175
+ ps = "ps -A"
176
+ options[:match] = Regexp.new(" #{Regexp.escape(options[:name])}")
177
+ end
178
+ elsif options[:match]
179
+ ps = "ps -A"
180
+ else
181
+ raise ArgumentError, "Invalid options."
182
+ end
183
+
184
+ processes = []
185
+ case RUBY_PLATFORM
186
+ when /solaris/
187
+ list = `#{ps} -o pid,ppid,nlwp,vsz,rss,comm`.split("\n")
188
+ threads_known = true
189
+ when /darwin/
190
+ list = `#{ps} -w -o pid,ppid,vsz,rss,command`.split("\n")
191
+ threads_known = false
192
+ else
193
+ list = `#{ps} -w -o pid,ppid,nlwp,vsz,rss,command`.split("\n")
194
+ threads_known = true
195
+ end
196
+ list.shift
197
+ list.each do |line|
198
+ line.gsub!(/^ */, '')
199
+ line.gsub!(/ *$/, '')
200
+
201
+ p = Process.new
202
+ if threads_known
203
+ p.pid, p.ppid, p.threads, p.vm_size, p.rss, p.name = line.split(/ +/, 6)
204
+ else
205
+ p.pid, p.ppid, p.vm_size, p.rss, p.name = line.split(/ +/, 5)
206
+ p.threads = "?"
207
+ end
208
+ p.name.sub!(/\Aruby: /, '')
209
+ p.name.sub!(/ \(ruby\)\Z/, '')
210
+ if p.name !~ /^ps/ && (!options[:match] || p.name.match(options[:match]))
211
+ # Convert some values to integer.
212
+ [:pid, :ppid, :vm_size, :rss].each do |attr|
213
+ p.send("#{attr}=", p.send(attr).to_i)
214
+ end
215
+ p.threads = p.threads.to_i if threads_known
216
+
217
+ if platform_provides_private_dirty_rss_information?
218
+ p.private_dirty_rss = determine_private_dirty_rss(p.pid)
219
+ end
220
+ processes << p
221
+ end
222
+ end
223
+ return processes
224
+ end
225
+
226
+ private
227
+ def platform_provides_private_dirty_rss_information?
228
+ return RUBY_PLATFORM =~ /linux/
229
+ end
230
+
231
+ # Returns the private dirty RSS for the given process, in KB.
232
+ def determine_private_dirty_rss(pid)
233
+ total = 0
234
+ File.read("/proc/#{pid}/smaps").split("\n").each do |line|
235
+ line =~ /^(Private)_Dirty: +(\d+)/
236
+ if $2
237
+ total += $2.to_i
238
+ end
239
+ end
240
+ if total == 0
241
+ return nil
242
+ else
243
+ return total
244
+ end
245
+ rescue Errno::EACCES, Errno::ENOENT
246
+ return nil
247
+ end
248
+
249
+ def print_process_list(title, processes, options = {})
250
+ table = Table.new(%w{PID PPID VMSize Private Resident Name})
251
+ table.add_rows(processes)
252
+ if options.has_key?(:show_ppid) && !options[:show_ppid]
253
+ table.remove_column('PPID')
254
+ end
255
+ if platform_provides_private_dirty_rss_information?
256
+ table.remove_column('Resident')
257
+ else
258
+ table.remove_column('Private')
259
+ end
260
+ puts table.to_s(title)
261
+
262
+ if platform_provides_private_dirty_rss_information?
263
+ total_private_dirty_rss = 0
264
+ some_private_dirty_rss_cannot_be_determined = false
265
+ processes.each do |p|
266
+ if p.private_dirty_rss.is_a?(Numeric)
267
+ total_private_dirty_rss += p.private_dirty_rss
268
+ else
269
+ some_private_dirty_rss_cannot_be_determined = true
270
+ end
271
+ end
272
+ puts "### Processes: #{processes.size}"
273
+ printf "### Total private dirty RSS: %.2f MB", total_private_dirty_rss / 1024.0
274
+ if some_private_dirty_rss_cannot_be_determined
275
+ puts " (?)"
276
+ else
277
+ puts
278
+ end
279
+ end
280
+ end
281
+ end
282
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: daemonizer
3
3
  version: !ruby/object:Gem::Version
4
- hash: 1
4
+ hash: 31
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 4
9
- - 7
10
- version: 0.4.7
9
+ - 8
10
+ version: 0.4.8
11
11
  platform: ruby
12
12
  authors:
13
13
  - Gleb Pomykalov
@@ -62,6 +62,7 @@ files:
62
62
  - lib/daemonizer/handler.rb
63
63
  - lib/daemonizer/option.rb
64
64
  - lib/daemonizer/process_manager.rb
65
+ - lib/daemonizer/stats.rb
65
66
  - lib/daemonizer/worker.rb
66
67
  - lib/daemonizer/worker_pool.rb
67
68
  has_rdoc: true