bluepill 0.0.46 → 0.0.47

Sign up to get free protection for your applications and to get access to all the features.
@@ -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