Skip to content

Commit ab55f22

Browse files
committed
Add debug logging
1 parent fa652ad commit ab55f22

File tree

1 file changed

+116
-18
lines changed

1 file changed

+116
-18
lines changed

script/passenger_worker_cleaner.rb

+116-18
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
MIN_WORKERS_ALLOWED = 4
99
LAST_USED_THRESHOLD_SECONDS = 10 * 60 # 10 minutes
1010

11+
# Helper method for debug logging
12+
def debug_log(message)
13+
puts "[DEBUG] #{message}" if ENV['DEBUG']
14+
end
15+
1116
# Class representing a single Passenger Worker Process
1217
class WorkerProcess
1318
attr_reader :pid, :sessions, :processed, :uptime_seconds, :cpu, :memory_mb, :last_used_seconds
@@ -20,6 +25,8 @@ def initialize(pid:, sessions:, processed:, uptime_str:, cpu:, memory_str:, last
2025
@cpu = cpu
2126
@memory_mb = parse_memory(memory_str)
2227
@last_used_seconds = parse_time(last_used_str)
28+
29+
debug_log("Initialized WorkerProcess: PID=#{@pid}, Sessions=#{@sessions}, Processed=#{@processed}, Uptime=#{@uptime_seconds}s, CPU=#{@cpu}%, Memory=#{@memory_mb}MB, Last Used=#{@last_used_seconds}s")
2330
end
2431

2532
# Parses a time string like "16m 52s" into total seconds
@@ -42,24 +49,41 @@ def parse_memory(mem_str)
4249
value = match[1].to_f
4350
unit = match[2]&.upcase || 'M'
4451

45-
case unit
46-
when 'K'
47-
(value / 1024).round(2)
48-
when 'M'
49-
value.round(2)
50-
when 'G'
51-
(value * 1024).round(2)
52-
when 'T'
53-
(value * 1024 * 1024).round(2)
54-
when 'P'
55-
(value * 1024 * 1024 * 1024).round(2)
56-
else
57-
value.round(2)
52+
memory_mb = case unit
53+
when 'K'
54+
(value / 1024).round(2)
55+
when 'M'
56+
value.round(2)
57+
when 'G'
58+
(value * 1024).round(2)
59+
when 'T'
60+
(value * 1024 * 1024).round(2)
61+
when 'P'
62+
(value * 1024 * 1024 * 1024).round(2)
63+
else
64+
value.round(2)
5865
end
66+
67+
debug_log("Parsed memory: Original='#{mem_str}', Parsed=#{memory_mb}MB")
68+
memory_mb
69+
end
70+
71+
# Formats uptime from seconds to "Xm Ys"
72+
def formatted_uptime
73+
minutes = (uptime_seconds / 60).to_i
74+
seconds = uptime_seconds % 60
75+
"#{minutes}m #{seconds}s"
76+
end
77+
78+
# Formats last used time from seconds to "Xm Ys"
79+
def formatted_last_used
80+
minutes = (last_used_seconds / 60).to_i
81+
seconds = last_used_seconds % 60
82+
"#{minutes}m #{seconds}s"
5983
end
6084

6185
def to_s
62-
"PID: #{@pid}, Last Used: #{@last_used_seconds}s, Memory: #{@memory_mb} MB"
86+
"PID: #{@pid}, Sessions: #{@sessions}, Processed: #{@processed}, Uptime: #{formatted_uptime}, CPU: #{@cpu}%, Memory: #{@memory_mb} MB, Last Used: #{formatted_last_used}"
6387
end
6488
end
6589

@@ -74,12 +98,14 @@ def initialize(command: PASSENGER_STATUS_CMD)
7498
end
7599

76100
def execute
101+
debug_log("Executing command: #{@command}")
77102
stdout, stderr, status = Open3.capture3(@command)
78103

79104
unless status.success?
80-
raise "Error executing #{@command}: #{stderr}"
105+
raise "Error executing #{@command}: #{stderr.strip}"
81106
end
82107

108+
debug_log('Command executed successfully.')
83109
parse(stdout)
84110
end
85111

@@ -90,16 +116,19 @@ def parse(output)
90116

91117
output.each_line do |line|
92118
line = line.strip
119+
debug_log("Parsing line: '#{line}'")
93120

94121
# Capture total processes using regex to handle variable whitespace
95122
if line =~ /^Processes\s*:\s*(\d+)/
96123
@total_processes = Regexp.last_match(1).to_i
124+
debug_log("Total Processes parsed: #{@total_processes}")
97125
next
98126
end
99127

100128
# Detect start of Application groups
101129
if line =~ /^-+ Application groups -+$/
102130
in_app_group = true
131+
debug_log('Entered Application groups section.')
103132
next
104133
end
105134

@@ -110,6 +139,7 @@ def parse(output)
110139
# Save previous worker if exists
111140
if current_worker_data.any?
112141
@workers << build_worker(current_worker_data)
142+
debug_log("Added WorkerProcess from previous data: PID=#{current_worker_data[:pid]}")
113143
current_worker_data = {}
114144
end
115145

@@ -118,26 +148,30 @@ def parse(output)
118148
current_worker_data[:sessions] = Regexp.last_match(2).to_i
119149
current_worker_data[:processed] = Regexp.last_match(3).to_i
120150
current_worker_data[:uptime_str] = Regexp.last_match(4).strip
151+
debug_log("Parsed Worker Entry: PID=#{current_worker_data[:pid]}, Sessions=#{current_worker_data[:sessions]}, Processed=#{current_worker_data[:processed]}, Uptime='#{current_worker_data[:uptime_str]}'")
121152
next
122153
end
123154

124155
# Extract CPU and Memory using regex to handle variable whitespace
125156
if line =~ /^CPU\s*:\s*([\d.]+)%\s+Memory\s*:\s*([\d.]+\s*[KMGTP]?)/i
126157
current_worker_data[:cpu] = Regexp.last_match(1).to_f
127158
current_worker_data[:memory_str] = Regexp.last_match(2).strip
159+
debug_log("Parsed CPU and Memory: CPU=#{current_worker_data[:cpu]}%, Memory='#{current_worker_data[:memory_str]}'")
128160
next
129161
end
130162

131163
# Extract Last used using regex to handle variable whitespace
132164
if line =~ /^Last\s+used\s*:\s*([\dm\s]+s)\s*(?:ago|ag)?/i
133165
current_worker_data[:last_used_str] = Regexp.last_match(1).strip
166+
debug_log("Parsed Last Used: '#{current_worker_data[:last_used_str]}'")
134167
next
135168
end
136169
end
137170

138171
# Add the last worker if exists
139172
if current_worker_data.any?
140173
@workers << build_worker(current_worker_data)
174+
debug_log("Added WorkerProcess from last worker data: PID=#{current_worker_data[:pid]}")
141175
end
142176
end
143177

@@ -165,14 +199,14 @@ def run
165199
@parser.execute
166200
rescue => e
167201
puts e.message
202+
debug_log("Error during execution: #{e.message}")
168203
exit 1
169204
end
170205

171206
total_processes = @parser.total_processes
172207
workers = @parser.workers
173208

174-
puts "Total Processes: #{total_processes}"
175-
puts "Total Workers: #{workers.size}"
209+
print_summary(total_processes, workers) if ENV['DEBUG']
176210

177211
if total_processes > MIN_WORKERS_ALLOWED
178212
puts "Number of processes (#{total_processes}) exceeds the minimum allowed (#{MIN_WORKERS_ALLOWED})."
@@ -186,35 +220,99 @@ def run
186220
last_used_seconds_remainder = last_used_seconds % 60
187221

188222
puts "Killing worker PID #{pid} with last used time of #{last_used_minutes}m #{last_used_seconds_remainder}s and memory: #{worker_to_kill.memory_mb} MB."
223+
debug_log("Attempting to kill WorkerProcess: PID=#{pid}, Last Used=#{last_used_seconds}s, Memory=#{worker_to_kill.memory_mb}MB")
189224

190225
kill_worker(pid)
191226
else
192227
puts "No workers have been idle for more than #{LAST_USED_THRESHOLD_SECONDS / 60} minutes."
228+
debug_log('No eligible workers found to kill.')
193229
end
194230
else
195231
puts "Number of processes (#{total_processes}) is under (#{MIN_WORKERS_ALLOWED}). No action needed."
232+
debug_log("Number of processes (#{total_processes}) is within the allowed limit.")
196233
end
197234
end
198235

199236
private
237+
def print_summary(total_processes, workers)
238+
puts "\n===== Passenger Workers Summary ====="
239+
puts "Total Processes: #{total_processes}"
240+
puts "Total Workers: #{workers.size}\n\n"
241+
242+
if workers.empty?
243+
puts 'No workers found.'
244+
return
245+
end
246+
247+
# Define column widths
248+
pid_width = 8
249+
sessions_width = 10
250+
processed_width = 12
251+
uptime_width = 12
252+
cpu_width = 8
253+
memory_width = 14
254+
last_used_width = 14
255+
256+
# Print table header
257+
header = [
258+
'PID'.ljust(pid_width),
259+
'Sessions'.ljust(sessions_width),
260+
'Processed'.ljust(processed_width),
261+
'Uptime'.ljust(uptime_width),
262+
'CPU (%)'.ljust(cpu_width),
263+
'Memory (MB)'.ljust(memory_width),
264+
'Last Used'.ljust(last_used_width)
265+
].join(' | ')
266+
267+
separator = '-' * header.length
268+
269+
puts header
270+
puts separator
271+
272+
# Print each worker's details
273+
workers.each do |worker|
274+
row = [
275+
worker.pid.to_s.ljust(pid_width),
276+
worker.sessions.to_s.ljust(sessions_width),
277+
worker.processed.to_s.ljust(processed_width),
278+
worker.formatted_uptime.ljust(uptime_width),
279+
worker.cpu.to_s.ljust(cpu_width),
280+
worker.memory_mb.to_s.ljust(memory_width),
281+
worker.formatted_last_used.ljust(last_used_width)
282+
].join(' | ')
283+
284+
puts row
285+
end
286+
287+
puts "===== End of Summary =====\n\n"
288+
end
289+
200290
def find_worker_to_kill(workers)
201291
eligible_workers = workers.select { |w| w.last_used_seconds > LAST_USED_THRESHOLD_SECONDS }
202292

293+
debug_log("Eligible workers to kill (idle > #{LAST_USED_THRESHOLD_SECONDS / 60} minutes): #{eligible_workers.map(&:pid).join(', ')}")
294+
203295
return nil if eligible_workers.empty?
204296

205297
# Find the worker with the maximum last used time
206-
eligible_workers.max_by { |w| w.last_used_seconds }
298+
worker = eligible_workers.max_by { |w| w.last_used_seconds }
299+
debug_log("Selected worker to kill: PID=#{worker.pid}, Last Used=#{worker.last_used_seconds}s, Memory=#{worker.memory_mb}MB")
300+
worker
207301
end
208302

209303
def kill_worker(pid)
210304
Process.kill('TERM', pid)
211305
puts "Successfully sent TERM signal to PID #{pid}."
306+
debug_log("Sent TERM signal to PID #{pid}.")
212307
rescue Errno::ESRCH
213308
puts "Process with PID #{pid} does not exist."
309+
debug_log("Failed to kill PID #{pid}: Process does not exist.")
214310
rescue Errno::EPERM
215311
puts "Insufficient permissions to kill PID #{pid}."
312+
debug_log("Failed to kill PID #{pid}: Insufficient permissions.")
216313
rescue => e
217314
puts "Failed to kill PID #{pid}: #{e.message}"
315+
debug_log("Failed to kill PID #{pid}: #{e.message}")
218316
end
219317
end
220318

0 commit comments

Comments
 (0)