eznemo 0.0.2

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