ziltoid 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 77f322004f95c4414b3364d2f5197ffda8b7dfd4
4
+ data.tar.gz: b47765f8f8d5484e7a70e6dd9a94e6b59242aa14
5
+ SHA512:
6
+ metadata.gz: 3c21b057c2abbe9c46c2b1227bd84ab2e8eea66d8852220e5421b163e98eb97c485ce20e7f9fe812c670e3e98dac881707debb35494062206917164ef6aeedf4
7
+ data.tar.gz: 22f3f11346e327a60d813098d71e6ca254117f126390ffecb791df8c0125f8808f9f6a218b6b97aecf6a330b400efd46e22e0baa999c5a5453e4f2217167e4dd
data/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # ziltoid
2
+
3
+ Cron based monitoring system.
4
+
5
+ There are many softwares that aim to watch processes, keeping them alive and clean. Some of them are well known :
6
+ - god (http://godrb.com)
7
+ - monit (http://mmonit.com/monit/)
8
+ - bluepill (https://github.com/bluepill-rb/bluepill)
9
+ - ... (https://www.ruby-toolbox.com/categories/server_monitoring)
10
+
11
+ All have good and bad sides.
12
+ One of the bad side is that each alternative is based on a deamon that compute data and then sleeps for a while. Who is monitoring this particular deamon ? What if this process suddenly stop ?
13
+ Also, you often need root right to run those tools. On some hosting environments (mainly in shared hosting), this is an issue.
14
+
15
+ Ziltoid is an attempt to solve those issues using the crontab system. It's on every system, it launches a task periodically then waits for an amount of time, it doesn't need monitoring, it can send emails to warn of a an error, and can run any script.
16
+
17
+ ## Usage
18
+
19
+ The principle of Ziltoid is to use the crontab system. You need to create a script that monitor your processes and then configure it in your crontab to run as frequently as possible (every minutes) :
20
+
21
+ * * * * * /bin/bash -l -c 'ruby /path_to_ziltoid.rb'
22
+
23
+ Ziltoid is here to help you build this script.
24
+
25
+ ### Process
26
+
27
+ It encapsulates a process and everything Ziltoid might need to monitor it :
28
+ - start, stop en restart commands
29
+ - pid file path
30
+ - RAM and CPU limits
31
+
32
+ You define one like so :
33
+
34
+ ```ruby
35
+ Ziltoid::Process.new("Lighty", {
36
+ :pid_file => "/home/ror/http/tmp/lighttpd.pid",
37
+ :commands => {
38
+ :start => "/home/ror/bin/lighty start",
39
+ :stop => "/home/ror/bin/lighty stop"
40
+ },
41
+ :limit => {
42
+ :ram => 256,
43
+ :cpu => 10
44
+ }
45
+ })
46
+ ```
47
+
48
+ Processes are seen as `watchables` by Ziltoid. You can build your own watchable objects and passe them to Ziltoid, as long you can call the following methods on them :
49
+ - `start`
50
+ - `stop`
51
+ - `restart`
52
+ - `watch`
53
+
54
+ ### Email Notifier
55
+
56
+ This is a wrapper to email notifications. Every log message of a level higher than Logger::INFO will be sent by email.
57
+
58
+ You can configure a mail like so :
59
+
60
+ ```ruby
61
+ Ziltoid::EmailNotifier.new(
62
+ :via_options => {
63
+ :address => 'smtp.ziltoid.com',
64
+ :port => '25',
65
+ :domain => "ziltoid.com"
66
+ },
67
+ :subject => "[Balloonz] Ziltoid message",
68
+ :to => ['developers@sociabliz.com'],
69
+ :from => 'ziltoidd@ziltoid.com'
70
+ )
71
+ ```
72
+
73
+ The `via_options` accept every options supported by the [pony](https://github.com/benprew/pony) library.
74
+
75
+ You can build your own notifiers (for IRC, XMPP, BaseCamp, Redmine, ....) as long as it respond to the `send` method. This method receives a `message` parameter which is the message to be sent.
76
+
77
+ ### Watcher
78
+
79
+ This is the main object of Ziltoid. You can think of it as a container. With this container, you plug in a Logger, a Notifier and as many watchables as you want. Then, you run the commande of your choice.
80
+
81
+ Commands are :
82
+ - `start`: to start watchable items
83
+ - `stop`: to stop watchable items
84
+ - `restart`: to restart watchable items
85
+ - `watch`: to watch watchable items. IE start them if dead, restart them if of limits
86
+
87
+ You can see a sample Ziltoid script in the `exemple` folder.
88
+
89
+ ## Drawbacks
90
+
91
+ Ziltoid is not yet complet. We are working on other watchables, like disk usage and RAM usage.
92
+ We also want to work on a grace system, where we don't count the RAM and CPU at start of process, and where we wait for a certain number of off-limit mesures to restart a process (and allow burst).
93
+
94
+ One of the bad point of the crontab system is that it doesn't run under the minute. But we think that if our system can't handle a dead process for a minute, there is an issue somewhere (in replication, sizing, ...).
95
+
96
+ ## About Ziltoid
97
+
98
+ It comes from Devin Townsen famous album "Ziltoid the omniscient".
99
+ Ziltoid is omniscient, watches everything and control everything. Even processes.
100
+
101
+ Ziltoid is given to you by [The Social Client](http://www.thesocialclient.com), a digital and CRM consultant agency.
102
+
103
+ ## Licencing
104
+
105
+ Ziltoid is a [Beerware](http://en.wikipedia.org/wiki/Beerware) and is licensed under the terms of the [WTFPL](http://www.wtfpl.net), see the included WTFPL-LICENSE file.
106
+
107
+ If we meet some day, and you think this stuff is worth it, you can buy us a beer in return.
@@ -0,0 +1,53 @@
1
+ require 'ostruct'
2
+ require 'optparse'
3
+
4
+ module Ziltoid
5
+ class CommandParser
6
+
7
+ ALLOWED_COMMANDS = ["watch", "start", "stop", "restart"]
8
+
9
+ # Returns a structure describing the options.
10
+ def self.parse(args)
11
+ runnable = OpenStruct.new
12
+
13
+ helptext = <<-HELP
14
+ Available commands are :
15
+ watch : watches all processes
16
+ start : starts all processes
17
+ stop : stops all processes
18
+ restart : restarts all processes
19
+ HELP
20
+
21
+ opt_parser = OptionParser.new do |opts|
22
+ # Printing generic help at the top of commands summary
23
+ opts.banner = "Usage: ziltoid.rb [options]"
24
+ opts.separator ""
25
+ opts.separator helptext
26
+ opts.separator ""
27
+ opts.separator "Common options :"
28
+
29
+ # No argument, shows at tail. This will print a commands summary.
30
+ opts.on_tail("-h", "--help", "Show this message") do
31
+ puts opts
32
+ exit
33
+ end
34
+ end
35
+
36
+ # Retrieves all arguments except option-like ones (e.g. '-h' or '-v')
37
+ opt_parser.parse!(args)
38
+ # Fetches the first argument as the intended command
39
+ command = args.shift
40
+
41
+ # Making sure the command is valid, otherwise print commands summary
42
+ if command && ALLOWED_COMMANDS.include?(command)
43
+ runnable.command = command
44
+ else
45
+ puts opt_parser.help
46
+ exit
47
+ end
48
+
49
+ runnable
50
+ end # parse()
51
+
52
+ end
53
+ end
@@ -0,0 +1,24 @@
1
+ require 'pony'
2
+
3
+ module Ziltoid
4
+ class EmailNotifier
5
+ attr_accessor :via_options, :from, :to, :subject
6
+
7
+ def initialize(options)
8
+ self.via_options = options[:via_options]
9
+ self.to = options[:to]
10
+ self.from = options[:from]
11
+ self.subject = options[:subject]
12
+ end
13
+
14
+ def send(message)
15
+ Pony.mail(
16
+ :to => self.to,
17
+ :via => :smtp,
18
+ :via_options => self.via_options,
19
+ :from => self.from,
20
+ :subject => self.subject, :body => message
21
+ )
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,106 @@
1
+ module Ziltoid
2
+ class Process
3
+ attr_accessor :name, :ram_limit, :cpu_limit, :start_command, :stop_command, :restart_command, :pid_file
4
+
5
+ WAIT_TIME_BEFORE_CHECK = 1.0
6
+
7
+ def initialize(name, options = {})
8
+ self.name = name
9
+ self.ram_limit = options[:limit] ? options[:limit][:ram] : nil
10
+ self.cpu_limit = options[:limit] ? options[:limit][:cpu] : nil
11
+ self.pid_file = options[:pid_file] || "~/.ziltoid/#{name}.pid"
12
+
13
+ if options[:commands]
14
+ self.start_command = options[:commands][:start] || nil
15
+ self.stop_command = options[:commands][:stop] || nil
16
+ self.restart_command = options[:commands][:restart] || nil
17
+ end
18
+ end
19
+
20
+ def pid
21
+ if self.pid_file && File.exist?(self.pid_file)
22
+ str = File.read(pid_file)
23
+ str.to_i if str.size > 0
24
+ end
25
+ end
26
+
27
+ def alive?
28
+ Ziltoid::System.pid_alive?(self.pid)
29
+ end
30
+
31
+ def dead?
32
+ !alive?
33
+ end
34
+
35
+ def remove_pid_file
36
+ if self.pid_file && File.exist?(self.pid_file)
37
+ File.delete(self.pid_file)
38
+ end
39
+ end
40
+
41
+ def above_cpu_limit?(include_children = true)
42
+ Ziltoid::System.cpu_usage(self.pid, include_children) > self.cpu_limit.to_f
43
+ end
44
+
45
+ def above_ram_limit?(include_children = true)
46
+ Ziltoid::System.ram_usage(self.pid, include_children) > self.ram_limit.to_i * 1024
47
+ end
48
+
49
+ def watch!
50
+ Watcher.log("Ziltoid is watching process #{self.name}")
51
+ if !alive?
52
+ Watcher.log("Process #{self.name} is dead", Logger::WARN)
53
+ return start!
54
+ end
55
+ if above_cpu_limit?
56
+ Watcher.log("Process #{self.name} is above CPU limit (#{self.cpu_limit.to_f})", Logger::WARN)
57
+ return restart!
58
+ end
59
+ if above_ram_limit?
60
+ Watcher.log("Process #{self.name} is above RAM limit (#{self.ram_limit.to_f})", Logger::WARN)
61
+ return restart!
62
+ end
63
+ end
64
+
65
+ def start!
66
+ return if Ziltoid::System.pid_alive?(self.pid)
67
+ Watcher.log("Ziltoid is starting process #{self.name}", Logger::WARN)
68
+ remove_pid_file
69
+ %x(#{self.start_command})
70
+ end
71
+
72
+ def stop!
73
+ Watcher.log("Ziltoid is stoping process #{self.name}", Logger::WARN)
74
+ memoized_pid = self.pid
75
+
76
+ if dead?
77
+ remove_pid_file
78
+ else
79
+
80
+ thread = Thread.new do
81
+ %x(#{self.stop_command})
82
+ sleep(WAIT_TIME_BEFORE_CHECK)
83
+ if alive?
84
+ %x(kill #{memoized_pid})
85
+ sleep(WAIT_TIME_BEFORE_CHECK)
86
+ if alive?
87
+ %x(kill -9 #{memoized_pid})
88
+ sleep(WAIT_TIME_BEFORE_CHECK)
89
+ end
90
+ end
91
+ remove_pid_file if dead?
92
+ end.join
93
+
94
+ end
95
+ end
96
+
97
+ def restart!
98
+ Watcher.log("Ziltoid is restarting process #{self.name}", Logger::WARN)
99
+ alive = self.alive?
100
+ return %x(#{self.restart_command}) if alive && self.restart_command
101
+ stop! if alive
102
+ return start!
103
+ end
104
+
105
+ end
106
+ end
@@ -0,0 +1,77 @@
1
+ module Ziltoid
2
+
3
+ module System
4
+
5
+ # The position of each field in ps output
6
+ PS_FIELDS = [:pid, :ppid, :cpu, :ram]
7
+
8
+ module_function
9
+
10
+ def pid_alive?(pid)
11
+ return false if pid.nil?
12
+ begin
13
+ ::Process.kill(0, pid)
14
+ true
15
+ rescue Errno::EPERM # no permission, but it is definitely alive
16
+ true
17
+ rescue Errno::ESRCH
18
+ false
19
+ end
20
+ end
21
+
22
+ def ps_aux
23
+ # BSD style ps invocation
24
+ processes = `ps axo pid,ppid,pcpu,rss`.split("\n")
25
+
26
+ processes.inject({}) do |result, process|
27
+ info = process.split(/\s+/)
28
+ info.delete_if { |p_info| p_info.strip.empty? }
29
+ info.map! { |p_info| p_info.gsub(",", ".") }
30
+
31
+ info = PS_FIELDS.each_with_index.inject({}) do |info_hash, (field, field_index)|
32
+ info_hash[field] = info[field_index]
33
+ info_hash
34
+ end
35
+
36
+ pid = info[:pid].strip.to_i
37
+ result[pid] = info
38
+ result
39
+ end
40
+ end
41
+
42
+ def cpu_usage(pid, include_children = true)
43
+ ps = ps_aux
44
+ return unless ps[pid]
45
+ cpu_used = ps[pid][:cpu].to_f
46
+
47
+ get_children(pid).each do |child_pid|
48
+ cpu_used += ps[child_pid][:cpu].to_f if ps[child_pid]
49
+ end if include_children
50
+
51
+ cpu_used
52
+ end
53
+
54
+ def ram_usage(pid, include_children = true)
55
+ ps = ps_aux
56
+ return unless ps[pid]
57
+ mem_used = ps[pid][:ram].to_i
58
+
59
+ get_children(pid).each do |child_pid|
60
+ mem_used += ps[child_pid][:ram].to_i if ps[child_pid]
61
+ end if include_children
62
+
63
+ mem_used
64
+ end
65
+
66
+ def get_children(parent_pid)
67
+ child_pids = []
68
+ ps_aux.each_pair do |_pid, info|
69
+ child_pids << info[:pid].to_i if info[:ppid].to_i == parent_pid.to_i
70
+ end
71
+ grand_children = child_pids.map { |pid| get_children(pid) }.flatten
72
+ child_pids.concat grand_children
73
+ end
74
+
75
+ end
76
+
77
+ end
@@ -0,0 +1,86 @@
1
+ require "logger"
2
+
3
+ module Ziltoid
4
+ class Watcher
5
+ attr_accessor :watchlist
6
+
7
+ def logger
8
+ Ziltoid::Watcher.logger
9
+ end
10
+
11
+ def self.logger
12
+ @@logger
13
+ end
14
+
15
+ def notifiers
16
+ Ziltoid::Watcher.notifiers
17
+ end
18
+
19
+ def self.notifiers
20
+ @@notifiers ||= []
21
+ return @@notifiers
22
+ end
23
+
24
+ def self.log(message, level = Logger::INFO)
25
+ @@logger ||= Logger.new($stdout)
26
+ @@logger.add(level, message)
27
+ if level > Logger::INFO
28
+ self.notifiers.each do |n|
29
+ n.send(message)
30
+ end
31
+ end
32
+ end
33
+
34
+ def initialize(options = {})
35
+ self.watchlist ||= {}
36
+ @@logger = options[:logger] || Logger.new($stdout)
37
+ @@logger.progname = options[:progname] || "Ziltoid"
38
+ @@logger.level = options[:log_level] || Logger::INFO
39
+ @@notifiers = options[:notifiers] if options[:notifiers]
40
+ end
41
+
42
+ def add(watchable)
43
+ self.watchlist[watchable.name] = watchable
44
+ end
45
+
46
+ def run!(command = :watch)
47
+ watchlist.values.each do |watchable|
48
+ watchable.send("#{command}!".to_sym)
49
+ end
50
+ end
51
+
52
+ def watch!
53
+ Watcher.log("Ziltoid is now on duty : watching all watchables !")
54
+ run!(:watch)
55
+ end
56
+
57
+ def start!
58
+ Watcher.log("Ziltoid is now on duty : all watchables starting !")
59
+ run!(:start)
60
+ end
61
+
62
+ def stop!
63
+ Watcher.log("Ziltoid is now on duty : all watchables stoping !")
64
+ run!(:stop)
65
+ end
66
+
67
+ def restart!
68
+ Watcher.log("Ziltoid is now on duty : all watchables restarting !")
69
+ run!(:restart)
70
+ end
71
+
72
+ def run(command = :watch)
73
+ case command
74
+ when :watch
75
+ watch!
76
+ when :start
77
+ start!
78
+ when :stop
79
+ stop!
80
+ when :restart
81
+ restart!
82
+ end
83
+ end
84
+
85
+ end
86
+ end
data/lib/ziltoid.rb ADDED
@@ -0,0 +1,3 @@
1
+ Dir.glob(File.join(File.dirname(__FILE__), 'ziltoid', '**')).each do |lib|
2
+ require "#{lib}"
3
+ end
data/ziltoid.gemspec ADDED
@@ -0,0 +1,15 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'ziltoid'
3
+ s.version = '1.0.0'
4
+ s.date = '2014-12-22'
5
+ s.summary = "Ziltoid, crontab based monitoring system"
6
+ s.description = "Ziltoid, crontab based monitoring system"
7
+ s.authors = ["Stéphane Akkaoui", "Vincent Gabou"]
8
+ s.email = 'developers@sociabliz.com'
9
+ s.files = %w(README.md ziltoid.gemspec) + Dir['lib/**/*.rb']
10
+ s.add_dependency 'pony'
11
+
12
+ s.homepage =
13
+ 'http://github.com/meuble/ziltoid'
14
+ s.license = 'WTFPL'
15
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ziltoid
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Stéphane Akkaoui
8
+ - Vincent Gabou
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-12-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: pony
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - '>='
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ description: Ziltoid, crontab based monitoring system
29
+ email: developers@sociabliz.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - README.md
35
+ - lib/ziltoid.rb
36
+ - lib/ziltoid/command_parser.rb
37
+ - lib/ziltoid/email_notifier.rb
38
+ - lib/ziltoid/process.rb
39
+ - lib/ziltoid/system.rb
40
+ - lib/ziltoid/watcher.rb
41
+ - ziltoid.gemspec
42
+ homepage: http://github.com/meuble/ziltoid
43
+ licenses:
44
+ - WTFPL
45
+ metadata: {}
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubyforge_project:
62
+ rubygems_version: 2.4.2
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: Ziltoid, crontab based monitoring system
66
+ test_files: []