bluepill 0.0.46 → 0.0.47

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.
@@ -1,5 +1,6 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  module Bluepill
2
- module ProcessConditions
3
+ module ProcessConditions
3
4
  def self.[](name)
4
5
  const_get(name.to_s.camelcase)
5
6
  end
@@ -1,14 +1,15 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  module Bluepill
2
3
  module ProcessConditions
3
4
  class AlwaysTrue < ProcessCondition
4
5
  def initialize(options = {})
5
6
  @below = options[:below]
6
7
  end
7
-
8
+
8
9
  def run(pid)
9
10
  1
10
11
  end
11
-
12
+
12
13
  def check(value)
13
14
  true
14
15
  end
@@ -1,15 +1,16 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  module Bluepill
2
3
  module ProcessConditions
3
4
  class CpuUsage < ProcessCondition
4
5
  def initialize(options = {})
5
6
  @below = options[:below]
6
7
  end
7
-
8
+
8
9
  def run(pid)
9
10
  # third col in the ps axu output
10
11
  System.cpu_usage(pid).to_f
11
12
  end
12
-
13
+
13
14
  def check(value)
14
15
  value < @below
15
16
  end
@@ -1,3 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  require 'net/http'
2
3
  require 'uri'
3
4
 
@@ -1,3 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  module Bluepill
2
3
  module ProcessConditions
3
4
  class MemUsage < ProcessCondition
@@ -5,20 +6,20 @@ module Bluepill
5
6
  FORMAT_STR = "%d%s"
6
7
  MB_LABEL = "MB"
7
8
  KB_LABEL = "KB"
8
-
9
+
9
10
  def initialize(options = {})
10
11
  @below = options[:below]
11
12
  end
12
-
13
+
13
14
  def run(pid)
14
15
  # rss is on the 5th col
15
16
  System.memory_usage(pid).to_f
16
17
  end
17
-
18
+
18
19
  def check(value)
19
20
  value.kilobytes < @below
20
21
  end
21
-
22
+
22
23
  def format_value(value)
23
24
  if value.kilobytes >= MB
24
25
  FORMAT_STR % [(value / 1024).round, MB_LABEL]
@@ -1,18 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  module Bluepill
2
3
  module ProcessConditions
3
- class ProcessCondition
4
+ class ProcessCondition
4
5
  def initialize(options = {})
5
6
  @options = options
6
7
  end
7
-
8
+
8
9
  def run(pid)
9
10
  raise "Implement in subclass!"
10
11
  end
11
-
12
+
12
13
  def check(value)
13
14
  raise "Implement in subclass!"
14
15
  end
15
-
16
+
16
17
  def format_value(value)
17
18
  value
18
19
  end
@@ -1,6 +1,8 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  module Bluepill
2
- class ProcessStatistics
3
+ class ProcessStatistics
3
4
  STRFTIME = "%m/%d/%Y %H:%I:%S"
5
+
4
6
  # possibly persist this data.
5
7
  def initialize
6
8
  @events = Util::RotationalArray.new(10)
@@ -11,14 +13,11 @@ module Bluepill
11
13
  end
12
14
 
13
15
  def to_s
14
- str = []
15
- @events.each do |(event, reason, time)|
16
+ str = @events.reverse.collect do |(event, reason, time)|
16
17
  str << " #{event} at #{time.strftime(STRFTIME)} - #{reason || "unspecified"}"
17
- end
18
- if str.size > 0
19
- str << "event history:"
20
- end
21
- str.reverse.join("\n")
18
+ end.join("\n")
19
+
20
+ "event history:\n#{str}"
22
21
  end
23
22
  end
24
23
  end
@@ -1,15 +1,16 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  require 'socket'
2
3
 
3
4
  module Bluepill
4
5
  module Socket
5
- TIMEOUT = 10
6
+ TIMEOUT = 60 # Used for client commands
6
7
 
7
8
  extend self
8
9
 
9
10
  def client(base_dir, name, &b)
10
11
  UNIXSocket.open(socket_path(base_dir, name), &b)
11
12
  end
12
-
13
+
13
14
  def client_command(base_dir, name, command)
14
15
  client(base_dir, name) do |socket|
15
16
  Timeout.timeout(TIMEOUT) do
@@ -20,7 +21,7 @@ module Bluepill
20
21
  rescue EOFError, Timeout::Error
21
22
  abort("Socket Timeout: Server may not be responding")
22
23
  end
23
-
24
+
24
25
  def server(base_dir, name)
25
26
  socket_path = self.socket_path(base_dir, name)
26
27
  begin
@@ -38,10 +39,9 @@ module Bluepill
38
39
  end
39
40
  end
40
41
  end
41
-
42
+
42
43
  def socket_path(base_dir, name)
43
- File.join(base_dir, 'socks', name + ".sock")
44
+ File.join(base_dir, 'socks', name + ".sock")
44
45
  end
45
46
  end
46
47
  end
47
-
@@ -1,3 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  require 'etc'
2
3
  require "shellwords"
3
4
 
@@ -7,7 +8,7 @@ module Bluepill
7
8
  module System
8
9
  APPEND_MODE = "a"
9
10
  extend self
10
-
11
+
11
12
  # The position of each field in ps output
12
13
  IDX_MAP = {
13
14
  :pid => 0,
@@ -15,7 +16,7 @@ module Bluepill
15
16
  :pcpu => 2,
16
17
  :rss => 3
17
18
  }
18
-
19
+
19
20
  def pid_alive?(pid)
20
21
  begin
21
22
  ::Process.kill(0, pid)
@@ -24,23 +25,23 @@ module Bluepill
24
25
  false
25
26
  end
26
27
  end
27
-
28
+
28
29
  def cpu_usage(pid)
29
30
  ps_axu[pid] && ps_axu[pid][IDX_MAP[:pcpu]].to_f
30
31
  end
31
-
32
+
32
33
  def memory_usage(pid)
33
34
  ps_axu[pid] && ps_axu[pid][IDX_MAP[:rss]].to_f
34
35
  end
35
-
36
+
36
37
  def get_children(parent_pid)
37
38
  child_pids = Array.new
38
- ps_axu.each_pair do |pid, chunks|
39
+ ps_axu.each_pair do |pid, chunks|
39
40
  child_pids << chunks[IDX_MAP[:pid]].to_i if chunks[IDX_MAP[:ppid]].to_i == parent_pid.to_i
40
41
  end
41
42
  child_pids
42
43
  end
43
-
44
+
44
45
  # Returns the pid of the child that executes the cmd
45
46
  def daemonize(cmd, options = {})
46
47
  rd, wr = IO.pipe
@@ -48,76 +49,76 @@ module Bluepill
48
49
  if child = Daemonize.safefork
49
50
  # we do not wanna create zombies, so detach ourselves from the child exit status
50
51
  ::Process.detach(child)
51
-
52
+
52
53
  # parent
53
54
  wr.close
54
-
55
+
55
56
  daemon_id = rd.read.to_i
56
57
  rd.close
57
-
58
+
58
59
  return daemon_id if daemon_id > 0
59
-
60
+
60
61
  else
61
62
  # child
62
63
  rd.close
63
64
 
64
65
  drop_privileges(options[:uid], options[:gid])
65
-
66
+
66
67
  # if we cannot write the pid file as the provided user, err out
67
68
  exit unless can_write_pid_file(options[:pid_file], options[:logger])
68
-
69
+
69
70
  to_daemonize = lambda do
70
71
  # Setting end PWD env emulates bash behavior when dealing with symlinks
71
72
  Dir.chdir(ENV["PWD"] = options[:working_dir]) if options[:working_dir]
72
73
  options[:environment].each { |key, value| ENV[key.to_s] = value.to_s } if options[:environment]
73
-
74
+
74
75
  redirect_io(*options.values_at(:stdin, :stdout, :stderr))
75
-
76
+
76
77
  ::Kernel.exec(*Shellwords.shellwords(cmd))
77
78
  exit
78
79
  end
79
80
 
80
81
  daemon_id = Daemonize.call_as_daemon(to_daemonize, nil, cmd)
81
-
82
+
82
83
  File.open(options[:pid_file], "w") {|f| f.write(daemon_id)}
83
-
84
- wr.write daemon_id
84
+
85
+ wr.write daemon_id
85
86
  wr.close
86
87
 
87
88
  exit
88
89
  end
89
90
  end
90
-
91
+
91
92
  # Returns the stdout, stderr and exit code of the cmd
92
93
  def execute_blocking(cmd, options = {})
93
94
  rd, wr = IO.pipe
94
-
95
+
95
96
  if child = Daemonize.safefork
96
97
  # parent
97
98
  wr.close
98
-
99
+
99
100
  cmd_status = rd.read
100
101
  rd.close
101
-
102
+
102
103
  ::Process.waitpid(child)
103
-
104
+
104
105
  return Marshal.load(cmd_status)
105
-
106
+
106
107
  else
107
108
  # child
108
109
  rd.close
109
-
110
+
110
111
  # create a child in which we can override the stdin, stdout and stderr
111
112
  cmd_out_read, cmd_out_write = IO.pipe
112
113
  cmd_err_read, cmd_err_write = IO.pipe
113
-
114
+
114
115
  pid = fork {
115
116
  # grandchild
116
117
  drop_privileges(options[:uid], options[:gid])
117
-
118
+
118
119
  Dir.chdir(ENV["PWD"] = options[:working_dir]) if options[:working_dir]
119
120
  options[:environment].each { |key, value| ENV[key.to_s] = value.to_s } if options[:environment]
120
-
121
+
121
122
  # close unused fds so ancestors wont hang. This line is the only reason we are not
122
123
  # using something like popen3. If this fd is not closed, the .read call on the parent
123
124
  # will never return because "wr" would still be open in the "exec"-ed cmd
@@ -143,10 +144,10 @@ module Bluepill
143
144
  # we do not use these ends of the pipes in the child
144
145
  cmd_out_write.close
145
146
  cmd_err_write.close
146
-
147
+
147
148
  # wait for the cmd to finish executing and acknowledge it's death
148
149
  ::Process.waitpid(pid)
149
-
150
+
150
151
  # collect stdout, stderr and exitcode
151
152
  result = {
152
153
  :stdout => cmd_out_read.read,
@@ -157,30 +158,30 @@ module Bluepill
157
158
  # We're done with these ends of the pipes as well
158
159
  cmd_out_read.close
159
160
  cmd_err_read.close
160
-
161
+
161
162
  # Time to tell the parent about what went down
162
163
  wr.write Marshal.dump(result)
163
164
  wr.close
164
165
 
165
- exit
166
+ exit
166
167
  end
167
168
  end
168
-
169
+
169
170
  def store
170
171
  @store ||= Hash.new
171
172
  end
172
-
173
+
173
174
  def reset_data
174
175
  store.clear unless store.empty?
175
176
  end
176
-
177
+
177
178
  def ps_axu
178
179
  # TODO: need a mutex here
179
180
  store[:ps_axu] ||= begin
180
181
  # BSD style ps invocation
181
182
  lines = `ps axo pid,ppid,pcpu,rss`.split("\n")
182
183
 
183
- lines.inject(Hash.new) do |mem, line|
184
+ lines.inject(Hash.new) do |mem, line|
184
185
  chunks = line.split(/\s+/)
185
186
  chunks.delete_if {|c| c.strip.empty? }
186
187
  pid = chunks[IDX_MAP[:pid]].strip.to_i
@@ -189,7 +190,7 @@ module Bluepill
189
190
  end
190
191
  end
191
192
  end
192
-
193
+
193
194
  # be sure to call this from a fork otherwise it will modify the attributes
194
195
  # of the bluepill daemon
195
196
  def drop_privileges(uid, gid)
@@ -202,25 +203,25 @@ module Bluepill
202
203
  ::Process::Sys.setuid(uid_num) if uid
203
204
  end
204
205
  end
205
-
206
+
206
207
  def can_write_pid_file(pid_file, logger)
207
208
  FileUtils.touch(pid_file)
208
209
  File.unlink(pid_file)
209
210
  return true
210
-
211
+
211
212
  rescue Exception => e
212
213
  logger.warning "%s - %s" % [e.class.name, e.message]
213
214
  e.backtrace.each {|l| logger.warning l}
214
215
  return false
215
216
  end
216
-
217
+
217
218
  def redirect_io(io_in, io_out, io_err)
218
219
  $stdin.reopen(io_in) if io_in
219
-
220
+
220
221
  if !io_out.nil? && !io_err.nil? && io_out == io_err
221
222
  $stdout.reopen(io_out, APPEND_MODE)
222
223
  $stderr.reopen($stdout)
223
-
224
+
224
225
  else
225
226
  $stdout.reopen(io_out, APPEND_MODE) if io_out
226
227
  $stderr.reopen(io_err, APPEND_MODE) if io_err
@@ -1,45 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  module Bluepill
2
3
  class Trigger
3
4
  @implementations = {}
4
5
  def self.inherited(klass)
5
6
  @implementations[klass.name.split('::').last.underscore.to_sym] = klass
6
7
  end
7
-
8
+
8
9
  def self.[](name)
9
10
  @implementations[name]
10
11
  end
11
12
 
12
13
  attr_accessor :process, :logger, :mutex, :scheduled_events
13
-
14
+
14
15
  def initialize(process, options = {})
15
16
  self.process = process
16
17
  self.logger = options[:logger]
17
18
  self.mutex = Mutex.new
18
19
  self.scheduled_events = []
19
20
  end
20
-
21
+
21
22
  def reset!
22
23
  self.cancel_all_events
23
24
  end
24
-
25
+
25
26
  def notify(transition)
26
27
  raise "Implement in subclass"
27
28
  end
28
-
29
+
29
30
  def dispatch!(event)
30
31
  self.process.dispatch!(event, self.class.name.split("::").last)
31
32
  end
32
-
33
- def deep_copy
34
- # TODO: This is a kludge. Ideally, process templates
35
- # would be facotries, and not a template object.
36
- mutex, @mutex = @mutex, nil
37
- clone = Marshal.load(Marshal.dump(self))
38
- clone.instance_variable_set("@mutex", Monitor.new)
39
- @mutex = mutex
40
- clone
41
- end
42
-
33
+
43
34
  def schedule_event(event, delay)
44
35
  # TODO: maybe wrap this in a ScheduledEvent class with methods like cancel
45
36
  thread = Thread.new(self) do |trigger|
@@ -54,16 +45,16 @@ module Bluepill
54
45
  trigger.logger.err(e.backtrace.join("\n"))
55
46
  end
56
47
  end
57
-
48
+
58
49
  self.scheduled_events.push([event, thread])
59
50
  end
60
-
51
+
61
52
  def cancel_all_events
62
53
  self.logger.info "Canceling all scheduled events"
63
54
  self.mutex.synchronize do
64
55
  self.scheduled_events.each {|_, thread| thread.kill}
65
56
  end
66
57
  end
67
-
58
+
68
59
  end
69
60
  end