eznemo 0.0.2

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: 14365e43e3184d7e2a7d8978b8a8c4fe76d57596
4
+ data.tar.gz: 2d7ca0b5a7fe4f8acdc9b2fa0a87020497b85317
5
+ SHA512:
6
+ metadata.gz: a75aa47b2879c4e018d1613f07db5f604831985d5e3b9b0d2f629ad0e78ad7049305af989f8c9aceb3dda1a5f7b3c31619bfe76eb40e822c9373abef741fe149
7
+ data.tar.gz: 452ce77dbd521523f4c7ecd417a6554f1c7d55026b4dcd53cfdc5d0ce855e4a6d9292c27c9767a826546d83b769d7c5d10356064dbeb937f72d9a12904dfa231
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Ken J.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # eznemo
2
+
3
+ A Ruby gem. It's a simple monitoring engine and stores results in a database. Runs on EventMachine.
4
+
5
+ For reports and alerts, analyze the results in the database.
6
+
7
+ ## Requirements
8
+
9
+ - Ruby 2.0.0 <=
10
+ - eventmachine 1.0 <=
11
+
12
+ ### Data Storage Options
13
+
14
+ Currently, only support MySQL, so you'll also need the following gem:
15
+
16
+ - mysql2 0.4 <=
17
+
18
+ ## Getting Started
19
+
20
+ ### Install
21
+
22
+ ```
23
+ $ gem install eznemo
24
+ ```
25
+
26
+ ### Use
27
+
28
+ ```
29
+ $ eznemo start -c config.yml
30
+ ```
31
+
32
+ ### Examples
33
+
34
+ Config file.
35
+
36
+ ```yaml
37
+ ---
38
+ :datastore:
39
+ :type: :mysql
40
+ :options:
41
+ :host: '127.0.0.1'
42
+ :username: 'user'
43
+ :password: 'paSsw0rd'
44
+ :database: 'eznemo'
45
+ :checks:
46
+ :tags:
47
+ - tag1
48
+ - tag2
49
+ :monitor:
50
+ :path: '/bin/ping'
51
+ :min_interval: 10
52
+ :timeout: 5
53
+ :cmd_opts: '-s 102'
54
+ ```
55
+
56
+ ## Data Structure
57
+
58
+ ### Checks
59
+
60
+ ```ruby
61
+ {
62
+ id: 123, # auto_increment
63
+ name: 'Check name',
64
+ hostname: '192.168.0.111',
65
+ interval: 60, # frequecy this check is run in seconds
66
+ type: 'ping', # or other monitor plugin name
67
+ state: true, # true means active
68
+ tags: '["tag1", "tag2"]',
69
+ options: '-S 192.168.0.11'
70
+ }
71
+ ```
72
+
73
+ ### Results
74
+
75
+ ```ruby
76
+ {
77
+ check_id: 123, # from checks
78
+ timestamp: '2016-04-01 10:00:00 -07:00',
79
+ status: true, # true means OK
80
+ response_ms: 0.012, # in milliseconds
81
+ status_desc: 'OK' # short description of the result
82
+ }
83
+ ```
84
+
85
+ ### MySQL
86
+
87
+ ```sql
88
+ CREATE TABLE `checks` (
89
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
90
+ `name` varchar(255) NOT NULL DEFAULT '',
91
+ `hostname` varchar(255) NOT NULL DEFAULT '',
92
+ `interval` int(11) NOT NULL,
93
+ `type` varchar(255) NOT NULL DEFAULT '',
94
+ `state` tinyint(1) NOT NULL,
95
+ `tags` text,
96
+ `options` text,
97
+ PRIMARY KEY (`id`)
98
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
99
+
100
+ CREATE TABLE `results` (
101
+ `id` int(11) NOT NULL AUTO_INCREMENT,
102
+ `check_id` int(11) NOT NULL,
103
+ `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
104
+ `status` tinyint(1) NOT NULL,
105
+ `response_ms` float NOT NULL DEFAULT '0',
106
+ `status_desc` varchar(255) NOT NULL DEFAULT '',
107
+ PRIMARY KEY (`id`),
108
+ KEY `check_id` (`check_id`),
109
+ KEY `timestamp` (`timestamp`),
110
+ KEY `status` (`status`)
111
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
112
+ ```
data/bin/eznemo ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ require 'kajiki'
3
+ require 'eznemo'
4
+
5
+
6
+ opts = Kajiki.preset_options(:simple, {config: true})
7
+
8
+ Kajiki.run(opts) do |cmd|
9
+ case cmd
10
+ when 'start'
11
+ EzNemo::Reactor.run!(opts)
12
+ end
13
+ end
data/eznemo.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
2
+ require 'eznemo/version'
3
+
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'eznemo'
7
+ s.version = EzNemo::Version
8
+ s.authors = ['Ken J.']
9
+ s.email = ['kenjij@gmail.com']
10
+ s.description = %q{Simple network monitoring}
11
+ s.summary = %q{Simple network monitoring implemented with Ruby.}
12
+ s.homepage = 'https://github.com/kenjij/eznemo'
13
+ s.license = 'MIT'
14
+
15
+ s.files = `git ls-files`.split($/)
16
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ s.require_paths = ['lib']
18
+
19
+ s.required_ruby_version = '>= 2.0.0'
20
+
21
+ s.add_runtime_dependency 'kajiki', '~> 1.1'
22
+ s.add_runtime_dependency 'eventmachine', '~> 1.0'
23
+ end
@@ -0,0 +1,18 @@
1
+ require 'yaml'
2
+
3
+ module EzNemo
4
+
5
+ # Load YAML config file
6
+ # @param path [String] config file
7
+ # @return [Object] config; a shared instance
8
+ def self.load_config(path)
9
+ raise 'config file missing' unless path
10
+ @config ||= YAML.load_file(path)
11
+ end
12
+
13
+ # see #self.load_config
14
+ def self.config
15
+ @config
16
+ end
17
+
18
+ end
@@ -0,0 +1,15 @@
1
+ module EzNemo
2
+
3
+ # @return [Object] data storage object; a shared instance
4
+ def self.datastore
5
+ @datastore ||= DataStore.new
6
+ end
7
+
8
+ # Storage for checks and results
9
+ class DataStore
10
+
11
+ include EzNemo::StorageAdapter
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,115 @@
1
+ module EzNemo
2
+
3
+ # ICMP ping plugin
4
+ class Ping
5
+
6
+ DEFAULT_MIN_INTERVAL = 10
7
+ DEFAULT_TIMEOUT = 5
8
+
9
+ attr_reader :monitor
10
+ attr_reader :config
11
+
12
+ def initialize
13
+ os = RbConfig::CONFIG['host_os']
14
+ case
15
+ when os.include?('darwin')
16
+ @os = 'bsd'
17
+ when os.include?('linux')
18
+ @os = 'linux'
19
+ end
20
+ end
21
+
22
+ # Gets called by Monitor at regisration
23
+ # @return [Symbol]
24
+ def name
25
+ return :ping
26
+ end
27
+
28
+ # Gets called by Monitor after regisration
29
+ # @param mon [Object] parent Monitor object
30
+ def registered(mon)
31
+ @monitor = mon
32
+ @config = EzNemo.config[:monitor][:ping] if EzNemo.config[:monitor]
33
+ @config ||= {}
34
+ @config[:path] ||= 'ping'
35
+ @config[:min_interval] ||= DEFAULT_MIN_INTERVAL
36
+ @config[:timeout] ||= DEFAULT_TIMEOUT
37
+ end
38
+
39
+ # Add a check using this plugin
40
+ # @param check [Hash]
41
+ def add_check(check)
42
+ min = config[:min_interval]
43
+ check[:interval] = min if check[:interval] < min
44
+ EM.add_periodic_timer(check[:interval]) do
45
+ self.send("#{@os}_ping", check)
46
+ end
47
+ end
48
+
49
+ def linux_ping(check)
50
+ result = {
51
+ timestamp: Time.now,
52
+ check_id: check[:id]
53
+ }
54
+ path = config[:path]
55
+ timeout = config[:timeout]
56
+ options = "#{config[:cmd_opts]} #{check[:options]}"
57
+ hostname = check[:hostname]
58
+ cmd = "#{path} -c 1 -nqW #{timeout} #{options} #{hostname}"
59
+ EM.system(cmd) do |output, status|
60
+ case status.exitstatus
61
+ when 0
62
+ expr = /=\s*([0-9\.]+)/
63
+ expr =~ output
64
+ result[:status] = 1
65
+ result[:response_ms] = $1.to_f
66
+ result[:status_desc] = 'OK'
67
+ when 1
68
+ result[:status] = 0
69
+ result[:response_ms] = 0
70
+ result[:status_desc] = 'NG'
71
+ else
72
+ output = 'see log' if output.nil? || output.size == 0
73
+ result[:status] = 0
74
+ result[:response_ms] = 0
75
+ result[:status_desc] = "ERROR: #{output}".chomp
76
+ end
77
+ monitor.report(result)
78
+ end
79
+ end
80
+
81
+ def bsd_ping(check)
82
+ result = {
83
+ timestamp: Time.now,
84
+ check_id: check[:id]
85
+ }
86
+ timeout = config[:timeout] * 1000
87
+ options = "#{config[:cmd_opts]} #{check[:option]}"
88
+ hostname = check[:hostname]
89
+ cmd = "#{path} -c 1 -nqW #{timeout} #{options} #{hostname}"
90
+ EM.system(cmd) do |output, status|
91
+ case status.exitstatus
92
+ when 0
93
+ expr = /=\s*([0-9\.]+)/
94
+ expr =~ output
95
+ result[:status] = 1
96
+ result[:response_ms] = $1.to_f
97
+ result[:status_desc] = 'OK'
98
+ when 2
99
+ result[:status] = 0
100
+ result[:response_ms] = 0
101
+ result[:status_desc] = 'NG'
102
+ else
103
+ result[:status] = 0
104
+ result[:response_ms] = 0
105
+ result[:status_desc] = "ERROR: #{output}".chomp
106
+ end
107
+ monitor.report(result)
108
+ end
109
+ end
110
+
111
+ end
112
+
113
+ monitor.register(Ping.new)
114
+
115
+ end
@@ -0,0 +1,50 @@
1
+ require 'json'
2
+
3
+ module EzNemo
4
+
5
+ # The shared Monitor instance
6
+ # @return [EzNemo::Monitor]
7
+ def self.monitor
8
+ @monitor ||= Monitor.new
9
+ end
10
+
11
+ # Maintains an array of all the monitor plugins
12
+ class Monitor
13
+
14
+ def initialize
15
+ @plugins = {}
16
+ end
17
+
18
+ # Registers a plugin; usually called by the plugin itself
19
+ # @param plugin [Object]
20
+ def register(plugin)
21
+ @plugins[plugin.name] = plugin
22
+ plugin.registered(self)
23
+ end
24
+
25
+ # Starts check loops in the reactor
26
+ # @param checks [Array<Hash, ...>]
27
+ def start_checks(checks)
28
+ cfg_tags = EzNemo.config[:checks][:tags]
29
+ i = 0
30
+ checks.each do |c|
31
+ if cfg_tags
32
+ c[:tags] ? tags = JSON.parse(c[:tags]) : tags = []
33
+ next if (cfg_tags & tags).empty?
34
+ end
35
+ p = @plugins[c[:type].to_sym]
36
+ p.add_check(c)
37
+ i += 1
38
+ end
39
+ puts "#{i} checks activated."
40
+ end
41
+
42
+ # Report result; usually called by the plugin
43
+ # @param result [Hash] :check_id, :timestamp, :status, :response_ms, :status_desc
44
+ def report(result)
45
+ EzNemo.datastore.store_result(result)
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,85 @@
1
+ require 'mysql2/em'
2
+
3
+
4
+ module EzNemo
5
+
6
+ # Defines DataStorage class for MySQL
7
+ module StorageAdapter
8
+
9
+ # Number of records it queues up before writing
10
+ QUEUE_SIZE = 20
11
+
12
+ def initialize
13
+ @results = []
14
+ @opts = EzNemo.config[:datastore][:options]
15
+ @opts[:flags] = Mysql2::Client::MULTI_STATEMENTS
16
+ end
17
+
18
+ # Creates and returns new instance of {Mysql2::Client}
19
+ # @return [Mysql2::Client]
20
+ def database
21
+ Mysql2::Client.new(@opts)
22
+ end
23
+
24
+ # Creates and returns new instance of {Mysql2::EM::Client}
25
+ # @return [Mysql2::EM::Client]
26
+ def emdatabase
27
+ Mysql2::EM::Client.new(@opts)
28
+ end
29
+
30
+ # Returns all active checks
31
+ # @return [Array<Hash, ...>]
32
+ def checks
33
+ q = 'SELECT * FROM checks WHERE state = true'
34
+ database.query(q, {symbolize_keys: true, cast_booleans: true})
35
+ end
36
+
37
+ # Stores a result; into queue first
38
+ # @param result [Hash] (see {EzNemo::Monitor#report})
39
+ def store_result(result)
40
+ @results << result
41
+ if @results.count >= QUEUE_SIZE
42
+ write_results
43
+ end
44
+ end
45
+
46
+ # Write the results to storage from queue
47
+ # @param sync [Boolean] use EM (async) if false
48
+ # @return [Object] Mysql2 client instance
49
+ def write_results(sync = false)
50
+ return nil if @results.empty?
51
+ sync ? db = database : db = emdatabase
52
+ stmt = ''
53
+ @results.each do |r|
54
+ r[:status_desc] = db.escape(r[:status_desc])
55
+ cols = r.keys.join(',')
56
+ vals = r.values.join("','")
57
+ stmt << "INSERT INTO results (#{cols}) VALUES ('#{vals}');"
58
+ end
59
+ @results.clear
60
+ if sync
61
+ db.query(stmt)
62
+ else
63
+ defer = db.query(stmt)
64
+ defer.callback do
65
+ end
66
+ defer.errback do |r|
67
+ puts r.message
68
+ db.close if db.ping
69
+ end
70
+ end
71
+ db
72
+ end
73
+
74
+ # Flush queue to storage
75
+ def flush
76
+ if write_results(true)
77
+ puts "Flushed."
78
+ else
79
+ puts "Nothing to flush."
80
+ end
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -0,0 +1,5 @@
1
+ module EzNemo
2
+
3
+ Version = '0.0.2'
4
+
5
+ end
data/lib/eznemo.rb ADDED
@@ -0,0 +1,53 @@
1
+ require 'eznemo/config'
2
+ require 'eznemo/version'
3
+ require 'eventmachine'
4
+
5
+ module EzNemo
6
+
7
+ # EventMachine reactor
8
+ class Reactor
9
+
10
+ # Start reactor
11
+ # @param opts [Hash] from command line
12
+ # @return [Object]
13
+ def self.run!(opts)
14
+ r = Reactor.new(opts)
15
+ r.run
16
+ r
17
+ end
18
+
19
+ # Usually called by #self.run!
20
+ def initialize(opts)
21
+ c = EzNemo.load_config(opts[:config])
22
+
23
+ require "eznemo/#{c[:datastore][:type].to_s}"
24
+ require 'eznemo/datastore'
25
+
26
+ require 'eznemo/monitor'
27
+ require 'eznemo/monitor/ping'
28
+ end
29
+
30
+ # Usually called by #self.run!
31
+ def run
32
+ ds = EzNemo.datastore
33
+
34
+ Signal.trap('INT') do
35
+ puts 'Interrupted. Flushing...'
36
+ ds.flush
37
+ exit
38
+ end
39
+
40
+ Signal.trap('SIGTERM') do
41
+ puts 'Stopping...'
42
+ ds.flush
43
+ exit
44
+ end
45
+
46
+ EM.run do
47
+ EzNemo.monitor.start_checks(ds.checks)
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eznemo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Ken J.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: kajiki
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: eventmachine
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ description: Simple network monitoring
42
+ email:
43
+ - kenjij@gmail.com
44
+ executables:
45
+ - eznemo
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - LICENSE
50
+ - README.md
51
+ - bin/eznemo
52
+ - eznemo.gemspec
53
+ - lib/eznemo.rb
54
+ - lib/eznemo/config.rb
55
+ - lib/eznemo/datastore.rb
56
+ - lib/eznemo/monitor.rb
57
+ - lib/eznemo/monitor/ping.rb
58
+ - lib/eznemo/mysql.rb
59
+ - lib/eznemo/version.rb
60
+ homepage: https://github.com/kenjij/eznemo
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 2.0.0
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.4.3
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Simple network monitoring implemented with Ruby.
84
+ test_files: []