dylanvaughn-bluepill 0.0.39
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/DESIGN.md +10 -0
- data/LICENSE +22 -0
- data/README.md +228 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/bin/bluepill +103 -0
- data/bin/bpsv +3 -0
- data/bluepill.gemspec +84 -0
- data/lib/bluepill.rb +32 -0
- data/lib/bluepill/application.rb +200 -0
- data/lib/bluepill/application/client.rb +7 -0
- data/lib/bluepill/application/server.rb +24 -0
- data/lib/bluepill/condition_watch.rb +55 -0
- data/lib/bluepill/controller.rb +119 -0
- data/lib/bluepill/dsl.rb +150 -0
- data/lib/bluepill/group.rb +71 -0
- data/lib/bluepill/logger.rb +62 -0
- data/lib/bluepill/process.rb +419 -0
- data/lib/bluepill/process_conditions.rb +13 -0
- data/lib/bluepill/process_conditions/always_true.rb +17 -0
- data/lib/bluepill/process_conditions/cpu_usage.rb +18 -0
- data/lib/bluepill/process_conditions/http.rb +52 -0
- data/lib/bluepill/process_conditions/mem_usage.rb +31 -0
- data/lib/bluepill/process_conditions/process_condition.rb +21 -0
- data/lib/bluepill/process_statistics.rb +24 -0
- data/lib/bluepill/socket.rb +47 -0
- data/lib/bluepill/system.rb +227 -0
- data/lib/bluepill/trigger.rb +60 -0
- data/lib/bluepill/triggers/flapping.rb +59 -0
- data/lib/bluepill/util/rotational_array.rb +66 -0
- data/lib/bluepill/version.rb +3 -0
- data/lib/example.rb +81 -0
- data/lib/runit_example.rb +25 -0
- metadata +167 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
module Bluepill
|
2
|
+
class Trigger
|
3
|
+
@implementations = {}
|
4
|
+
def self.inherited(klass)
|
5
|
+
@implementations[klass.name.split('::').last.underscore.to_sym] = klass
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.[](name)
|
9
|
+
@implementations[name]
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :process, :logger, :mutex, :scheduled_events
|
13
|
+
|
14
|
+
def initialize(process, options = {})
|
15
|
+
self.process = process
|
16
|
+
self.logger = options[:logger]
|
17
|
+
self.mutex = Mutex.new
|
18
|
+
self.scheduled_events = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def reset!
|
22
|
+
self.cancel_all_events
|
23
|
+
end
|
24
|
+
|
25
|
+
def notify(transition)
|
26
|
+
raise "Implement in subclass"
|
27
|
+
end
|
28
|
+
|
29
|
+
def dispatch!(event)
|
30
|
+
self.process.dispatch!(event, self.class.name.split("::").last)
|
31
|
+
end
|
32
|
+
|
33
|
+
def schedule_event(event, delay)
|
34
|
+
# TODO: maybe wrap this in a ScheduledEvent class with methods like cancel
|
35
|
+
thread = Thread.new(self) do |trigger|
|
36
|
+
begin
|
37
|
+
sleep delay.to_f
|
38
|
+
trigger.logger.info("Retrying from flapping")
|
39
|
+
trigger.dispatch!(event)
|
40
|
+
trigger.mutex.synchronize do
|
41
|
+
trigger.scheduled_events.delete_if { |_, thread| thread == Thread.current }
|
42
|
+
end
|
43
|
+
rescue StandardError => e
|
44
|
+
trigger.logger.err(e)
|
45
|
+
trigger.logger.err(e.backtrace.join("\n"))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
self.scheduled_events.push([event, thread])
|
50
|
+
end
|
51
|
+
|
52
|
+
def cancel_all_events
|
53
|
+
self.logger.info "Canceling all scheduled events"
|
54
|
+
self.mutex.synchronize do
|
55
|
+
self.scheduled_events.each {|_, thread| thread.kill}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Bluepill
|
2
|
+
module Triggers
|
3
|
+
class Flapping < Bluepill::Trigger
|
4
|
+
TRIGGER_STATES = [:starting, :restarting]
|
5
|
+
|
6
|
+
PARAMS = [:times, :within, :retry_in]
|
7
|
+
|
8
|
+
attr_accessor *PARAMS
|
9
|
+
attr_reader :timeline
|
10
|
+
|
11
|
+
def initialize(process, options = {})
|
12
|
+
options.reverse_merge!(:times => 5, :within => 1, :retry_in => 5)
|
13
|
+
|
14
|
+
options.each_pair do |name, val|
|
15
|
+
instance_variable_set("@#{name}", val) if PARAMS.include?(name)
|
16
|
+
end
|
17
|
+
|
18
|
+
@timeline = Util::RotationalArray.new(@times)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def notify(transition)
|
23
|
+
if TRIGGER_STATES.include?(transition.to_name)
|
24
|
+
self.timeline << Time.now.to_i
|
25
|
+
self.check_flapping
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def reset!
|
30
|
+
@timeline.clear
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
def check_flapping
|
35
|
+
num_occurances = (@timeline.nitems == self.times)
|
36
|
+
|
37
|
+
# The process has not flapped if we haven't encountered enough incidents
|
38
|
+
return unless num_occurances
|
39
|
+
|
40
|
+
# Check if the incident happend within the timeframe
|
41
|
+
duration = (@timeline.last - @timeline.first) <= self.within
|
42
|
+
|
43
|
+
if duration
|
44
|
+
self.logger.info "Flapping detected: retrying in #{self.retry_in} seconds"
|
45
|
+
|
46
|
+
self.schedule_event(:start, self.retry_in)
|
47
|
+
|
48
|
+
# this happens in the process' thread so we don't have to worry about concurrency issues with this event
|
49
|
+
self.dispatch!(:unmonitor)
|
50
|
+
|
51
|
+
@timeline.clear
|
52
|
+
|
53
|
+
# This will prevent a transition from happening in the process state_machine
|
54
|
+
throw :halt
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Bluepill
|
2
|
+
module Util
|
3
|
+
class RotationalArray < Array
|
4
|
+
def initialize(size)
|
5
|
+
super(size)
|
6
|
+
|
7
|
+
@capacity = size
|
8
|
+
@counter = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def push(value)
|
12
|
+
idx = rotational_idx(@counter)
|
13
|
+
self[idx] = value
|
14
|
+
|
15
|
+
@counter += 1
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
alias_method :<<, :push
|
20
|
+
|
21
|
+
def pop
|
22
|
+
raise "Cannot call pop on a rotational array"
|
23
|
+
end
|
24
|
+
|
25
|
+
def shift
|
26
|
+
raise "Cannot call shift on a rotational array"
|
27
|
+
end
|
28
|
+
|
29
|
+
def unshift
|
30
|
+
raise "Cannot call unshift on a rotational array"
|
31
|
+
end
|
32
|
+
|
33
|
+
def last
|
34
|
+
return if @counter.zero?
|
35
|
+
|
36
|
+
self[rotational_idx(@counter - 1)]
|
37
|
+
end
|
38
|
+
|
39
|
+
def first
|
40
|
+
return if @counter.zero?
|
41
|
+
return self[0] if @counter <= @capacity
|
42
|
+
|
43
|
+
self[rotational_idx(@counter)]
|
44
|
+
end
|
45
|
+
|
46
|
+
def clear
|
47
|
+
@counter = 0
|
48
|
+
super
|
49
|
+
end
|
50
|
+
|
51
|
+
def each(&block)
|
52
|
+
times = @counter >= @capacity ? @capacity : @counter
|
53
|
+
start = @counter >= @capacity ? rotational_idx(@counter) : 0
|
54
|
+
times.times do |i|
|
55
|
+
block.call(self[rotational_idx(start + i)])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def rotational_idx(idx)
|
62
|
+
idx % @capacity
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/example.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bluepill'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
ROOT_DIR = "/tmp/bp"
|
6
|
+
|
7
|
+
# Watch with
|
8
|
+
# watch -n0.2 'ps axu | egrep "(CPU|forking|bluepill|sleep)" | grep -v grep | sort'
|
9
|
+
Bluepill.application(:sample_app) do |app|
|
10
|
+
0.times do |i|
|
11
|
+
app.process("process_#{i}") do |process|
|
12
|
+
process.pid_file = "#{ROOT_DIR}/pids/process_#{i}.pid"
|
13
|
+
|
14
|
+
# I could not figure out a portable way to
|
15
|
+
# specify the path to the sample forking server across the diff developer laptops.
|
16
|
+
# Since this code is eval'ed we cannot reliably use __FILE__
|
17
|
+
process.start_command = "/Users/rohith/work/bluepill/bin/sample_forking_server #{4242 + i}"
|
18
|
+
process.stop_command = "kill -INT {{PID}}"
|
19
|
+
process.daemonize = true
|
20
|
+
|
21
|
+
process.start_grace_time = 1.seconds
|
22
|
+
process.restart_grace_time = 7.seconds
|
23
|
+
process.stop_grace_time = 7.seconds
|
24
|
+
|
25
|
+
process.uid = "rohith"
|
26
|
+
process.gid = "staff"
|
27
|
+
|
28
|
+
# process.checks :cpu_usage, :every => 10, :below => 0.5, :times => [5, 5]
|
29
|
+
process.checks :flapping, :times => 2, :within => 30.seconds, :retry_in => 7.seconds
|
30
|
+
|
31
|
+
process.monitor_children do |child_process|
|
32
|
+
# child_process.checks :cpu_usage,
|
33
|
+
# :every => 10,
|
34
|
+
# :below => 0.5,
|
35
|
+
# :times => [5, 5]
|
36
|
+
|
37
|
+
# child_process.checks :mem_usage,
|
38
|
+
# :every => 3,
|
39
|
+
# :below => 600.kilobytes,
|
40
|
+
# :times => [3, 5],
|
41
|
+
# :fires => [:stop]
|
42
|
+
|
43
|
+
child_process.stop_command = "kill -QUIT {{PID}}"
|
44
|
+
# child_process.checks :flapping, :times => 2, :within => 30.seconds, :retry_in => 7.seconds
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
0.times do |i|
|
50
|
+
app.process("group_process_#{i}") do |process|
|
51
|
+
process.group = "group_1"
|
52
|
+
process.pid_file = "/Users/rohith/ffs/tmp/pids/mongrel_#{i}.pid"
|
53
|
+
process.start_command = "cd ~/ffs && mongrel_rails start -P #{process.pid_file} -p 3000 -d"
|
54
|
+
|
55
|
+
process.start_grace_time = 10.seconds
|
56
|
+
|
57
|
+
process.uid = "rohith"
|
58
|
+
process.gid = "staff"
|
59
|
+
|
60
|
+
# process.checks :always_true, :every => 10
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
1.times do |i|
|
65
|
+
app.process("group_process_#{i}") do |process|
|
66
|
+
process.uid = "rohith"
|
67
|
+
process.gid = "wheel"
|
68
|
+
|
69
|
+
process.stderr = "/tmp/err.log"
|
70
|
+
process.stdout = "/tmp/err.log"
|
71
|
+
|
72
|
+
|
73
|
+
process.group = "grouped"
|
74
|
+
process.start_command = %Q{cd /tmp && ruby -e '$stderr.puts("hello stderr");$stdout.puts("hello stdout"); $stdout.flush; $stderr.flush; sleep 10'}
|
75
|
+
process.daemonize = true
|
76
|
+
process.pid_file = "/tmp/noperm/p_#{process.group}_#{i}.pid"
|
77
|
+
|
78
|
+
# process.checks :always_true, :every => 5
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bluepill'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
# ATTENTION:
|
6
|
+
# You must declare only one application per config when foreground mode specified
|
7
|
+
#
|
8
|
+
# http://github.com/akzhan/runit-man used as example of monitored application.
|
9
|
+
|
10
|
+
Bluepill.application(:runit_man, :foreground => true) do |app|
|
11
|
+
app.process("runit-man") do |process|
|
12
|
+
process.pid_file = "/etc/service/runit-man/supervise/pid"
|
13
|
+
|
14
|
+
process.start_command = "/usr/bin/sv start runit-man"
|
15
|
+
process.stop_command = "/usr/bin/sv stop runit-man"
|
16
|
+
process.restart_command = "/usr/bin/sv restart runit-man"
|
17
|
+
|
18
|
+
process.start_grace_time = 1.seconds
|
19
|
+
process.restart_grace_time = 7.seconds
|
20
|
+
process.stop_grace_time = 7.seconds
|
21
|
+
|
22
|
+
process.checks :http, :within => 30.seconds, :retry_in => 7.seconds, :every => 30.seconds,
|
23
|
+
:url => 'http://localhost:4567/', :kind => :success, :pattern => /html/, :timeout => 3.seconds
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dylanvaughn-bluepill
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 81
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 39
|
10
|
+
version: 0.0.39
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Arya Asemanfar
|
14
|
+
- Gary Tsang
|
15
|
+
- Rohith Ravi
|
16
|
+
autorequire:
|
17
|
+
bindir: bin
|
18
|
+
cert_chain: []
|
19
|
+
|
20
|
+
date: 2010-08-06 00:00:00 -07:00
|
21
|
+
default_executable: bluepill
|
22
|
+
dependencies:
|
23
|
+
- !ruby/object:Gem::Dependency
|
24
|
+
name: daemons
|
25
|
+
prerelease: false
|
26
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
hash: 5
|
32
|
+
segments:
|
33
|
+
- 1
|
34
|
+
- 0
|
35
|
+
- 9
|
36
|
+
version: 1.0.9
|
37
|
+
type: :runtime
|
38
|
+
version_requirements: *id001
|
39
|
+
- !ruby/object:Gem::Dependency
|
40
|
+
name: blankslate
|
41
|
+
prerelease: false
|
42
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
hash: 107
|
48
|
+
segments:
|
49
|
+
- 2
|
50
|
+
- 1
|
51
|
+
- 2
|
52
|
+
- 2
|
53
|
+
version: 2.1.2.2
|
54
|
+
type: :runtime
|
55
|
+
version_requirements: *id002
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: state_machine
|
58
|
+
prerelease: false
|
59
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
hash: 63
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
- 8
|
68
|
+
- 0
|
69
|
+
version: 0.8.0
|
70
|
+
type: :runtime
|
71
|
+
version_requirements: *id003
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: activesupport
|
74
|
+
prerelease: false
|
75
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
hash: 11
|
81
|
+
segments:
|
82
|
+
- 2
|
83
|
+
- 3
|
84
|
+
- 4
|
85
|
+
version: 2.3.4
|
86
|
+
type: :runtime
|
87
|
+
version_requirements: *id004
|
88
|
+
description: Bluepill keeps your daemons up while taking up as little resources as possible. After all you probably want the resources of your server to be used by whatever daemons you are running rather than the thing that's supposed to make sure they are brought back up, should they die or misbehave.
|
89
|
+
email: dylancvaughn@gmail.com
|
90
|
+
executables:
|
91
|
+
- bluepill
|
92
|
+
extensions: []
|
93
|
+
|
94
|
+
extra_rdoc_files:
|
95
|
+
- LICENSE
|
96
|
+
- README.md
|
97
|
+
files:
|
98
|
+
- .gitignore
|
99
|
+
- DESIGN.md
|
100
|
+
- LICENSE
|
101
|
+
- README.md
|
102
|
+
- Rakefile
|
103
|
+
- VERSION
|
104
|
+
- bin/bluepill
|
105
|
+
- bin/bpsv
|
106
|
+
- bluepill.gemspec
|
107
|
+
- lib/bluepill.rb
|
108
|
+
- lib/bluepill/application.rb
|
109
|
+
- lib/bluepill/application/client.rb
|
110
|
+
- lib/bluepill/application/server.rb
|
111
|
+
- lib/bluepill/condition_watch.rb
|
112
|
+
- lib/bluepill/controller.rb
|
113
|
+
- lib/bluepill/dsl.rb
|
114
|
+
- lib/bluepill/group.rb
|
115
|
+
- lib/bluepill/logger.rb
|
116
|
+
- lib/bluepill/process.rb
|
117
|
+
- lib/bluepill/process_conditions.rb
|
118
|
+
- lib/bluepill/process_conditions/always_true.rb
|
119
|
+
- lib/bluepill/process_conditions/cpu_usage.rb
|
120
|
+
- lib/bluepill/process_conditions/http.rb
|
121
|
+
- lib/bluepill/process_conditions/mem_usage.rb
|
122
|
+
- lib/bluepill/process_conditions/process_condition.rb
|
123
|
+
- lib/bluepill/process_statistics.rb
|
124
|
+
- lib/bluepill/socket.rb
|
125
|
+
- lib/bluepill/system.rb
|
126
|
+
- lib/bluepill/trigger.rb
|
127
|
+
- lib/bluepill/triggers/flapping.rb
|
128
|
+
- lib/bluepill/util/rotational_array.rb
|
129
|
+
- lib/bluepill/version.rb
|
130
|
+
- lib/example.rb
|
131
|
+
- lib/runit_example.rb
|
132
|
+
has_rdoc: true
|
133
|
+
homepage: http://github.com/arya/bluepill
|
134
|
+
licenses: []
|
135
|
+
|
136
|
+
post_install_message:
|
137
|
+
rdoc_options:
|
138
|
+
- --charset=UTF-8
|
139
|
+
require_paths:
|
140
|
+
- lib
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
142
|
+
none: false
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
hash: 3
|
147
|
+
segments:
|
148
|
+
- 0
|
149
|
+
version: "0"
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
none: false
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
hash: 3
|
156
|
+
segments:
|
157
|
+
- 0
|
158
|
+
version: "0"
|
159
|
+
requirements: []
|
160
|
+
|
161
|
+
rubyforge_project:
|
162
|
+
rubygems_version: 1.3.7
|
163
|
+
signing_key:
|
164
|
+
specification_version: 3
|
165
|
+
summary: A process monitor written in Ruby with stability and minimalism in mind.
|
166
|
+
test_files: []
|
167
|
+
|