8
8
MIN_WORKERS_ALLOWED = 4
9
9
LAST_USED_THRESHOLD_SECONDS = 10 * 60 # 10 minutes
10
10
11
+ # Helper method for debug logging
12
+ def debug_log ( message )
13
+ puts "[DEBUG] #{ message } " if ENV [ 'DEBUG' ]
14
+ end
15
+
11
16
# Class representing a single Passenger Worker Process
12
17
class WorkerProcess
13
18
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
20
25
@cpu = cpu
21
26
@memory_mb = parse_memory ( memory_str )
22
27
@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" )
23
30
end
24
31
25
32
# Parses a time string like "16m 52s" into total seconds
@@ -42,24 +49,41 @@ def parse_memory(mem_str)
42
49
value = match [ 1 ] . to_f
43
50
unit = match [ 2 ] &.upcase || 'M'
44
51
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 )
58
65
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"
59
83
end
60
84
61
85
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 } "
63
87
end
64
88
end
65
89
@@ -74,12 +98,14 @@ def initialize(command: PASSENGER_STATUS_CMD)
74
98
end
75
99
76
100
def execute
101
+ debug_log ( "Executing command: #{ @command } " )
77
102
stdout , stderr , status = Open3 . capture3 ( @command )
78
103
79
104
unless status . success?
80
- raise "Error executing #{ @command } : #{ stderr } "
105
+ raise "Error executing #{ @command } : #{ stderr . strip } "
81
106
end
82
107
108
+ debug_log ( 'Command executed successfully.' )
83
109
parse ( stdout )
84
110
end
85
111
@@ -90,16 +116,19 @@ def parse(output)
90
116
91
117
output . each_line do |line |
92
118
line = line . strip
119
+ debug_log ( "Parsing line: '#{ line } '" )
93
120
94
121
# Capture total processes using regex to handle variable whitespace
95
122
if line =~ /^Processes\s *:\s *(\d +)/
96
123
@total_processes = Regexp . last_match ( 1 ) . to_i
124
+ debug_log ( "Total Processes parsed: #{ @total_processes } " )
97
125
next
98
126
end
99
127
100
128
# Detect start of Application groups
101
129
if line =~ /^-+ Application groups -+$/
102
130
in_app_group = true
131
+ debug_log ( 'Entered Application groups section.' )
103
132
next
104
133
end
105
134
@@ -110,6 +139,7 @@ def parse(output)
110
139
# Save previous worker if exists
111
140
if current_worker_data . any?
112
141
@workers << build_worker ( current_worker_data )
142
+ debug_log ( "Added WorkerProcess from previous data: PID=#{ current_worker_data [ :pid ] } " )
113
143
current_worker_data = { }
114
144
end
115
145
@@ -118,26 +148,30 @@ def parse(output)
118
148
current_worker_data [ :sessions ] = Regexp . last_match ( 2 ) . to_i
119
149
current_worker_data [ :processed ] = Regexp . last_match ( 3 ) . to_i
120
150
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 ] } '" )
121
152
next
122
153
end
123
154
124
155
# Extract CPU and Memory using regex to handle variable whitespace
125
156
if line =~ /^CPU\s *:\s *([\d .]+)%\s +Memory\s *:\s *([\d .]+\s *[KMGTP]?)/i
126
157
current_worker_data [ :cpu ] = Regexp . last_match ( 1 ) . to_f
127
158
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 ] } '" )
128
160
next
129
161
end
130
162
131
163
# Extract Last used using regex to handle variable whitespace
132
164
if line =~ /^Last\s +used\s *:\s *([\d m\s ]+s)\s *(?:ago|ag)?/i
133
165
current_worker_data [ :last_used_str ] = Regexp . last_match ( 1 ) . strip
166
+ debug_log ( "Parsed Last Used: '#{ current_worker_data [ :last_used_str ] } '" )
134
167
next
135
168
end
136
169
end
137
170
138
171
# Add the last worker if exists
139
172
if current_worker_data . any?
140
173
@workers << build_worker ( current_worker_data )
174
+ debug_log ( "Added WorkerProcess from last worker data: PID=#{ current_worker_data [ :pid ] } " )
141
175
end
142
176
end
143
177
@@ -165,14 +199,14 @@ def run
165
199
@parser . execute
166
200
rescue => e
167
201
puts e . message
202
+ debug_log ( "Error during execution: #{ e . message } " )
168
203
exit 1
169
204
end
170
205
171
206
total_processes = @parser . total_processes
172
207
workers = @parser . workers
173
208
174
- puts "Total Processes: #{ total_processes } "
175
- puts "Total Workers: #{ workers . size } "
209
+ print_summary ( total_processes , workers ) if ENV [ 'DEBUG' ]
176
210
177
211
if total_processes > MIN_WORKERS_ALLOWED
178
212
puts "Number of processes (#{ total_processes } ) exceeds the minimum allowed (#{ MIN_WORKERS_ALLOWED } )."
@@ -186,35 +220,99 @@ def run
186
220
last_used_seconds_remainder = last_used_seconds % 60
187
221
188
222
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" )
189
224
190
225
kill_worker ( pid )
191
226
else
192
227
puts "No workers have been idle for more than #{ LAST_USED_THRESHOLD_SECONDS / 60 } minutes."
228
+ debug_log ( 'No eligible workers found to kill.' )
193
229
end
194
230
else
195
231
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." )
196
233
end
197
234
end
198
235
199
236
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
+
200
290
def find_worker_to_kill ( workers )
201
291
eligible_workers = workers . select { |w | w . last_used_seconds > LAST_USED_THRESHOLD_SECONDS }
202
292
293
+ debug_log ( "Eligible workers to kill (idle > #{ LAST_USED_THRESHOLD_SECONDS / 60 } minutes): #{ eligible_workers . map ( &:pid ) . join ( ', ' ) } " )
294
+
203
295
return nil if eligible_workers . empty?
204
296
205
297
# 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
207
301
end
208
302
209
303
def kill_worker ( pid )
210
304
Process . kill ( 'TERM' , pid )
211
305
puts "Successfully sent TERM signal to PID #{ pid } ."
306
+ debug_log ( "Sent TERM signal to PID #{ pid } ." )
212
307
rescue Errno ::ESRCH
213
308
puts "Process with PID #{ pid } does not exist."
309
+ debug_log ( "Failed to kill PID #{ pid } : Process does not exist." )
214
310
rescue Errno ::EPERM
215
311
puts "Insufficient permissions to kill PID #{ pid } ."
312
+ debug_log ( "Failed to kill PID #{ pid } : Insufficient permissions." )
216
313
rescue => e
217
314
puts "Failed to kill PID #{ pid } : #{ e . message } "
315
+ debug_log ( "Failed to kill PID #{ pid } : #{ e . message } " )
218
316
end
219
317
end
220
318
0 commit comments