ziltoid 1.0.0

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.
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: []