bluepill 0.1.2 → 0.1.4
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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +820 -0
- data/CONTRIBUTING.md +31 -0
- data/LICENSE +1 -1
- data/README.md +69 -30
- data/bin/bluepill +2 -2
- data/bluepill.gemspec +17 -6
- data/lib/bluepill/application.rb +311 -43
- data/lib/bluepill/condition_watch.rb +105 -10
- data/lib/bluepill/controller.rb +125 -14
- data/lib/bluepill/dsl/app_proxy.rb +103 -4
- data/lib/bluepill/dsl/process_factory.rb +185 -62
- data/lib/bluepill/dsl/process_proxy.rb +132 -14
- data/lib/bluepill/dsl.rb +27 -5
- data/lib/bluepill/group.rb +142 -9
- data/lib/bluepill/logger.rb +122 -7
- data/lib/bluepill/process.rb +582 -211
- data/lib/bluepill/process_commands.rb +262 -0
- data/lib/bluepill/process_conditions/always_true.rb +18 -4
- data/lib/bluepill/process_conditions/cpu_usage.rb +34 -0
- data/lib/bluepill/process_conditions/file_time.rb +40 -4
- data/lib/bluepill/process_conditions/http.rb +81 -23
- data/lib/bluepill/process_conditions/mem_usage.rb +64 -5
- data/lib/bluepill/process_conditions/process_condition.rb +45 -2
- data/lib/bluepill/process_conditions/running_time.rb +36 -1
- data/lib/bluepill/process_conditions/zombie_process.rb +26 -5
- data/lib/bluepill/process_conditions.rb +15 -1
- data/lib/bluepill/process_journal.rb +256 -104
- data/lib/bluepill/process_statistics.rb +33 -3
- data/lib/bluepill/socket.rb +63 -16
- data/lib/bluepill/system/commands.rb +286 -0
- data/lib/bluepill/system.rb +193 -229
- data/lib/bluepill/trigger.rb +111 -15
- data/lib/bluepill/triggers/flapping.rb +86 -5
- data/lib/bluepill/util/rotational_array.rb +25 -2
- data/lib/bluepill/version.rb +71 -19
- data/lib/bluepill.rb +25 -2
- data/sig/bluepill.rbs +926 -0
- data/sig/manifest.yaml +7 -0
- metadata +16 -35
- data/bin/sample_forking_server +0 -66
- data/lib/bluepill/application/client.rb +0 -6
- data/lib/bluepill/application/server.rb +0 -21
data/sig/bluepill.rbs
ADDED
|
@@ -0,0 +1,926 @@
|
|
|
1
|
+
# Type signatures for bluepill
|
|
2
|
+
|
|
3
|
+
module Bluepill
|
|
4
|
+
# The value of Bluepill::System.ps_axu: ps fields by pid
|
|
5
|
+
type ps_table = Hash[Integer, Array[String]]
|
|
6
|
+
|
|
7
|
+
# A time in seconds. An ActiveSupport::Duration also works, since it
|
|
8
|
+
# passes for its value.
|
|
9
|
+
type seconds = Integer | Float
|
|
10
|
+
|
|
11
|
+
# A process block from the config, which runs with self set to the
|
|
12
|
+
# ProcessProxy and also gets it as an argument
|
|
13
|
+
type process_block = ^(ProcessProxy process) [self: ProcessProxy] -> void
|
|
14
|
+
|
|
15
|
+
# Sets up Bundler when bluepill runs with a Gemfile, such as under bundle
|
|
16
|
+
# exec
|
|
17
|
+
def self.setup_bundler: (?String? gemfile) -> void
|
|
18
|
+
|
|
19
|
+
# Loads an application from a config file. The block runs with self set to
|
|
20
|
+
# the AppProxy, and also gets it as an argument.
|
|
21
|
+
def self.application: (_ToS app_name, ?Hash[Symbol, untyped] options) { (AppProxy app) [self: AppProxy] -> void } -> void
|
|
22
|
+
|
|
23
|
+
module Version
|
|
24
|
+
def self.major: () -> Integer
|
|
25
|
+
|
|
26
|
+
def self.minor: () -> Integer
|
|
27
|
+
|
|
28
|
+
def self.patch: () -> Integer
|
|
29
|
+
|
|
30
|
+
def self.pre: () -> Integer?
|
|
31
|
+
|
|
32
|
+
def self.to_h: () -> { major: Integer, minor: Integer, patch: Integer, pre: Integer? }
|
|
33
|
+
|
|
34
|
+
def self.to_a: () -> Array[Integer]
|
|
35
|
+
|
|
36
|
+
def self.to_s: () -> String
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The daemon: it holds the groups of processes and serves commands from
|
|
40
|
+
# bluepill's socket
|
|
41
|
+
class Application
|
|
42
|
+
PROCESS_COMMANDS: Array[Symbol]
|
|
43
|
+
|
|
44
|
+
attr_accessor name: String
|
|
45
|
+
attr_accessor logger: Logger
|
|
46
|
+
attr_accessor base_dir: String
|
|
47
|
+
attr_accessor socket: UNIXServer?
|
|
48
|
+
attr_accessor pid_file: String
|
|
49
|
+
attr_accessor kill_timeout: Integer
|
|
50
|
+
attr_accessor groups: Hash[String?, Group]
|
|
51
|
+
# Not used by bluepill
|
|
52
|
+
attr_accessor work_queue: untyped
|
|
53
|
+
attr_accessor pids_dir: String
|
|
54
|
+
attr_accessor log_file: String?
|
|
55
|
+
|
|
56
|
+
@foreground: bool?
|
|
57
|
+
@mutex: Thread::Mutex
|
|
58
|
+
@listener_thread: Thread?
|
|
59
|
+
@running: bool
|
|
60
|
+
|
|
61
|
+
def initialize: (String name, ?Hash[Symbol, untyped] options) -> void
|
|
62
|
+
|
|
63
|
+
def foreground?: () -> bool
|
|
64
|
+
|
|
65
|
+
def mutex: [T] () { () -> T } -> T
|
|
66
|
+
|
|
67
|
+
def load: () -> void
|
|
68
|
+
|
|
69
|
+
# The PROCESS_COMMANDS, which return the affected processes, or the
|
|
70
|
+
# status lines for status
|
|
71
|
+
def start: (?String? group_name, ?String? process_name) -> Array[String]
|
|
72
|
+
|
|
73
|
+
def stop: (?String? group_name, ?String? process_name) -> Array[String]
|
|
74
|
+
|
|
75
|
+
def restart: (?String? group_name, ?String? process_name) -> Array[String]
|
|
76
|
+
|
|
77
|
+
def unmonitor: (?String? group_name, ?String? process_name) -> Array[String]
|
|
78
|
+
|
|
79
|
+
def status: (?String? group_name, ?String? process_name) -> Array[String]
|
|
80
|
+
|
|
81
|
+
def add_process: (Process process, ?_ToS? group_name) -> Array[Process]
|
|
82
|
+
|
|
83
|
+
def version: () -> singleton(Version)
|
|
84
|
+
|
|
85
|
+
# Ruby's protected methods are private in RBS
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def send_to_process_or_group: (Symbol method, String? group_name, String? process_name) -> Array[String]
|
|
90
|
+
|
|
91
|
+
def start_listener: () -> Thread
|
|
92
|
+
|
|
93
|
+
def start_server: () -> void
|
|
94
|
+
|
|
95
|
+
def run: () -> void
|
|
96
|
+
|
|
97
|
+
def cleanup: () -> void
|
|
98
|
+
|
|
99
|
+
def setup_signal_traps: () -> void
|
|
100
|
+
|
|
101
|
+
def setup_logger_and_pids_dir: () -> void
|
|
102
|
+
|
|
103
|
+
def setup_pids_dir: () -> void
|
|
104
|
+
|
|
105
|
+
def kill_previous_bluepill: () -> void
|
|
106
|
+
|
|
107
|
+
def write_pid_file: () -> void
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Runs the bluepill commands, and sends them to the daemon over its socket
|
|
111
|
+
class Controller
|
|
112
|
+
attr_accessor base_dir: String
|
|
113
|
+
attr_accessor log_file: String?
|
|
114
|
+
attr_accessor sockets_dir: String
|
|
115
|
+
attr_accessor pids_dir: String
|
|
116
|
+
|
|
117
|
+
def initialize: (Hash[Symbol, untyped] options) -> void
|
|
118
|
+
|
|
119
|
+
def running_applications: () -> Array[String]
|
|
120
|
+
|
|
121
|
+
def handle_command: (String application, String | Symbol command, *String args) -> void
|
|
122
|
+
|
|
123
|
+
# Returns what the daemon responds with, which is unmarshaled from the socket
|
|
124
|
+
def send_to_daemon: (String application, String | Symbol command, *String args) -> untyped
|
|
125
|
+
|
|
126
|
+
def grep_pattern: (String application, ?String? query) -> String
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
def cleanup_bluepill_directory: () -> void
|
|
131
|
+
|
|
132
|
+
def pid_for: (String app) -> (Integer | false)
|
|
133
|
+
|
|
134
|
+
def setup_dir_structure: () -> void
|
|
135
|
+
|
|
136
|
+
def verify_version!: (String application) -> void
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
module Socket
|
|
140
|
+
TIMEOUT: Integer
|
|
141
|
+
MAX_ATTEMPTS: Integer
|
|
142
|
+
|
|
143
|
+
def self.client: [T] (String base_dir, String name) { (UNIXSocket socket) -> T } -> T
|
|
144
|
+
|
|
145
|
+
# Returns the daemon's response, unmarshaled
|
|
146
|
+
def self.client_command: (String base_dir, String name, String command) -> untyped
|
|
147
|
+
|
|
148
|
+
def self.server: (String base_dir, String name) -> UNIXServer
|
|
149
|
+
|
|
150
|
+
def self.socket_path: (String base_dir, String name) -> String
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Runs commands for a Process, which is the only class that includes it
|
|
154
|
+
module ProcessCommands : _Host
|
|
155
|
+
# What ProcessCommands uses from Process
|
|
156
|
+
interface _Host
|
|
157
|
+
%a{pure} def name: () -> String
|
|
158
|
+
%a{pure} def logger: () -> Logger
|
|
159
|
+
%a{pure} def children: () -> Array[Process]
|
|
160
|
+
%a{pure} def pre_start_command: () -> String?
|
|
161
|
+
%a{pure} def start_command: () -> String?
|
|
162
|
+
%a{pure} def stop_command: () -> String?
|
|
163
|
+
%a{pure} def restart_command: () -> String?
|
|
164
|
+
%a{pure} def stop_signals: () -> Array[Symbol | Integer]?
|
|
165
|
+
%a{pure} def start_grace_time: () -> seconds
|
|
166
|
+
%a{pure} def stop_grace_time: () -> seconds
|
|
167
|
+
%a{pure} def restart_grace_time: () -> seconds
|
|
168
|
+
%a{pure} def on_start_timeout: () -> (String | Symbol)
|
|
169
|
+
%a{pure} def monitor_children: () -> bool
|
|
170
|
+
%a{pure} def uid: () -> String?
|
|
171
|
+
%a{pure} def gid: () -> String?
|
|
172
|
+
%a{pure} def working_dir: () -> String?
|
|
173
|
+
%a{pure} def environment: () -> Hash[_ToS, _ToS]?
|
|
174
|
+
%a{pure} def pid_file: () -> String?
|
|
175
|
+
%a{pure} def stdin: () -> String?
|
|
176
|
+
%a{pure} def stdout: () -> String?
|
|
177
|
+
%a{pure} def stderr: () -> String?
|
|
178
|
+
%a{pure} def supplementary_groups: () -> Array[String]?
|
|
179
|
+
def daemonize?: () -> bool
|
|
180
|
+
def monitor_children?: () -> bool
|
|
181
|
+
def actual_pid: () -> Integer?
|
|
182
|
+
def clean_threads: () -> void
|
|
183
|
+
def signal_process: (Integer | String | Symbol code) -> bool
|
|
184
|
+
def unlink_pid: () -> void
|
|
185
|
+
def skip_ticks_for: (seconds seconds) -> Integer
|
|
186
|
+
def dispatch!: (Symbol | String event, ?String? reason) -> bool
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
@threads: Array[Thread]
|
|
190
|
+
|
|
191
|
+
def start_process: () -> Integer
|
|
192
|
+
|
|
193
|
+
def pre_start_process: () -> void
|
|
194
|
+
|
|
195
|
+
def stop_process: () -> Integer
|
|
196
|
+
|
|
197
|
+
def restart_process: () -> Integer
|
|
198
|
+
|
|
199
|
+
def cleanup_process: () -> void
|
|
200
|
+
|
|
201
|
+
def prepare_command: (String command) -> String
|
|
202
|
+
|
|
203
|
+
def system_command_options: () -> System::command_options
|
|
204
|
+
|
|
205
|
+
# Runs the block, and dispatches next_state if it takes longer than secs
|
|
206
|
+
def with_timeout: [T] (seconds secs, String | Symbol next_state) { (Numeric secs) -> T } -> (T | bool)
|
|
207
|
+
|
|
208
|
+
def run_command: (String cmd, String description) -> void
|
|
209
|
+
|
|
210
|
+
def send_stop_signals: (Array[Symbol | Integer] signals) -> void
|
|
211
|
+
|
|
212
|
+
def wait_for_stop: (seconds seconds) -> void
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# What state_machine passes to transition callbacks and triggers
|
|
216
|
+
interface _Transition
|
|
217
|
+
def from_name: () -> Symbol?
|
|
218
|
+
|
|
219
|
+
def to_name: () -> Symbol?
|
|
220
|
+
|
|
221
|
+
def loopback?: () -> bool
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# A monitored process
|
|
225
|
+
class Process
|
|
226
|
+
include ProcessCommands
|
|
227
|
+
|
|
228
|
+
CONFIGURABLE_ATTRIBUTES: Array[Symbol]
|
|
229
|
+
|
|
230
|
+
attr_accessor name: String
|
|
231
|
+
attr_accessor watches: Array[ConditionWatch]
|
|
232
|
+
attr_accessor triggers: Array[Trigger]
|
|
233
|
+
attr_accessor skip_ticks_until: Integer?
|
|
234
|
+
attr_accessor process_running: bool?
|
|
235
|
+
|
|
236
|
+
# CONFIGURABLE_ATTRIBUTES
|
|
237
|
+
attr_accessor pre_start_command: String?
|
|
238
|
+
attr_accessor start_command: String?
|
|
239
|
+
attr_accessor stop_command: String?
|
|
240
|
+
attr_accessor restart_command: String?
|
|
241
|
+
attr_accessor stdout: String?
|
|
242
|
+
attr_accessor stderr: String?
|
|
243
|
+
attr_accessor stdin: String?
|
|
244
|
+
attr_accessor daemonize: bool?
|
|
245
|
+
attr_accessor pid_file: String?
|
|
246
|
+
attr_accessor working_dir: String?
|
|
247
|
+
attr_accessor environment: Hash[_ToS, _ToS]?
|
|
248
|
+
attr_accessor start_grace_time: seconds
|
|
249
|
+
attr_accessor stop_grace_time: seconds
|
|
250
|
+
attr_accessor restart_grace_time: seconds
|
|
251
|
+
attr_accessor uid: String?
|
|
252
|
+
attr_accessor gid: String?
|
|
253
|
+
attr_accessor cache_actual_pid: bool
|
|
254
|
+
attr_accessor monitor_children: bool
|
|
255
|
+
attr_accessor child_process_factory: ProcessFactory?
|
|
256
|
+
attr_accessor pid_command: String?
|
|
257
|
+
attr_accessor auto_start: bool?
|
|
258
|
+
attr_accessor supplementary_groups: Array[String]?
|
|
259
|
+
# Signal names alternating with delays in seconds
|
|
260
|
+
attr_accessor stop_signals: Array[Symbol | Integer]?
|
|
261
|
+
attr_accessor on_start_timeout: String | Symbol
|
|
262
|
+
attr_accessor group_start_noblock: bool
|
|
263
|
+
attr_accessor group_restart_noblock: bool
|
|
264
|
+
attr_accessor group_stop_noblock: bool
|
|
265
|
+
attr_accessor group_unmonitor_noblock: bool
|
|
266
|
+
|
|
267
|
+
attr_reader children: Array[Process]
|
|
268
|
+
attr_reader statistics: ProcessStatistics
|
|
269
|
+
attr_reader logger: Logger
|
|
270
|
+
|
|
271
|
+
@event_mutex: Monitor
|
|
272
|
+
@threads: Array[Thread]
|
|
273
|
+
@actual_pid: Integer?
|
|
274
|
+
@transitioned: bool
|
|
275
|
+
|
|
276
|
+
# Methods that state_machine defines, in a module it includes
|
|
277
|
+
interface _StateMachine
|
|
278
|
+
def state: () -> String
|
|
279
|
+
|
|
280
|
+
def state=: (String state) -> String
|
|
281
|
+
|
|
282
|
+
def state_name: () -> Symbol
|
|
283
|
+
|
|
284
|
+
def human_state_name: () -> String
|
|
285
|
+
|
|
286
|
+
def state?: (Symbol state) -> bool
|
|
287
|
+
|
|
288
|
+
def unmonitored?: () -> bool
|
|
289
|
+
|
|
290
|
+
def up?: () -> bool
|
|
291
|
+
|
|
292
|
+
def down?: () -> bool
|
|
293
|
+
|
|
294
|
+
def starting?: () -> bool
|
|
295
|
+
|
|
296
|
+
def stopping?: () -> bool
|
|
297
|
+
|
|
298
|
+
def restarting?: () -> bool
|
|
299
|
+
|
|
300
|
+
def state_events: () -> Array[Symbol]
|
|
301
|
+
|
|
302
|
+
def state_transitions: () -> Array[_Transition]
|
|
303
|
+
|
|
304
|
+
def fire_state_event: (Symbol | String event) -> bool
|
|
305
|
+
|
|
306
|
+
def can_tick?: () -> bool
|
|
307
|
+
|
|
308
|
+
def tick_transition: () -> _Transition?
|
|
309
|
+
|
|
310
|
+
def tick!: () -> true
|
|
311
|
+
|
|
312
|
+
def start: () -> bool
|
|
313
|
+
|
|
314
|
+
def can_start?: () -> bool
|
|
315
|
+
|
|
316
|
+
def start_transition: () -> _Transition?
|
|
317
|
+
|
|
318
|
+
def start!: () -> true
|
|
319
|
+
|
|
320
|
+
def stop: () -> bool
|
|
321
|
+
|
|
322
|
+
def can_stop?: () -> bool
|
|
323
|
+
|
|
324
|
+
def stop_transition: () -> _Transition?
|
|
325
|
+
|
|
326
|
+
def stop!: () -> true
|
|
327
|
+
|
|
328
|
+
def unmonitor: () -> bool
|
|
329
|
+
|
|
330
|
+
def can_unmonitor?: () -> bool
|
|
331
|
+
|
|
332
|
+
def unmonitor_transition: () -> _Transition?
|
|
333
|
+
|
|
334
|
+
def unmonitor!: () -> true
|
|
335
|
+
|
|
336
|
+
def restart: () -> bool
|
|
337
|
+
|
|
338
|
+
def can_restart?: () -> bool
|
|
339
|
+
|
|
340
|
+
def restart_transition: () -> _Transition?
|
|
341
|
+
|
|
342
|
+
def restart!: () -> true
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
include _StateMachine
|
|
346
|
+
|
|
347
|
+
# Methods bluepill defines
|
|
348
|
+
|
|
349
|
+
def initialize: (String process_name, Hash[Symbol, Hash[Symbol, untyped]] checks, ?Hash[Symbol, untyped] options) -> void
|
|
350
|
+
|
|
351
|
+
# Runs the state machine's tick event, then the watches and children
|
|
352
|
+
def tick: () -> void
|
|
353
|
+
|
|
354
|
+
def logger=: (Logger logger) -> void
|
|
355
|
+
|
|
356
|
+
def dispatch!: (Symbol | String event, ?String? reason) -> bool
|
|
357
|
+
|
|
358
|
+
def record_transition: (_Transition transition) -> void
|
|
359
|
+
|
|
360
|
+
def notify_triggers: (_Transition transition) -> void
|
|
361
|
+
|
|
362
|
+
def add_watch: (Symbol name, Hash[Symbol, untyped] options) -> Array[ConditionWatch]
|
|
363
|
+
|
|
364
|
+
def add_trigger: (Symbol name, Hash[Symbol, untyped] options) -> Array[Trigger]
|
|
365
|
+
|
|
366
|
+
def run_watches: () -> void
|
|
367
|
+
|
|
368
|
+
def determine_initial_state: () -> void
|
|
369
|
+
|
|
370
|
+
def handle_user_command: (String cmd) -> void
|
|
371
|
+
|
|
372
|
+
def process_running?: (?bool force) -> bool
|
|
373
|
+
|
|
374
|
+
def clean_threads: () -> void
|
|
375
|
+
|
|
376
|
+
def daemonize?: () -> bool
|
|
377
|
+
|
|
378
|
+
def monitor_children?: () -> bool
|
|
379
|
+
|
|
380
|
+
def signal_process: (Integer | String | Symbol code) -> bool
|
|
381
|
+
|
|
382
|
+
def cache_actual_pid?: () -> bool
|
|
383
|
+
|
|
384
|
+
def actual_pid: () -> Integer?
|
|
385
|
+
|
|
386
|
+
def pid_from_file: () -> Integer?
|
|
387
|
+
|
|
388
|
+
def pid_from_command: () -> Integer?
|
|
389
|
+
|
|
390
|
+
def positive_pid: (Integer pid) -> Integer?
|
|
391
|
+
|
|
392
|
+
def actual_pid=: (Integer pid) -> void
|
|
393
|
+
|
|
394
|
+
def clear_pid: () -> nil
|
|
395
|
+
|
|
396
|
+
def unlink_pid: () -> void
|
|
397
|
+
|
|
398
|
+
def skip_ticks_for: (seconds seconds) -> Integer
|
|
399
|
+
|
|
400
|
+
def skipping_ticks?: () -> bool?
|
|
401
|
+
|
|
402
|
+
def refresh_children!: () -> void
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# Keeps a short history of a process's events
|
|
406
|
+
class ProcessStatistics
|
|
407
|
+
type event = [Symbol | String, String?, Time]
|
|
408
|
+
|
|
409
|
+
STRFTIME: String
|
|
410
|
+
EVENTS_TO_PERSIST: Integer
|
|
411
|
+
|
|
412
|
+
attr_reader events: Util::RotationalArray[event]
|
|
413
|
+
|
|
414
|
+
def initialize: () -> void
|
|
415
|
+
|
|
416
|
+
def record_event: (Symbol | String event, String? reason) -> Util::RotationalArray[event]
|
|
417
|
+
|
|
418
|
+
def to_s: () -> String
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# Processes that share a group name. Processes without one are in the
|
|
422
|
+
# group named nil.
|
|
423
|
+
class Group
|
|
424
|
+
attr_accessor name: String?
|
|
425
|
+
attr_accessor processes: Array[Process]
|
|
426
|
+
attr_accessor logger: Logger
|
|
427
|
+
# Not used by bluepill
|
|
428
|
+
attr_accessor process_logger: untyped
|
|
429
|
+
|
|
430
|
+
def initialize: (String? name, ?{ ?logger: Logger } options) -> void
|
|
431
|
+
|
|
432
|
+
def add_process: (Process process) -> Array[Process]
|
|
433
|
+
|
|
434
|
+
def tick: () -> void
|
|
435
|
+
|
|
436
|
+
def determine_initial_state: () -> void
|
|
437
|
+
|
|
438
|
+
# Proxied events, which return the affected processes as group:process
|
|
439
|
+
|
|
440
|
+
def start: (?String? process_name) -> Array[String]
|
|
441
|
+
|
|
442
|
+
def unmonitor: (?String? process_name) -> Array[String]
|
|
443
|
+
|
|
444
|
+
def stop: (?String? process_name) -> Array[String]
|
|
445
|
+
|
|
446
|
+
def restart: (?String? process_name) -> Array[String]
|
|
447
|
+
|
|
448
|
+
def status: (?String? process_name) -> Array[String]
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# Logs to a file or syslog, with nested prefixes
|
|
452
|
+
class Logger
|
|
453
|
+
# Where a Logger without a parent logger writes: a LoggerAdapter, or
|
|
454
|
+
# Syslog
|
|
455
|
+
interface _Backend
|
|
456
|
+
def emerg: (String message) -> void
|
|
457
|
+
|
|
458
|
+
def alert: (String message) -> void
|
|
459
|
+
|
|
460
|
+
def crit: (String message) -> void
|
|
461
|
+
|
|
462
|
+
def err: (String message) -> void
|
|
463
|
+
|
|
464
|
+
def warning: (String message) -> void
|
|
465
|
+
|
|
466
|
+
def notice: (String message) -> void
|
|
467
|
+
|
|
468
|
+
def info: (String message) -> void
|
|
469
|
+
|
|
470
|
+
def debug: (String message) -> void
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
type options = {
|
|
474
|
+
?logger: Logger,
|
|
475
|
+
?prefix: String?,
|
|
476
|
+
?stdout: bool,
|
|
477
|
+
?log_file: String?,
|
|
478
|
+
?identity: String
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
LOG_METHODS: Array[Symbol]
|
|
482
|
+
|
|
483
|
+
# A ::Logger that also answers to the syslog method names
|
|
484
|
+
class LoggerAdapter < ::Logger
|
|
485
|
+
LOGGER_EQUIVALENTS: Hash[Symbol, Symbol]
|
|
486
|
+
|
|
487
|
+
alias err error
|
|
488
|
+
|
|
489
|
+
alias warning warn
|
|
490
|
+
|
|
491
|
+
alias emerg fatal
|
|
492
|
+
|
|
493
|
+
alias alert warn
|
|
494
|
+
|
|
495
|
+
alias crit fatal
|
|
496
|
+
|
|
497
|
+
alias notice info
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
@options: options
|
|
501
|
+
@logger: Logger | (Object & _Backend)
|
|
502
|
+
@prefix: String?
|
|
503
|
+
@stdout: bool?
|
|
504
|
+
@prefixes: Hash[String?, Logger]
|
|
505
|
+
|
|
506
|
+
def initialize: (?options options) -> void
|
|
507
|
+
|
|
508
|
+
def emerg: (_ToS msg, ?Array[String?] prefix) -> void
|
|
509
|
+
|
|
510
|
+
def alert: (_ToS msg, ?Array[String?] prefix) -> void
|
|
511
|
+
|
|
512
|
+
def crit: (_ToS msg, ?Array[String?] prefix) -> void
|
|
513
|
+
|
|
514
|
+
def err: (_ToS msg, ?Array[String?] prefix) -> void
|
|
515
|
+
|
|
516
|
+
def warning: (_ToS msg, ?Array[String?] prefix) -> void
|
|
517
|
+
|
|
518
|
+
def notice: (_ToS msg, ?Array[String?] prefix) -> void
|
|
519
|
+
|
|
520
|
+
def info: (_ToS msg, ?Array[String?] prefix) -> void
|
|
521
|
+
|
|
522
|
+
def debug: (_ToS msg, ?Array[String?] prefix) -> void
|
|
523
|
+
|
|
524
|
+
def prefix_with: (String? prefix) -> Logger
|
|
525
|
+
|
|
526
|
+
def reopen: () -> void
|
|
527
|
+
|
|
528
|
+
private
|
|
529
|
+
|
|
530
|
+
def create_logger: () -> (Object & _Backend)
|
|
531
|
+
|
|
532
|
+
def require_syslog: () -> bool
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
# A value a ConditionWatch got, and whether it passed the check
|
|
536
|
+
class HistoryValue < Struct[_ToS]
|
|
537
|
+
attr_accessor value: _ToS
|
|
538
|
+
attr_accessor critical: bool
|
|
539
|
+
|
|
540
|
+
def self.new: (_ToS value, bool critical) -> HistoryValue
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# Runs a process condition every so often, and fires its events when the
|
|
544
|
+
# check fails often enough
|
|
545
|
+
class ConditionWatch
|
|
546
|
+
EMPTY_ARRAY: Array[Symbol]
|
|
547
|
+
|
|
548
|
+
attr_accessor logger: Logger
|
|
549
|
+
attr_accessor name: Symbol
|
|
550
|
+
|
|
551
|
+
@fires: Array[Symbol]
|
|
552
|
+
@every: Integer
|
|
553
|
+
@times: Array[Integer]
|
|
554
|
+
@include_children: bool
|
|
555
|
+
@history: Util::RotationalArray[HistoryValue]
|
|
556
|
+
@process_condition: ProcessConditions::ProcessCondition[untyped]
|
|
557
|
+
@last_ran_at: Integer?
|
|
558
|
+
|
|
559
|
+
def initialize: (Symbol name, ?Hash[Symbol, untyped] options) -> void
|
|
560
|
+
|
|
561
|
+
# Returns the events to fire
|
|
562
|
+
def run: (Integer? pid, ?Integer tick_number) -> Array[Symbol]
|
|
563
|
+
|
|
564
|
+
def clear_history!: () -> Util::RotationalArray[HistoryValue]
|
|
565
|
+
|
|
566
|
+
def fired?: () -> bool
|
|
567
|
+
|
|
568
|
+
def to_s: () -> String
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
# Reacts to a process's state transitions. Subclasses register themselves
|
|
572
|
+
# by their underscored name.
|
|
573
|
+
class Trigger
|
|
574
|
+
self.@implementations: Hash[Symbol, singleton(Trigger)]
|
|
575
|
+
|
|
576
|
+
def self.inherited: (singleton(Trigger) klass) -> void
|
|
577
|
+
|
|
578
|
+
def self.[]: %a{implicitly-returns-nil} (Symbol name) -> singleton(Trigger)
|
|
579
|
+
|
|
580
|
+
attr_accessor process: Process
|
|
581
|
+
attr_accessor logger: Logger
|
|
582
|
+
attr_accessor mutex: Thread::Mutex
|
|
583
|
+
attr_accessor scheduled_events: Array[[Symbol, Thread]]
|
|
584
|
+
|
|
585
|
+
def initialize: (Process process, ?Hash[Symbol, untyped] options) -> void
|
|
586
|
+
|
|
587
|
+
def reset!: () -> void
|
|
588
|
+
|
|
589
|
+
def notify: (_Transition transition) -> void
|
|
590
|
+
|
|
591
|
+
def dispatch!: (Symbol event) -> bool
|
|
592
|
+
|
|
593
|
+
def schedule_event: (Symbol event, seconds delay) -> Thread
|
|
594
|
+
|
|
595
|
+
def cancel_all_events: () -> void
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
module Triggers
|
|
599
|
+
# Unmonitors a process that starts or restarts too often, and starts it
|
|
600
|
+
# again later
|
|
601
|
+
class Flapping < Trigger
|
|
602
|
+
TRIGGER_STATES: Array[Symbol]
|
|
603
|
+
PARAMS: Array[Symbol]
|
|
604
|
+
|
|
605
|
+
attr_accessor times: Integer
|
|
606
|
+
attr_accessor within: Integer
|
|
607
|
+
attr_accessor retry_in: Integer
|
|
608
|
+
attr_reader timeline: Util::RotationalArray[Integer]
|
|
609
|
+
|
|
610
|
+
def initialize: (Process process, ?Hash[Symbol, untyped] options) -> void
|
|
611
|
+
|
|
612
|
+
def notify: (_Transition transition) -> void
|
|
613
|
+
|
|
614
|
+
def reset!: () -> void
|
|
615
|
+
|
|
616
|
+
def check_flapping: () -> void
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
# self in an application block
|
|
621
|
+
class AppProxy
|
|
622
|
+
APP_ATTRIBUTES: Array[Symbol]
|
|
623
|
+
|
|
624
|
+
attr_accessor working_dir: String?
|
|
625
|
+
attr_accessor uid: String?
|
|
626
|
+
attr_accessor gid: String?
|
|
627
|
+
attr_accessor environment: Hash[_ToS, _ToS]?
|
|
628
|
+
attr_accessor auto_start: bool?
|
|
629
|
+
attr_reader app: Application
|
|
630
|
+
|
|
631
|
+
def initialize: (_ToS app_name, Hash[Symbol, untyped] options) -> void
|
|
632
|
+
|
|
633
|
+
# The block runs with self set to the ProcessProxy, and also gets it as
|
|
634
|
+
# an argument
|
|
635
|
+
def process: (String process_name) { (ProcessProxy process) [self: ProcessProxy] -> void } -> Array[Process]
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
# Builds processes from a process block in the config
|
|
639
|
+
class ProcessFactory
|
|
640
|
+
@@process_keys: Hash[Array[String?], nil]
|
|
641
|
+
@@pid_files: Hash[String, nil]
|
|
642
|
+
|
|
643
|
+
@process_block: process_block
|
|
644
|
+
|
|
645
|
+
attr_reader attributes: Hash[Symbol, untyped]
|
|
646
|
+
|
|
647
|
+
def initialize: (Hash[Symbol, untyped] attributes, process_block process_block) -> void
|
|
648
|
+
|
|
649
|
+
def create_process: (String name, String pids_dir) -> Process
|
|
650
|
+
|
|
651
|
+
def create_child_process: (String name, Integer pid, Logger logger) -> Process
|
|
652
|
+
|
|
653
|
+
private
|
|
654
|
+
|
|
655
|
+
def assign_default_pid_file: (String process_name, String pids_dir) -> String?
|
|
656
|
+
|
|
657
|
+
def validate_process!: (ProcessProxy process) -> void
|
|
658
|
+
|
|
659
|
+
def validate_unique_process!: (ProcessProxy process) -> void
|
|
660
|
+
|
|
661
|
+
def validate_required_attributes!: (ProcessProxy process) -> void
|
|
662
|
+
|
|
663
|
+
def validate_unique_pid_file!: () -> void
|
|
664
|
+
|
|
665
|
+
def validate_stop_signals!: () -> void
|
|
666
|
+
|
|
667
|
+
def warn_unless_pid_known: (ProcessProxy process, String? default_pid_file) -> void
|
|
668
|
+
|
|
669
|
+
def config_error!: (String message) -> bot
|
|
670
|
+
|
|
671
|
+
def validate_child_process!: (ProcessProxy child) -> void
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
# self in a process block. Any attribute can be set with `name = value`,
|
|
675
|
+
# `name value` or `name!` (for true), and read back with `name`.
|
|
676
|
+
class ProcessProxy
|
|
677
|
+
attr_reader attributes: Hash[Symbol, untyped]
|
|
678
|
+
attr_reader watches: Hash[Symbol, Hash[Symbol, untyped]]
|
|
679
|
+
attr_reader name: String
|
|
680
|
+
|
|
681
|
+
def initialize: (String process_name, Hash[Symbol, untyped] attributes, process_block process_block) -> void
|
|
682
|
+
|
|
683
|
+
# Sets or reads an attribute
|
|
684
|
+
def method_missing: (Symbol name, *untyped args) -> untyped
|
|
685
|
+
|
|
686
|
+
def respond_to_missing?: (Symbol name, bool? include_private) -> bool
|
|
687
|
+
|
|
688
|
+
def checks: (Symbol name, ?Hash[Symbol, untyped] options) -> Hash[Symbol, untyped]
|
|
689
|
+
|
|
690
|
+
# The block configures the children like a process block
|
|
691
|
+
def monitor_children: () ?{ (ProcessProxy child) [self: ProcessProxy] -> void } -> process_block?
|
|
692
|
+
|
|
693
|
+
def to_process: () -> Process
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
# Memoizes ps output for a tick, and runs commands as daemons or until
|
|
697
|
+
# they finish
|
|
698
|
+
module System
|
|
699
|
+
# Options for daemonize and execute_blocking
|
|
700
|
+
type command_options = {
|
|
701
|
+
?uid: String?,
|
|
702
|
+
?gid: String?,
|
|
703
|
+
?working_dir: String?,
|
|
704
|
+
?environment: Hash[_ToS, _ToS]?,
|
|
705
|
+
?pid_file: String?,
|
|
706
|
+
?logger: Logger,
|
|
707
|
+
?stdin: String?,
|
|
708
|
+
?stdout: String?,
|
|
709
|
+
?stderr: String?,
|
|
710
|
+
?supplementary_groups: Array[String]?
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
type blocking_result = { exit_code: Integer?, stdout: String, stderr: String }
|
|
714
|
+
|
|
715
|
+
type grandchild_pipes = { out: [IO, IO], err: [IO, IO], result: IO }
|
|
716
|
+
|
|
717
|
+
APPEND_MODE: String
|
|
718
|
+
|
|
719
|
+
IDX_MAP: { pid: Integer, ppid: Integer, pcpu: Integer, rss: Integer, etime: Integer, command: Integer }
|
|
720
|
+
|
|
721
|
+
PS_MUTEX: Thread::Mutex
|
|
722
|
+
|
|
723
|
+
# The store lives on the module. Steep checks the module functions as
|
|
724
|
+
# instance methods, so it needs the instance variable too.
|
|
725
|
+
self.@store: Hash[Symbol, ps_table | String]
|
|
726
|
+
@store: Hash[Symbol, ps_table | String]
|
|
727
|
+
|
|
728
|
+
# Raised when ps could not be run, so there is no data for this tick
|
|
729
|
+
class PsError < StandardError
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
def self.pid_alive?: (Integer pid) -> bool
|
|
733
|
+
|
|
734
|
+
def self.cpu_usage: (Integer? pid, boolish include_children) -> Float?
|
|
735
|
+
|
|
736
|
+
def self.memory_usage: (Integer? pid, boolish include_children) -> Float?
|
|
737
|
+
|
|
738
|
+
def self.running_time: (Integer? pid) -> Integer?
|
|
739
|
+
|
|
740
|
+
def self.command: (Integer? pid) -> String?
|
|
741
|
+
|
|
742
|
+
def self.get_children: (Integer? parent_pid) -> Array[Integer]
|
|
743
|
+
|
|
744
|
+
def self.delete_if_exists: (String? filename) -> void
|
|
745
|
+
|
|
746
|
+
def self.store: () -> Hash[Symbol, ps_table | String]
|
|
747
|
+
|
|
748
|
+
def self.reset_data: () -> void
|
|
749
|
+
|
|
750
|
+
def self.ps_axu: () -> ps_table
|
|
751
|
+
|
|
752
|
+
def self.parse_elapsed_time: (String? str) -> Integer
|
|
753
|
+
|
|
754
|
+
# Returns the daemon's pid
|
|
755
|
+
def self.daemonize: (String cmd, ?command_options options) -> Integer?
|
|
756
|
+
|
|
757
|
+
def self.execute_blocking: (String cmd, ?command_options options) -> blocking_result
|
|
758
|
+
|
|
759
|
+
def self.run_blocking_child: (String cmd, command_options options, IO result_pipe) -> bot
|
|
760
|
+
|
|
761
|
+
def self.exec_blocking_grandchild: (String cmd, command_options options, grandchild_pipes pipes) -> bot
|
|
762
|
+
|
|
763
|
+
def self.setup_command_environment: (command_options options) -> void
|
|
764
|
+
|
|
765
|
+
def self.drop_privileges: (String? uid, String? gid, Array[String]? supplementary_groups) -> void
|
|
766
|
+
|
|
767
|
+
def self.can_write_pid_file: (String pid_file, Logger logger) -> bool
|
|
768
|
+
|
|
769
|
+
def self.redirect_io: (String? io_in, String? io_out, String? io_err) -> void
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
# Remembers the pids and process groups of the processes bluepill starts,
|
|
773
|
+
# so it can kill any left over from an earlier run
|
|
774
|
+
module ProcessJournal
|
|
775
|
+
self.@logger: Logger
|
|
776
|
+
self.@journal_base_dir: String
|
|
777
|
+
|
|
778
|
+
def self.logger: () -> Logger
|
|
779
|
+
|
|
780
|
+
def self.journal_base_dir: () -> String
|
|
781
|
+
|
|
782
|
+
# Only the first logger is kept
|
|
783
|
+
def self.logger=: (Logger new_logger) -> Logger
|
|
784
|
+
|
|
785
|
+
def self.base_dir=: (String base_dir) -> void
|
|
786
|
+
|
|
787
|
+
def self.skip_pid?: (Integer? pid) -> bool
|
|
788
|
+
|
|
789
|
+
def self.skip_pgid?: (Integer? pgid) -> bool
|
|
790
|
+
|
|
791
|
+
def self.acquire_atomic_fs_lock: [T] (String name) { () -> T } -> T
|
|
792
|
+
|
|
793
|
+
def self.clear_all_atomic_fs_locks: () -> void
|
|
794
|
+
|
|
795
|
+
def self.pid_journal_filename: (String journal_name) -> String
|
|
796
|
+
|
|
797
|
+
def self.pgid_journal_filename: (String journal_name) -> String
|
|
798
|
+
|
|
799
|
+
def self.pid_journal: (String filename) -> Array[Integer]
|
|
800
|
+
|
|
801
|
+
def self.pgid_journal: (String filename) -> Array[Integer]
|
|
802
|
+
|
|
803
|
+
def self.clear_atomic_fs_lock: (String name) -> void
|
|
804
|
+
|
|
805
|
+
def self.kill_all_from_all_journals: () -> void
|
|
806
|
+
|
|
807
|
+
def self.kill_all_from_journal: (String journal_name) -> void
|
|
808
|
+
|
|
809
|
+
def self.kill_all_pgids_from_journal: (String journal_name) -> void
|
|
810
|
+
|
|
811
|
+
def self.kill_all_pids_from_journal: (String journal_name) -> void
|
|
812
|
+
|
|
813
|
+
def self.kill_all_from_journal_file: (String filename, Array[Integer] ids, String label, Integer sign) -> void
|
|
814
|
+
|
|
815
|
+
def self.signal_journaled: (String signal, Array[Integer] ids, String label, Integer sign) -> void
|
|
816
|
+
|
|
817
|
+
def self.append_pgid_to_journal: (String journal_name, Integer? pgid) -> void
|
|
818
|
+
|
|
819
|
+
def self.append_pid_to_journal: (String journal_name, Integer? pid) -> void
|
|
820
|
+
end
|
|
821
|
+
|
|
822
|
+
module ProcessConditions
|
|
823
|
+
def self.[]: (Symbol | String name) -> singleton(ProcessCondition)
|
|
824
|
+
|
|
825
|
+
# Checks something about a process. V is the type of value that run
|
|
826
|
+
# returns and check tests.
|
|
827
|
+
class ProcessCondition[V]
|
|
828
|
+
@options: Hash[Symbol, untyped]
|
|
829
|
+
|
|
830
|
+
def initialize: (?Hash[Symbol, untyped] options) -> void
|
|
831
|
+
|
|
832
|
+
def run: (Integer? pid, boolish include_children) -> V
|
|
833
|
+
|
|
834
|
+
# Whether the value is fine
|
|
835
|
+
def check: (V value) -> bool
|
|
836
|
+
|
|
837
|
+
def format_value: (V value) -> V
|
|
838
|
+
end
|
|
839
|
+
|
|
840
|
+
class AlwaysTrue < ProcessCondition[Integer]
|
|
841
|
+
def run: (Integer? pid, boolish include_children) -> Integer
|
|
842
|
+
|
|
843
|
+
def check: (Integer value) -> true
|
|
844
|
+
end
|
|
845
|
+
|
|
846
|
+
class CpuUsage < ProcessCondition[Float]
|
|
847
|
+
@below: Numeric
|
|
848
|
+
|
|
849
|
+
def run: (Integer? pid, boolish include_children) -> Float
|
|
850
|
+
|
|
851
|
+
def check: (Float value) -> bool
|
|
852
|
+
end
|
|
853
|
+
|
|
854
|
+
# Seconds since the file changed, or the error that stopped bluepill
|
|
855
|
+
# from finding out
|
|
856
|
+
class FileTime < ProcessCondition[Float | StandardError | nil]
|
|
857
|
+
@below: Numeric
|
|
858
|
+
@filename: String
|
|
859
|
+
|
|
860
|
+
def run: (Integer? pid, boolish include_children) -> (Float | StandardError | nil)
|
|
861
|
+
|
|
862
|
+
def check: (Float | StandardError | nil value) -> bool
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
class Http < ProcessCondition[Net::HTTPResponse | StandardError]
|
|
866
|
+
@uri: URI::HTTP
|
|
867
|
+
@kind: Class
|
|
868
|
+
@pattern: Regexp?
|
|
869
|
+
@open_timeout: Integer
|
|
870
|
+
@read_timeout: Integer
|
|
871
|
+
|
|
872
|
+
def run: (Integer? pid, boolish include_children) -> (Net::HTTPResponse | StandardError)
|
|
873
|
+
|
|
874
|
+
def check: (Net::HTTPResponse | StandardError value) -> bool
|
|
875
|
+
|
|
876
|
+
private
|
|
877
|
+
|
|
878
|
+
def hide_net_http_bug: [T] () { () -> T } -> T
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
# Memory usage in kilobytes
|
|
882
|
+
class MemUsage < ProcessCondition[Float]
|
|
883
|
+
MB: Integer
|
|
884
|
+
FORMAT_STR: String
|
|
885
|
+
MB_LABEL: String
|
|
886
|
+
KB_LABEL: String
|
|
887
|
+
|
|
888
|
+
@below: Numeric
|
|
889
|
+
|
|
890
|
+
def run: (Integer? pid, boolish include_children) -> Float
|
|
891
|
+
|
|
892
|
+
def check: (Float value) -> bool
|
|
893
|
+
|
|
894
|
+
def format_value: (Float value) -> String
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
# Seconds since the process started
|
|
898
|
+
class RunningTime < ProcessCondition[Integer]
|
|
899
|
+
@below: Numeric
|
|
900
|
+
|
|
901
|
+
def run: (Integer? pid, boolish include_children) -> Integer
|
|
902
|
+
|
|
903
|
+
def check: (Integer value) -> bool
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
# The process's command line, which says <defunct> for a zombie
|
|
907
|
+
class ZombieProcess < ProcessCondition[String?]
|
|
908
|
+
def run: (Integer? pid, boolish include_children) -> String?
|
|
909
|
+
|
|
910
|
+
def check: (String? value) -> bool
|
|
911
|
+
end
|
|
912
|
+
end
|
|
913
|
+
|
|
914
|
+
module Util
|
|
915
|
+
# An array that drops its oldest element when it grows past its capacity
|
|
916
|
+
class RotationalArray[Elem] < Array[Elem]
|
|
917
|
+
@capacity: Integer
|
|
918
|
+
|
|
919
|
+
def initialize: (Integer size) -> void
|
|
920
|
+
|
|
921
|
+
def push: (Elem value) -> self
|
|
922
|
+
|
|
923
|
+
alias << push
|
|
924
|
+
end
|
|
925
|
+
end
|
|
926
|
+
end
|