apolo 0.0.3

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.
@@ -0,0 +1,47 @@
1
+ # Apolo
2
+
3
+ A lightweight framework based on a hexagonal architecture for building automation, monitoring, and metrics plugins in ruby.
4
+
5
+ The goals of the framework:
6
+ 1. Ruby is an object-oriented language with great support for functional programming, and I want to make the most of that to keep apolo's code easy to change.
7
+ 2. Clean and well-organised
8
+ I want a structure that communicates what each part of the system is doing.
9
+ 3. DRY - The focus is on the domain classes so I can re-use them for Chef, Nagios, collectd, and what ever else I come across.
10
+
11
+ [![Gem Version](http://img.shields.io/gem/v/apolo.svg)][gem]
12
+ [![Build Status](http://img.shields.io/SteelHouseLabs/apolo/apolo.svg)][travis]
13
+ [![Dependency Status](http://img.shields.io/gemnasium/SteelHouseLabs/apolo.svg)][gemnasium]
14
+ [![Code Climate](http://img.shields.io/codeclimate/github/SteelHouseLabs/apolo.svg)][codeclimate]
15
+
16
+ [gem]: https://rubygems.org/gems/apolo
17
+ [travis]: http://travis-ci.org/SteelHouseLabs/apolo
18
+ [gemnasium]: https://gemnasium.com/SteelHouseLabs/apolo
19
+ [codeclimate]: https://codeclimate.com/github/SteelHouseLabs/apolo
20
+
21
+ ## Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ ```ruby
26
+ gem 'apolo'
27
+ ```
28
+
29
+ And then execute:
30
+
31
+ $ bundle
32
+
33
+ Or install it yourself as:
34
+
35
+ $ gem install apolo
36
+
37
+ ## Usage
38
+
39
+ TODO: Write usage instructions here
40
+
41
+ ## Contributing
42
+
43
+ 1. Fork it ( https://github.com/[my-github-username]/apolo/fork )
44
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
45
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
46
+ 4. Push to the branch (`git push origin my-new-feature`)
47
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,29 @@
1
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
2
+
3
+ require "bundler"
4
+ require "thor/rake_compat"
5
+
6
+ class Default < Thor
7
+ include Thor::RakeCompat
8
+ Bundler::GemHelper.install_tasks
9
+
10
+ desc "build", "Build apolo-#{Apolo::VERSION}.gem into the pkg directory"
11
+ def build
12
+ Rake::Task["build"].execute
13
+ end
14
+
15
+ desc "install", "Build and install apolo-#{Apolo::VERSION}.gem into system gems"
16
+ def install
17
+ Rake::Task["install"].execute
18
+ end
19
+
20
+ desc "release", "Create tag v#{Apolo::VERSION} and build and push apolo-#{Apolo::VERSION}.gem to Rubygems"
21
+ def release
22
+ Rake::Task["release"].execute
23
+ end
24
+
25
+ desc "spec", "Run RSpec code examples"
26
+ def spec
27
+ exec "rspec spec"
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'apolo/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'apolo'
8
+ spec.version = Apolo::VERSION
9
+ spec.authors = ['Efrén Fuentes', 'Thomas Vincent']
10
+ spec.email = ['thomasvincent@steelhouselabs.com']
11
+ spec.summary = %q{Metric, Monitoring & automation services framework.}
12
+ spec.description = %q{Metric, Monitoring & automation services framework.}
13
+ spec.homepage = ''
14
+ spec.license = 'Apache 2.0'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = %w[lib]
20
+ spec.required_rubygems_version = ">= 1.3.5"
21
+ spec.summary = spec.description
22
+ spec.test_files = Dir.glob("spec/**/*")
23
+ spec.add_development_dependency 'bundler', '~> 1.7'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_dependency 'sequel', '~> 4.14'
26
+ spec.add_dependency 'sqlite3', '~> 1.3'
27
+ spec.add_dependency 'cupsffi', '~> 0.1'
28
+ spec.add_dependency "logger-better"
29
+ spec.add_dependency "tnt"
30
+ end
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+ require 'apolo'
3
+ require 'optparse'
4
+
5
+ options = { critical: 101, warning: 101 }
6
+
7
+ begin
8
+ OptionParser.new do |opts|
9
+ opts.on('-c', '--critical MAX', 'Max cpu usage percentage for critical') { |v| options[:critical] = v }
10
+ opts.on('-w', '--warning MAX', 'Max cpu usage percentage for warning') { |v| options[:warning] = v }
11
+ end.parse!
12
+ rescue Exception => msg
13
+ puts msg
14
+ exit
15
+ end
16
+
17
+ # Supress warning messages.
18
+ original_verbose, $VERBOSE = $VERBOSE, nil
19
+ @@options = options
20
+ # Activate warning messages again.
21
+ $VERBOSE = original_verbose
22
+
23
+ class CheckCpuSocket < Apolo::Monitor
24
+ name 'CPU_Socket'
25
+
26
+ # Nagios notifier
27
+ notify Apolo::Notifiers::Nagios, file: 'nagios.cmd',\
28
+ host: 'localhost',\
29
+ service: 'CPU_Socket',\
30
+ warning: @@options[:warning].to_i,\
31
+ critical: @@options[:critical].to_i
32
+
33
+ run do
34
+ # create new CpuSocket for calculations
35
+ cpu_socket = Apolo::Domains::CpuSocket.new
36
+
37
+ # get percentage of usage for each socket
38
+ cpu_usage = cpu_socket.cpu_usage
39
+
40
+ # send notify to nagios
41
+ (0..cpu_socket.sockets).each do |socket|
42
+ notify message: "Socket #{socket} usage #{cpu_usage[socket]}", value: cpu_usage[socket]
43
+ end
44
+
45
+ end
46
+ end
47
+
48
+ # create monitor and run it
49
+ monitor = CheckCpuSocket.new
50
+ monitor.run
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ require 'apolo'
3
+ require 'optparse'
4
+
5
+ options = { critical: 120, warning: 120 }
6
+
7
+ begin
8
+ OptionParser.new do |opts|
9
+ opts.on('-c', '--critical MAX', 'Max numbers of minutes on queue for critical') { |v| options[:critical] = v }
10
+ opts.on('-w', '--warning MAX', 'Max numbers of minutes on queue for warning') { |v| options[:warning] = v }
11
+ opts.on('-p' '--printer name', 'Printer name to check for jobs') { |v| options[:printer] = v }
12
+ end.parse!
13
+ rescue Exception => msg
14
+ puts msg
15
+ exit
16
+ end
17
+
18
+ # Supress warning messages.
19
+ original_verbose, $VERBOSE = $VERBOSE, nil
20
+ @@options = options
21
+ # Activate warning messages again.
22
+ $VERBOSE = original_verbose
23
+
24
+
25
+ class CheckCups < Apolo::Monitor
26
+ name 'CUPS'
27
+
28
+ # Notifiers
29
+ notify Apolo::Notifiers::Nagios, file: 'nagios.cmd',\
30
+ host: 'localhost',\
31
+ service: 'CUPS',\
32
+ warning: @@options[:warning].to_i,\
33
+ critical: @@options[:critical].to_i
34
+
35
+ run do
36
+ # create new Cups instance
37
+ cups = Apolo::Domains::Cups.new(@@options[:printer_name])
38
+
39
+ # send notify to nagios
40
+ if cups.jobs_count > 0
41
+ notify message: "Last job #{cups.minutes} minutes ago: #{cups.job[:id]} - #{cups.job[:dest]} - #{cups.job[:user]} - #{Time.at(cups.job[:creation_time])}", value: cups.minutes
42
+ else
43
+ notify message: 'Not jobs found', value: cups.minutes
44
+ end
45
+
46
+ end
47
+ end
48
+
49
+ # create monitor and run it
50
+ monitor = CheckCups.new
51
+ monitor.run
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+ require 'apolo'
3
+ require 'optparse'
4
+
5
+ options = { critical: 120, warning: 120, ipv6: false }
6
+
7
+ begin
8
+ OptionParser.new do |opts|
9
+ opts.on('-c', '--critical MAX', 'Max numbers of open sockets for critical') { |v| options[:critical] = v }
10
+ opts.on('-w', '--warning MAX', 'Max numbers of open sockets for warning') { |v| options[:warning] = v }
11
+ opts.on('-6' '--ipv6 (true|false)', 'Use IP6 or not') { options[:ipv6] = true }
12
+ end.parse!
13
+ rescue Exception => msg
14
+ puts msg
15
+ exit
16
+ end
17
+
18
+ # Supress warning messages.
19
+ original_verbose, $VERBOSE = $VERBOSE, nil
20
+ @@options = options
21
+ # Activate warning messages again.
22
+ $VERBOSE = original_verbose
23
+
24
+
25
+ class CheckTCPSockets < Apolo::Monitor
26
+ name 'TCP_Sockets'
27
+
28
+ # Notifiers
29
+ notify Apolo::Notifiers::Nagios, file: 'nagios.cmd',\
30
+ host: 'localhost',\
31
+ service: 'TCP_Sockets',\
32
+ warning: @@options[:warning].to_i,\
33
+ critical: @@options[:critical].to_i
34
+
35
+ run do
36
+ # get family options
37
+ family = @@options[:ipv6] ? :INET6 : :INET
38
+
39
+ sockets = Apolo::Domains::TCPSockets.connections(family)
40
+
41
+ # send notify to nagios
42
+ notify message: "#{sockets.count} connections #{family}", value: sockets.count
43
+
44
+ end
45
+ end
46
+
47
+ # create monitor and run it
48
+ monitor = CheckTCPSockets.new
49
+ monitor.run
@@ -0,0 +1,6 @@
1
+ require 'apolo/version'
2
+ require 'apolo/monitor'
3
+ require 'apolo/reader'
4
+ require 'apolo/reader_config'
5
+ require 'apolo/notifiers'
6
+ require 'apolo/domains'
@@ -0,0 +1,3 @@
1
+ require 'apolo/domains/cpu_socket'
2
+ require 'apolo/domains/cups'
3
+ require 'apolo/domains/tcp_sockets'
@@ -0,0 +1,97 @@
1
+ module Apolo
2
+ module Domains
3
+ # CpuSocket
4
+ #
5
+ # Get information about percentage of usage for each cpu on all sockets
6
+ class CpuSocket
7
+ def initialize
8
+ @init_stats = statistics_of_process
9
+ sleep 1
10
+ @end_stats = statistics_of_process
11
+ end
12
+
13
+ # Get number of cpus for each socket
14
+ #
15
+ # @return [Integer] the number of cpus for sockets
16
+ def cpu_socket
17
+ cpus / (sockets + 1)
18
+ end
19
+
20
+ # Get usage for cpus
21
+ #
22
+ # @return [Integer] the usage for cpus
23
+ def usage_sum(cores, stats)
24
+ usage_sum = 0
25
+
26
+ cores.each do |core|
27
+ line = stats[core + 1].split(' ')
28
+ usage_sum = line[1].to_i + line[2].to_i + line[3].to_i
29
+ end
30
+
31
+ usage_sum
32
+ end
33
+
34
+ # Get total of process
35
+ #
36
+ # @return [Integer] the total of process
37
+ def proc_total(cores, stats)
38
+ proc_total = 0
39
+ (1..4).each do |i|
40
+ cores.each do |core|
41
+ line = stats[core + 1].split(' ')
42
+ proc_total += line[i].to_i
43
+ end
44
+ end
45
+ proc_total
46
+ end
47
+
48
+ # Get usage for each cpu
49
+ #
50
+ # @return [Array] the percentage of usage for each cpu
51
+ def cpu_usage
52
+ cpu_usage = []
53
+ (0..sockets).each do |socket|
54
+ cores = (socket * cpu_socket..socket * cpu_socket + (cpu_socket - 1))
55
+
56
+ init_usage = usage_sum(cores, @init_stats)
57
+ end_usage = usage_sum(cores, @end_stats)
58
+
59
+ proc_usage = end_usage - init_usage
60
+
61
+ init_total = proc_total(cores, @init_stats)
62
+ end_total = proc_total(cores, @end_stats)
63
+
64
+ proctotal = (end_total - init_total)
65
+
66
+ usage = (proc_usage.to_f / proctotal.to_f)
67
+
68
+ cpu_usage[socket] = (100 * usage).to_f
69
+ end
70
+ cpu_usage
71
+ end
72
+
73
+ # Get number of sockets
74
+ #
75
+ # @return [Integer] the number of sockets on system
76
+ def sockets
77
+ File.readlines('/proc/cpuinfo').grep(/^physical id/).last.split(' ')[3].to_i
78
+ end
79
+
80
+ # Get number of cpus
81
+ #
82
+ # @return [Integer] the number of cpus on system
83
+ def cpus
84
+ File.readlines('/proc/cpuinfo').grep(/^processor/).count
85
+ end
86
+
87
+ private
88
+
89
+ # Get statistics of process
90
+ #
91
+ # @return [Array] the statistics of process
92
+ def statistics_of_process
93
+ File.readlines('/proc/stat')
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,35 @@
1
+ require 'cupsffi'
2
+
3
+ module Apolo
4
+ module Domains
5
+ class Cups
6
+ attr_accessor :jobs_count, :minutes, :job
7
+ def initialize(printer_name)
8
+ if printer_name.nil?
9
+ printer = CupsPrinter.new(printers.first)
10
+ @printer_name = printer.name
11
+ else
12
+ @printer_name = printer_name
13
+ end
14
+
15
+ get_data
16
+ end
17
+
18
+ def printers
19
+ CupsPrinter.get_all_printer_names
20
+ end
21
+
22
+ def get_data
23
+ pointer = FFI::MemoryPointer.new :pointer
24
+ @jobs_count = CupsFFI::cupsGetJobs(pointer, @printer_name, 0, CupsFFI::CUPS_WHICHJOBS_ACTIVE)
25
+ @job = CupsFFI::CupsJobS.new(pointer.get_pointer(0))
26
+
27
+ if @jobs_count > 0
28
+ @minutes = (Time.now - Time.at(@job[:creation_time])).to_i / 60
29
+ else
30
+ @minutes = 0
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,13 @@
1
+ require 'socket'
2
+
3
+ module Apolo
4
+ module Domains
5
+ class TCPSockets
6
+ class << self
7
+ def connections(family)
8
+ Socket.getaddrinfo('localhost', nil, family)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,143 @@
1
+ module Apolo
2
+ class Monitor
3
+ class << self
4
+ def reader_templates
5
+ @reader_templates || []
6
+ end
7
+
8
+ def notifier_templates
9
+ @notifier_templates || []
10
+ end
11
+
12
+ def name_template
13
+ @name_template || self.to_s
14
+ end
15
+
16
+ def running_template
17
+ @running_template
18
+ end
19
+
20
+ # Set the name of the app. Can be used by notifiers in order to have
21
+ # a better description of the service in question.
22
+ #
23
+ # @param [String, #read] name The name to be given to a Apolo-based
24
+ # class.
25
+ def name(val = nil)
26
+ @name_template = val if val
27
+ @name_template
28
+ end
29
+
30
+ # Register a reader in the list of readers.
31
+ #
32
+ # @param [Hash{Scout => String}, #read] reader_description A hash
33
+ # containing Reader class as key and its description as a value.
34
+ #
35
+ # @yield Block to be evaluated to configure the current {Reader}.
36
+ def using(reader_description, &block)
37
+ @reader_templates ||= []
38
+ @reader_templates << {
39
+ :reader_description => reader_description,
40
+ :block => block
41
+ }
42
+ end
43
+
44
+ # Register a notifier class in the list of notifications.
45
+ #
46
+ # @param [Class, #read] class A class that will be used to issue
47
+ # a notification. The class must accept a configuration hash in the
48
+ # constructor and also implement a #notify method that will receive an
49
+ # outpost instance. See {Apolo::Notifiers::Console} for an example.
50
+ #
51
+ # @param [Hash, #read] options Options that will be used to configure the
52
+ # notification class.
53
+ def notify(notifier, options={})
54
+ @notifier_templates ||= []
55
+ @notifier_templates << {:notifier => notifier, :options => options}
56
+ end
57
+
58
+ def run(&block)
59
+ @running_template = block
60
+ end
61
+ end
62
+
63
+ # Returns all the registered readers.
64
+ attr_reader :readers
65
+
66
+ # Returns all the registered notifiers.
67
+ attr_reader :notifiers
68
+
69
+ # Reader/setter for the name of this monitor
70
+ attr_accessor :name
71
+
72
+ # New instance of a Application-based class.
73
+ def initialize
74
+ @readers = Hash.new { |h, k| h[k] = [] }
75
+ @notifiers = {}
76
+ @name = self.class.name_template
77
+ @running = self.class.running_template
78
+
79
+ # Register readers
80
+ self.class.reader_templates.each do |template|
81
+ add_reader(template[:reader_description], &template[:block])
82
+ end
83
+
84
+ self.class.notifier_templates.each do |template|
85
+ add_notifier(template[:notifier], template[:options])
86
+ end
87
+ end
88
+
89
+ # @see Monitor#using
90
+ def add_reader(reader_description, &block)
91
+ config = ReaderConfig.new
92
+ config.instance_exec(&block) if block
93
+
94
+ reader_description.each do |reader, description|
95
+ @readers[reader] << {
96
+ :description => description,
97
+ :config => config
98
+ }
99
+ end
100
+ end
101
+
102
+ # @see Monitor#notify
103
+ def add_notifier(notifier_name, options)
104
+ @notifiers[notifier_name] = options
105
+ end
106
+
107
+ def run
108
+ instance_exec &@running
109
+ end
110
+
111
+ def notify(data)
112
+ message = data[:message]
113
+ value = data[:value]
114
+
115
+ unless message && value
116
+ raise ArgumentError, 'You need to set :message and :value to send notify.'
117
+ end
118
+
119
+ @notifiers.each do |notifier, options|
120
+ # .dup is NOT reliable
121
+ options_copy = Marshal.load(Marshal.dump(options))
122
+ notifier.new(options_copy).notify(@name, message, value)
123
+ end
124
+ end
125
+
126
+ def get_data(reader_exec)
127
+ @readers.each do |reader, configurations|
128
+ if configurations.first[:description] == reader_exec
129
+ return run_reader(reader, configurations.last[:config])
130
+ end
131
+ end
132
+ raise ArgumentError, "Can't found #{reader_exec} reader."
133
+ end
134
+
135
+ private
136
+
137
+ def run_reader(reader, config)
138
+ reader_instance = reader.new(config)
139
+
140
+ reader_instance.execute
141
+ end
142
+ end
143
+ end