daemonizer 0.4.7 → 0.4.8

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.
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