kostya-bluepill 0.0.60.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/DESIGN.md +10 -0
- data/Gemfile +10 -0
- data/LICENSE +22 -0
- data/README.md +305 -0
- data/Rakefile +38 -0
- data/bin/bluepill +104 -0
- data/bluepill.gemspec +37 -0
- data/examples/example.rb +87 -0
- data/examples/new_example.rb +89 -0
- data/examples/new_runit_example.rb +29 -0
- data/examples/runit_example.rb +26 -0
- data/lib/bluepill.rb +38 -0
- data/lib/bluepill/application.rb +201 -0
- data/lib/bluepill/application/client.rb +8 -0
- data/lib/bluepill/application/server.rb +23 -0
- data/lib/bluepill/condition_watch.rb +50 -0
- data/lib/bluepill/controller.rb +110 -0
- data/lib/bluepill/dsl.rb +12 -0
- data/lib/bluepill/dsl/app_proxy.rb +25 -0
- data/lib/bluepill/dsl/process_factory.rb +122 -0
- data/lib/bluepill/dsl/process_proxy.rb +44 -0
- data/lib/bluepill/group.rb +72 -0
- data/lib/bluepill/process.rb +480 -0
- data/lib/bluepill/process_conditions.rb +14 -0
- data/lib/bluepill/process_conditions/always_true.rb +18 -0
- data/lib/bluepill/process_conditions/cpu_usage.rb +19 -0
- data/lib/bluepill/process_conditions/http.rb +58 -0
- data/lib/bluepill/process_conditions/mem_usage.rb +32 -0
- data/lib/bluepill/process_conditions/process_condition.rb +22 -0
- data/lib/bluepill/process_statistics.rb +27 -0
- data/lib/bluepill/socket.rb +58 -0
- data/lib/bluepill/system.rb +236 -0
- data/lib/bluepill/trigger.rb +59 -0
- data/lib/bluepill/triggers/flapping.rb +56 -0
- data/lib/bluepill/util/rotational_array.rb +20 -0
- data/lib/bluepill/version.rb +4 -0
- data/spec/lib/bluepill/process_statistics_spec.rb +24 -0
- data/spec/lib/bluepill/system_spec.rb +36 -0
- data/spec/spec_helper.rb +19 -0
- metadata +304 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module Bluepill
|
3
|
+
class Trigger
|
4
|
+
@implementations = {}
|
5
|
+
def self.inherited(klass)
|
6
|
+
@implementations[klass.name.split('::').last.underscore.to_sym] = klass
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.[](name)
|
10
|
+
@implementations[name]
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :process, :logger, :mutex, :scheduled_events
|
14
|
+
|
15
|
+
def initialize(process, options = {})
|
16
|
+
self.process = process
|
17
|
+
self.logger = options[:logger]
|
18
|
+
self.mutex = Mutex.new
|
19
|
+
self.scheduled_events = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def reset!
|
23
|
+
self.cancel_all_events
|
24
|
+
end
|
25
|
+
|
26
|
+
def notify(transition)
|
27
|
+
raise "Implement in subclass"
|
28
|
+
end
|
29
|
+
|
30
|
+
def dispatch!(event)
|
31
|
+
self.process.dispatch!(event, self.class.name.split("::").last)
|
32
|
+
end
|
33
|
+
|
34
|
+
def schedule_event(event, delay)
|
35
|
+
# TODO: maybe wrap this in a ScheduledEvent class with methods like cancel
|
36
|
+
thread = Thread.new(self) do |trigger|
|
37
|
+
begin
|
38
|
+
sleep delay.to_f
|
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.exception(e)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
self.scheduled_events.push([event, thread])
|
49
|
+
end
|
50
|
+
|
51
|
+
def cancel_all_events
|
52
|
+
self.logger.info "Canceling all scheduled events"
|
53
|
+
self.mutex.synchronize do
|
54
|
+
self.scheduled_events.each {|_, thread| thread.kill}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module Bluepill
|
3
|
+
module Triggers
|
4
|
+
class Flapping < Bluepill::Trigger
|
5
|
+
TRIGGER_STATES = [:starting, :restarting]
|
6
|
+
|
7
|
+
PARAMS = [:times, :within, :retry_in]
|
8
|
+
|
9
|
+
attr_accessor *PARAMS
|
10
|
+
attr_reader :timeline
|
11
|
+
|
12
|
+
def initialize(process, options = {})
|
13
|
+
options.reverse_merge!(:times => 5, :within => 1, :retry_in => 5)
|
14
|
+
|
15
|
+
options.each_pair do |name, val|
|
16
|
+
instance_variable_set("@#{name}", val) if PARAMS.include?(name)
|
17
|
+
end
|
18
|
+
|
19
|
+
@timeline = Util::RotationalArray.new(@times)
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def notify(transition)
|
24
|
+
if TRIGGER_STATES.include?(transition.to_name)
|
25
|
+
self.timeline << Time.now.to_i
|
26
|
+
self.check_flapping
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset!
|
31
|
+
@timeline.clear
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
def check_flapping
|
36
|
+
# The process has not flapped if we haven't encountered enough incidents
|
37
|
+
return unless (@timeline.compact.length == self.times)
|
38
|
+
|
39
|
+
# Check if the incident happend within the timeframe
|
40
|
+
duration = (@timeline.last - @timeline.first) <= self.within
|
41
|
+
|
42
|
+
if duration
|
43
|
+
self.logger.info "Flapping detected: retrying in #{self.retry_in} seconds"
|
44
|
+
|
45
|
+
self.schedule_event(:start, self.retry_in) unless self.retry_in == 0 # retry_in zero means "do not retry, ever"
|
46
|
+
self.schedule_event(:unmonitor, 0)
|
47
|
+
|
48
|
+
@timeline.clear
|
49
|
+
|
50
|
+
# This will prevent a transition from happening in the process state_machine
|
51
|
+
throw :halt
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module Bluepill
|
3
|
+
module Util
|
4
|
+
class RotationalArray < Array
|
5
|
+
def initialize(size)
|
6
|
+
@capacity = size
|
7
|
+
|
8
|
+
super() # no size - intentionally
|
9
|
+
end
|
10
|
+
|
11
|
+
def push(value)
|
12
|
+
super(value)
|
13
|
+
|
14
|
+
self.shift if self.length > @capacity
|
15
|
+
self
|
16
|
+
end
|
17
|
+
alias_method :<<, :push
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
describe Bluepill::ProcessStatistics do
|
2
|
+
before(:each) do
|
3
|
+
@stats = Bluepill::ProcessStatistics.new
|
4
|
+
end
|
5
|
+
|
6
|
+
it "should record events" do
|
7
|
+
@stats.record_event('some event', 'some reason')
|
8
|
+
@stats.record_event('another event', 'another reason')
|
9
|
+
@stats.events.should have(2).events
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should record #EVENTS_TO_PERSIST events" do
|
13
|
+
(2 * Bluepill::ProcessStatistics::EVENTS_TO_PERSIST).times do
|
14
|
+
@stats.record_event('some event', 'some reason')
|
15
|
+
end
|
16
|
+
@stats.events.should have(Bluepill::ProcessStatistics::EVENTS_TO_PERSIST).events
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return event history" do
|
20
|
+
@stats.record_event('some event', 'some reason')
|
21
|
+
@stats.to_s.should match(/some reason/)
|
22
|
+
@stats.to_s.should match(/event history/)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
describe Bluepill::System do
|
2
|
+
describe :pid_alive? do
|
3
|
+
it "should be true if process responds to zero signal" do
|
4
|
+
mock(::Process).kill(0, 555)
|
5
|
+
Bluepill::System.should be_pid_alive(555)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should be false if process throws exception on zero signal" do
|
9
|
+
mock(::Process).kill(0, 555) { raise Errno::ESRCH.new }
|
10
|
+
Bluepill::System.should_not be_pid_alive(555)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe :store do
|
15
|
+
it "should be Hash" do
|
16
|
+
Bluepill::System.store.should be_kind_of(Hash)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return same Hash or every call" do
|
20
|
+
Bluepill::System.store.should be_equal(Bluepill::System.store)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should store assigned pairs" do
|
24
|
+
Bluepill::System.store[:somekey] = 10
|
25
|
+
Bluepill::System.store[:somekey].should be_eql(10)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe :reset_data do
|
30
|
+
it 'should clear the #store' do
|
31
|
+
Bluepill::System.store[:anotherkey] = Faker::Lorem.sentence
|
32
|
+
Bluepill::System.reset_data
|
33
|
+
Bluepill::System.store.should be_empty
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
if RUBY_VERSION >= '1.9'
|
2
|
+
if ENV['ENABLE_SIMPLECOV']
|
3
|
+
require 'simplecov'
|
4
|
+
SimpleCov.start
|
5
|
+
end
|
6
|
+
else
|
7
|
+
require 'rubygems'
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'faker'
|
11
|
+
require 'rspec/core'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
|
14
|
+
|
15
|
+
RSpec.configure do |conf|
|
16
|
+
conf.mock_with :rr
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'bluepill'
|
metadata
ADDED
@@ -0,0 +1,304 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kostya-bluepill
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 189
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 60
|
10
|
+
- 1
|
11
|
+
version: 0.0.60.1
|
12
|
+
platform: ruby
|
13
|
+
authors:
|
14
|
+
- Arya Asemanfar
|
15
|
+
- Gary Tsang
|
16
|
+
- Rohith Ravi
|
17
|
+
autorequire:
|
18
|
+
bindir: bin
|
19
|
+
cert_chain: []
|
20
|
+
|
21
|
+
date: 2012-08-15 00:00:00 +04:00
|
22
|
+
default_executable:
|
23
|
+
dependencies:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: daemons
|
26
|
+
prerelease: false
|
27
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
hash: 27
|
33
|
+
segments:
|
34
|
+
- 1
|
35
|
+
- 1
|
36
|
+
- 4
|
37
|
+
version: 1.1.4
|
38
|
+
- - <=
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
hash: 31
|
41
|
+
segments:
|
42
|
+
- 1
|
43
|
+
- 1
|
44
|
+
- 6
|
45
|
+
version: 1.1.6
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id001
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: state_machine
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ~>
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 19
|
57
|
+
segments:
|
58
|
+
- 1
|
59
|
+
- 1
|
60
|
+
- 0
|
61
|
+
version: 1.1.0
|
62
|
+
type: :runtime
|
63
|
+
version_requirements: *id002
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: activesupport
|
66
|
+
prerelease: false
|
67
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
hash: 7
|
73
|
+
segments:
|
74
|
+
- 3
|
75
|
+
- 0
|
76
|
+
- 0
|
77
|
+
version: 3.0.0
|
78
|
+
type: :runtime
|
79
|
+
version_requirements: *id003
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: i18n
|
82
|
+
prerelease: false
|
83
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
hash: 11
|
89
|
+
segments:
|
90
|
+
- 0
|
91
|
+
- 5
|
92
|
+
- 0
|
93
|
+
version: 0.5.0
|
94
|
+
type: :runtime
|
95
|
+
version_requirements: *id004
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: madvertise-logging
|
98
|
+
prerelease: false
|
99
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
hash: 3
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
version: "0"
|
108
|
+
type: :runtime
|
109
|
+
version_requirements: *id005
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: bundler
|
112
|
+
prerelease: false
|
113
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
hash: 3
|
119
|
+
segments:
|
120
|
+
- 1
|
121
|
+
- 0
|
122
|
+
- 10
|
123
|
+
version: 1.0.10
|
124
|
+
type: :development
|
125
|
+
version_requirements: *id006
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rake
|
128
|
+
prerelease: false
|
129
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
130
|
+
none: false
|
131
|
+
requirements:
|
132
|
+
- - "!="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
hash: 59
|
135
|
+
segments:
|
136
|
+
- 0
|
137
|
+
- 9
|
138
|
+
- 0
|
139
|
+
version: 0.9.0
|
140
|
+
type: :development
|
141
|
+
version_requirements: *id007
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: rspec-core
|
144
|
+
prerelease: false
|
145
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
146
|
+
none: false
|
147
|
+
requirements:
|
148
|
+
- - ~>
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
hash: 3
|
151
|
+
segments:
|
152
|
+
- 2
|
153
|
+
- 0
|
154
|
+
version: "2.0"
|
155
|
+
type: :development
|
156
|
+
version_requirements: *id008
|
157
|
+
- !ruby/object:Gem::Dependency
|
158
|
+
name: rspec-expectations
|
159
|
+
prerelease: false
|
160
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ~>
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
hash: 3
|
166
|
+
segments:
|
167
|
+
- 2
|
168
|
+
- 0
|
169
|
+
version: "2.0"
|
170
|
+
type: :development
|
171
|
+
version_requirements: *id009
|
172
|
+
- !ruby/object:Gem::Dependency
|
173
|
+
name: rr
|
174
|
+
prerelease: false
|
175
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
176
|
+
none: false
|
177
|
+
requirements:
|
178
|
+
- - ~>
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
hash: 15
|
181
|
+
segments:
|
182
|
+
- 1
|
183
|
+
- 0
|
184
|
+
version: "1.0"
|
185
|
+
type: :development
|
186
|
+
version_requirements: *id010
|
187
|
+
- !ruby/object:Gem::Dependency
|
188
|
+
name: faker
|
189
|
+
prerelease: false
|
190
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
191
|
+
none: false
|
192
|
+
requirements:
|
193
|
+
- - ~>
|
194
|
+
- !ruby/object:Gem::Version
|
195
|
+
hash: 25
|
196
|
+
segments:
|
197
|
+
- 0
|
198
|
+
- 9
|
199
|
+
version: "0.9"
|
200
|
+
type: :development
|
201
|
+
version_requirements: *id011
|
202
|
+
- !ruby/object:Gem::Dependency
|
203
|
+
name: yard
|
204
|
+
prerelease: false
|
205
|
+
requirement: &id012 !ruby/object:Gem::Requirement
|
206
|
+
none: false
|
207
|
+
requirements:
|
208
|
+
- - ~>
|
209
|
+
- !ruby/object:Gem::Version
|
210
|
+
hash: 5
|
211
|
+
segments:
|
212
|
+
- 0
|
213
|
+
- 7
|
214
|
+
version: "0.7"
|
215
|
+
type: :development
|
216
|
+
version_requirements: *id012
|
217
|
+
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.
|
218
|
+
email:
|
219
|
+
- entombedvirus@gmail.com
|
220
|
+
executables:
|
221
|
+
- bluepill
|
222
|
+
extensions: []
|
223
|
+
|
224
|
+
extra_rdoc_files:
|
225
|
+
- LICENSE
|
226
|
+
- README.md
|
227
|
+
files:
|
228
|
+
- .gitignore
|
229
|
+
- .rspec
|
230
|
+
- DESIGN.md
|
231
|
+
- Gemfile
|
232
|
+
- LICENSE
|
233
|
+
- README.md
|
234
|
+
- Rakefile
|
235
|
+
- bin/bluepill
|
236
|
+
- bluepill.gemspec
|
237
|
+
- examples/example.rb
|
238
|
+
- examples/new_example.rb
|
239
|
+
- examples/new_runit_example.rb
|
240
|
+
- examples/runit_example.rb
|
241
|
+
- lib/bluepill.rb
|
242
|
+
- lib/bluepill/application.rb
|
243
|
+
- lib/bluepill/application/client.rb
|
244
|
+
- lib/bluepill/application/server.rb
|
245
|
+
- lib/bluepill/condition_watch.rb
|
246
|
+
- lib/bluepill/controller.rb
|
247
|
+
- lib/bluepill/dsl.rb
|
248
|
+
- lib/bluepill/dsl/app_proxy.rb
|
249
|
+
- lib/bluepill/dsl/process_factory.rb
|
250
|
+
- lib/bluepill/dsl/process_proxy.rb
|
251
|
+
- lib/bluepill/group.rb
|
252
|
+
- lib/bluepill/process.rb
|
253
|
+
- lib/bluepill/process_conditions.rb
|
254
|
+
- lib/bluepill/process_conditions/always_true.rb
|
255
|
+
- lib/bluepill/process_conditions/cpu_usage.rb
|
256
|
+
- lib/bluepill/process_conditions/http.rb
|
257
|
+
- lib/bluepill/process_conditions/mem_usage.rb
|
258
|
+
- lib/bluepill/process_conditions/process_condition.rb
|
259
|
+
- lib/bluepill/process_statistics.rb
|
260
|
+
- lib/bluepill/socket.rb
|
261
|
+
- lib/bluepill/system.rb
|
262
|
+
- lib/bluepill/trigger.rb
|
263
|
+
- lib/bluepill/triggers/flapping.rb
|
264
|
+
- lib/bluepill/util/rotational_array.rb
|
265
|
+
- lib/bluepill/version.rb
|
266
|
+
- spec/lib/bluepill/process_statistics_spec.rb
|
267
|
+
- spec/lib/bluepill/system_spec.rb
|
268
|
+
- spec/spec_helper.rb
|
269
|
+
has_rdoc: true
|
270
|
+
homepage: http://github.com/kostya/bluepill
|
271
|
+
licenses: []
|
272
|
+
|
273
|
+
post_install_message:
|
274
|
+
rdoc_options: []
|
275
|
+
|
276
|
+
require_paths:
|
277
|
+
- lib
|
278
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
279
|
+
none: false
|
280
|
+
requirements:
|
281
|
+
- - ">="
|
282
|
+
- !ruby/object:Gem::Version
|
283
|
+
hash: 3
|
284
|
+
segments:
|
285
|
+
- 0
|
286
|
+
version: "0"
|
287
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
288
|
+
none: false
|
289
|
+
requirements:
|
290
|
+
- - ">="
|
291
|
+
- !ruby/object:Gem::Version
|
292
|
+
hash: 3
|
293
|
+
segments:
|
294
|
+
- 0
|
295
|
+
version: "0"
|
296
|
+
requirements: []
|
297
|
+
|
298
|
+
rubyforge_project:
|
299
|
+
rubygems_version: 1.4.2
|
300
|
+
signing_key:
|
301
|
+
specification_version: 3
|
302
|
+
summary: A process monitor written in Ruby with stability and minimalism in mind.
|
303
|
+
test_files: []
|
304
|
+
|