procmon 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +44 -0
- data/bin/placeholder +0 -0
- data/lib/procmon/.dsl.rb.swp +0 -0
- data/lib/procmon/dsl/process_factory.rb +41 -0
- data/lib/procmon/dsl.rb +7 -0
- data/lib/procmon/notifiers/email.rb +16 -0
- data/lib/procmon/notifiers/notifier.rb +9 -0
- data/lib/procmon/notifiers.rb +5 -0
- data/lib/procmon/process.rb +81 -0
- data/lib/procmon/process_conditions/mem_usage.rb +55 -0
- data/lib/procmon/process_conditions/process_condition.rb +25 -0
- data/lib/procmon/process_conditions.rb +12 -0
- data/lib/procmon/process_notification.rb +14 -0
- data/lib/procmon/system.rb +38 -0
- data/lib/procmon/version.rb +3 -0
- data/lib/procmon.rb +13 -0
- data/lib/test.rb +36 -0
- data/procmon.gemspec +24 -0
- metadata +92 -0
data/README
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
Example of how to use Procmon
|
2
|
+
|
3
|
+
require 'procmon'
|
4
|
+
|
5
|
+
|
6
|
+
notifier = Proc.new do
|
7
|
+
puts "I NEED TO SEND OUT AN EMAIL"
|
8
|
+
end
|
9
|
+
|
10
|
+
notifier2 = Proc.new do
|
11
|
+
puts "I NEED TO SEND OUT AN SMS"
|
12
|
+
end
|
13
|
+
|
14
|
+
# action be be a Proc object. You can define whatever you want to do
|
15
|
+
Procmon.process("Mail") do |process|
|
16
|
+
process.checks :mem_usage, :above => 100.megabytes, :actions => [notifier, notifier2]
|
17
|
+
end
|
18
|
+
|
19
|
+
# You can specify a code block as well
|
20
|
+
Procmon.process("Mail") do |process|
|
21
|
+
process.pid = 29124
|
22
|
+
process.checks :mem_usage, :above => 100.megabytes do
|
23
|
+
puts "I'm in your base"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Using built-in email notifier
|
28
|
+
NOTIFICATION_TARGET='ddao@example.com'
|
29
|
+
Procmon.process("Mail") do |process|
|
30
|
+
process.checks :mem_usage, :above => 100.megabytes, :actions => [Procmon::Notifiers::Email]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Would be nice if we can do this as well
|
34
|
+
# Procmon.process("Mail") do |process|
|
35
|
+
# process.checks :mem_usage, :above => 100.megabytes do |action|
|
36
|
+
# action.perform(notifier)
|
37
|
+
# action.perform(Procmon::Notifiers::Email, 'someone@example.com')
|
38
|
+
# end
|
39
|
+
# process.checks :mem_usage, :above => 100.megabytes, :actions => [notifier, notifier2]
|
40
|
+
# process.checks :mem_usage, :above => 100.megabytes, :actions => [Procmon::Notifiers::Email]
|
41
|
+
#end
|
42
|
+
|
43
|
+
|
44
|
+
|
data/bin/placeholder
ADDED
File without changes
|
Binary file
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Procmon
|
2
|
+
class ProcessFactory
|
3
|
+
attr_reader :attributes, :name, :checks
|
4
|
+
def initialize(name, attributes, process_block)
|
5
|
+
@checks = {}
|
6
|
+
@name = name
|
7
|
+
@attributes = attributes
|
8
|
+
if process_block.arity == 0
|
9
|
+
instance_eval &process_block
|
10
|
+
else
|
11
|
+
instance_exec(self, &process_block)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def method_missing(name, *args)
|
16
|
+
if args.size == 1 && name.to_s =~ /^(.*)=$/
|
17
|
+
@attributes[$1.to_sym] = args.first
|
18
|
+
elsif args.size == 1
|
19
|
+
@attributes[name.to_sym] = args.first
|
20
|
+
elsif args.size == 0 && name.to_s =~ /^(.*)!$/
|
21
|
+
@attributes[$1.to_sym] = true
|
22
|
+
elsif args.empty? && @attributes.key?(name.to_sym)
|
23
|
+
@attributes[name.to_sym]
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def checks(name, options = {}, &block)
|
30
|
+
if block
|
31
|
+
options[:actions] ||= []
|
32
|
+
options[:actions] << block
|
33
|
+
end
|
34
|
+
@checks[name] = options
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_process
|
38
|
+
Process.new(@name, @checks, @attributes)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/procmon/dsl.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Procmon
|
2
|
+
module Notifiers
|
3
|
+
class Email < Notifier
|
4
|
+
def self.notify(notification, target=nil)
|
5
|
+
if target.nil? && !defined?(NOTIFICATION_TARGET)
|
6
|
+
warn "Don't know where to send the notification"
|
7
|
+
elsif target.nil?
|
8
|
+
target = NOTIFICATION_TARGET
|
9
|
+
end
|
10
|
+
|
11
|
+
puts "Need to email #{target} the following"
|
12
|
+
puts notification
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Procmon
|
2
|
+
class Process
|
3
|
+
CONFIGURABLE_ATTRIBUTES = [
|
4
|
+
:pid_file,
|
5
|
+
:pid_command,
|
6
|
+
:pid
|
7
|
+
]
|
8
|
+
attr_accessor *CONFIGURABLE_ATTRIBUTES
|
9
|
+
attr_accessor :name, :checks
|
10
|
+
|
11
|
+
def initialize(name, checks, options={})
|
12
|
+
@name = name
|
13
|
+
@checks = []
|
14
|
+
|
15
|
+
CONFIGURABLE_ATTRIBUTES.each do |attribute_name|
|
16
|
+
self.send("#{attribute_name}=", options[attribute_name]) if options.has_key?(attribute_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
if @pid_command.nil? or @pid_command.empty?
|
20
|
+
@pid_command = "pidof"
|
21
|
+
end
|
22
|
+
|
23
|
+
if @pid.nil?
|
24
|
+
@pid = fetch_pid
|
25
|
+
end
|
26
|
+
|
27
|
+
checks.each do |name, opts|
|
28
|
+
self.add_check(name, opts)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def run_checks
|
33
|
+
@checks.each do |check|
|
34
|
+
value = check.run(pid)
|
35
|
+
condition_met = check.check(value)
|
36
|
+
perform_actions(check) if condition_met
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_check(name, options)
|
41
|
+
self.checks << ProcessConditions[name].new(options)
|
42
|
+
end
|
43
|
+
|
44
|
+
def perform_actions(check)
|
45
|
+
return if check.actions.nil? or check.actions.empty?
|
46
|
+
|
47
|
+
check.actions.each do |action|
|
48
|
+
if action.kind_of?(Proc)
|
49
|
+
action.call(self, check)
|
50
|
+
elsif action.kind_of?(Class) && action.respond_to?('notify')
|
51
|
+
notification = ProcessNotification.new(self, check)
|
52
|
+
action.notify(notification)
|
53
|
+
else
|
54
|
+
warn "Don't know what to do for #{action}."
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def fetch_pid
|
61
|
+
pid_from_command || pid_from_file
|
62
|
+
end
|
63
|
+
|
64
|
+
def pid_from_file
|
65
|
+
if pid_file
|
66
|
+
if File.exists?(pid_file)
|
67
|
+
str = File.read(pid_file)
|
68
|
+
str.to_i if str.size > 0
|
69
|
+
else
|
70
|
+
logger.warning("pid_file #{pid_file} does not exist or cannot be read")
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def pid_from_command
|
77
|
+
pid = %x{#{pid_command} #{@name}}.strip
|
78
|
+
(pid =~ /\A\d+\z/) ? pid.to_i : nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Procmon
|
2
|
+
module ProcessConditions
|
3
|
+
class MemUsage < ProcessCondition
|
4
|
+
MB = 1024 ** 2
|
5
|
+
FORMAT_STR = "%d%s"
|
6
|
+
MB_LABEL = "MB"
|
7
|
+
KB_LABEL = "KB"
|
8
|
+
|
9
|
+
attr_reader :actions
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@options = options
|
13
|
+
@actions = options[:actions]
|
14
|
+
@below = options[:below]
|
15
|
+
@above = options[:above]
|
16
|
+
if @below && @above && @below < @above
|
17
|
+
raise "Invalid range for mem check condition"
|
18
|
+
elsif @below.nil? && @above.nil?
|
19
|
+
raise "Invalid range for mem check condition"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def run(pid)
|
24
|
+
# rss is on the 5th col
|
25
|
+
System.memory_usage(pid).to_f
|
26
|
+
end
|
27
|
+
|
28
|
+
def check(value)
|
29
|
+
if @below && @above
|
30
|
+
value.kilobytes < @below && value.kilobytes > @above
|
31
|
+
elsif @below
|
32
|
+
value.kilobytes < @below
|
33
|
+
elsif @above
|
34
|
+
value.kilobytes > @above
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def format_value(value)
|
39
|
+
if value.kilobytes >= MB
|
40
|
+
FORMAT_STR % [(value / 1024).round, MB_LABEL]
|
41
|
+
else
|
42
|
+
FORMAT_STR % [value, KB_LABEL]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def description
|
47
|
+
ret = "Memory usage is "
|
48
|
+
conditions = {}
|
49
|
+
conditions[:below] = @options[:below] if @options[:below]
|
50
|
+
conditions[:above] = @options[:above] if @options[:above]
|
51
|
+
ret += conditions.inspect
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Procmon
|
2
|
+
module ProcessConditions
|
3
|
+
class ProcessCondition
|
4
|
+
def initialize(options = {})
|
5
|
+
@options = options
|
6
|
+
end
|
7
|
+
|
8
|
+
def run(pid)
|
9
|
+
raise "Implement in subclass!"
|
10
|
+
end
|
11
|
+
|
12
|
+
def check(value)
|
13
|
+
raise "Implement in subclass!"
|
14
|
+
end
|
15
|
+
|
16
|
+
def format_value(value)
|
17
|
+
value
|
18
|
+
end
|
19
|
+
|
20
|
+
def description
|
21
|
+
raise "Implement in subclass!"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Procmon
|
2
|
+
module ProcessConditions
|
3
|
+
def self.[](name)
|
4
|
+
const_get(name.to_s.camelcase)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require "procmon/process_conditions/process_condition"
|
10
|
+
Dir["#{File.dirname(__FILE__)}/process_conditions/*.rb"].each do |pc|
|
11
|
+
require pc
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Procmon
|
2
|
+
class ProcessNotification
|
3
|
+
def initialize(process, check)
|
4
|
+
hostname = `hostname`.strip
|
5
|
+
@msg = ""
|
6
|
+
@msg += "Hostname: #{hostname}" + "\n"
|
7
|
+
@msg += "Process: #{process.name}" + "\n"
|
8
|
+
@msg += "Description: #{check.description}"
|
9
|
+
end
|
10
|
+
def to_s
|
11
|
+
@msg
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Procmon
|
2
|
+
# Copied from Bluepill code
|
3
|
+
module System
|
4
|
+
extend self
|
5
|
+
|
6
|
+
# The position of each field in ps output
|
7
|
+
IDX_MAP = {
|
8
|
+
:pid => 0,
|
9
|
+
:ppid => 1,
|
10
|
+
:pcpu => 2,
|
11
|
+
:rss => 3
|
12
|
+
}
|
13
|
+
|
14
|
+
def store
|
15
|
+
@store ||= Hash.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def memory_usage(pid)
|
19
|
+
ps_axu[pid] && ps_axu[pid][IDX_MAP[:rss]].to_f
|
20
|
+
end
|
21
|
+
|
22
|
+
def ps_axu
|
23
|
+
# TODO: need a mutex here
|
24
|
+
store[:ps_axu] ||= begin
|
25
|
+
# BSD style ps invocation
|
26
|
+
lines = `ps axo pid,ppid,pcpu,rss`.split("\n")
|
27
|
+
|
28
|
+
lines.inject(Hash.new) do |mem, line|
|
29
|
+
chunks = line.split(/\s+/)
|
30
|
+
chunks.delete_if {|c| c.strip.empty? }
|
31
|
+
pid = chunks[IDX_MAP[:pid]].strip.to_i
|
32
|
+
mem[pid] = chunks
|
33
|
+
mem
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/procmon.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
require 'active_support/core_ext/hash'
|
4
|
+
require 'active_support/core_ext/numeric'
|
5
|
+
require 'active_support/duration'
|
6
|
+
|
7
|
+
require 'procmon/system'
|
8
|
+
require 'procmon/process'
|
9
|
+
require 'procmon/dsl'
|
10
|
+
require 'procmon/dsl/process_factory'
|
11
|
+
require 'procmon/process_conditions'
|
12
|
+
require 'procmon/process_notification'
|
13
|
+
require 'procmon/notifiers'
|
data/lib/test.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'procmon'
|
2
|
+
|
3
|
+
NOTIFICATION_TARGET='ddao@example.com'
|
4
|
+
|
5
|
+
notifier = Proc.new do
|
6
|
+
puts "I NEED TO SEND OUT AN EMAIL"
|
7
|
+
end
|
8
|
+
|
9
|
+
notifier2 = Proc.new do
|
10
|
+
puts "I NEED TO SEND OUT AN EMAIL 2"
|
11
|
+
end
|
12
|
+
|
13
|
+
Procmon.process("Mail") do |process|
|
14
|
+
process.pid = 29124
|
15
|
+
process.checks :mem_usage, :above => 100.megabytes do
|
16
|
+
puts "I'm in your base"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Procmon.process("Mail") do |process|
|
21
|
+
process.checks :mem_usage, :above => 100.megabytes, :actions => [notifier, notifier2]
|
22
|
+
end
|
23
|
+
|
24
|
+
Procmon.process("Mail") do |process|
|
25
|
+
process.checks :mem_usage, :above => 100.megabytes, :actions => [Procmon::Notifiers::Email]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Would be nice if we can do this
|
29
|
+
# Procmon.process("Mail") do |process|
|
30
|
+
# process.checks :mem_usage, :above => 100.megabytes do |action|
|
31
|
+
# action.perform(notifier)
|
32
|
+
# action.perform(Procmon::Notifiers::Email, 'someone@eharmony.com')
|
33
|
+
# end
|
34
|
+
# process.checks :mem_usage, :above => 100.megabytes, :actions => [notifier, notifier2]
|
35
|
+
# process.checks :mem_usage, :above => 100.megabytes, :actions => [Procmon::Notifiers::Email]
|
36
|
+
#end
|
data/procmon.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
require "procmon/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "procmon"
|
7
|
+
s.version = Procmon::VERSION.dup
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Darren Dao"]
|
10
|
+
s.email = ["darrendao@gmail.com"]
|
11
|
+
s.homepage = "http://github.com/darrendao/procmon"
|
12
|
+
s.summary = %q{A process monitor written in Ruby. Concepts and design are based on Bluepill and God, but with emphasis on simplicity and extensibility.}
|
13
|
+
s.description = %q{Procmon allows you to check on processes and then perform arbitary actions based on the result of the checks. For example, you can send notification when mem usage is too high, or you can restart a process if it's dead, etc. It is not meant to run as a daemon. It is designed to be invoked manually or via cron.}
|
14
|
+
|
15
|
+
s.add_dependency 'activesupport', '>= 3.0.0'
|
16
|
+
s.add_dependency 'i18n', '>= 0.5.0'
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
s.extra_rdoc_files = ["README"]
|
23
|
+
end
|
24
|
+
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: procmon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Darren Dao
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2011-11-15 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 3.0.0
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: i18n
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.5.0
|
34
|
+
version:
|
35
|
+
description: Procmon allows you to check on processes and then perform arbitary actions based on the result of the checks. For example, you can send notification when mem usage is too high, or you can restart a process if it's dead, etc. It is not meant to run as a daemon. It is designed to be invoked manually or via cron.
|
36
|
+
email:
|
37
|
+
- darrendao@gmail.com
|
38
|
+
executables:
|
39
|
+
- placeholder
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files:
|
43
|
+
- README
|
44
|
+
files:
|
45
|
+
- README
|
46
|
+
- bin/placeholder
|
47
|
+
- lib/procmon.rb
|
48
|
+
- lib/procmon/.dsl.rb.swp
|
49
|
+
- lib/procmon/dsl.rb
|
50
|
+
- lib/procmon/dsl/process_factory.rb
|
51
|
+
- lib/procmon/notifiers.rb
|
52
|
+
- lib/procmon/notifiers/email.rb
|
53
|
+
- lib/procmon/notifiers/notifier.rb
|
54
|
+
- lib/procmon/process.rb
|
55
|
+
- lib/procmon/process_conditions.rb
|
56
|
+
- lib/procmon/process_conditions/mem_usage.rb
|
57
|
+
- lib/procmon/process_conditions/process_condition.rb
|
58
|
+
- lib/procmon/process_notification.rb
|
59
|
+
- lib/procmon/system.rb
|
60
|
+
- lib/procmon/version.rb
|
61
|
+
- lib/test.rb
|
62
|
+
- procmon.gemspec
|
63
|
+
has_rdoc: true
|
64
|
+
homepage: http://github.com/darrendao/procmon
|
65
|
+
licenses: []
|
66
|
+
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: "0"
|
77
|
+
version:
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: "0"
|
83
|
+
version:
|
84
|
+
requirements: []
|
85
|
+
|
86
|
+
rubyforge_project:
|
87
|
+
rubygems_version: 1.3.5
|
88
|
+
signing_key:
|
89
|
+
specification_version: 3
|
90
|
+
summary: A process monitor written in Ruby. Concepts and design are based on Bluepill and God, but with emphasis on simplicity and extensibility.
|
91
|
+
test_files: []
|
92
|
+
|