salus 0.1.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 +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/LICENSE.md +29 -0
- data/README.md +254 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/salus +12 -0
- data/lib/salus.rb +183 -0
- data/lib/salus/cli.rb +53 -0
- data/lib/salus/cli/baseutils.rb +92 -0
- data/lib/salus/cli/zabbix.rb +150 -0
- data/lib/salus/configuration.rb +38 -0
- data/lib/salus/group.rb +159 -0
- data/lib/salus/logging.rb +15 -0
- data/lib/salus/metric.rb +173 -0
- data/lib/salus/metric/absolute.rb +21 -0
- data/lib/salus/metric/counter.rb +41 -0
- data/lib/salus/metric/derive.rb +36 -0
- data/lib/salus/metric/gauge.rb +5 -0
- data/lib/salus/metric/text.rb +9 -0
- data/lib/salus/renderer.rb +8 -0
- data/lib/salus/renderer/base.rb +50 -0
- data/lib/salus/renderer/block.rb +13 -0
- data/lib/salus/renderer/collectd.rb +21 -0
- data/lib/salus/renderer/graphite.rb +14 -0
- data/lib/salus/renderer/stdout.rb +18 -0
- data/lib/salus/renderer/zabbixbulk.rb +30 -0
- data/lib/salus/renderer/zabbixsender.rb +24 -0
- data/lib/salus/thread.rb +8 -0
- data/lib/salus/thread/cpu.rb +18 -0
- data/lib/salus/thread/future.rb +168 -0
- data/lib/salus/thread/latch.rb +28 -0
- data/lib/salus/thread/lockable.rb +56 -0
- data/lib/salus/thread/monotonictime.rb +15 -0
- data/lib/salus/thread/observable.rb +117 -0
- data/lib/salus/thread/pool.rb +482 -0
- data/lib/salus/version.rb +3 -0
- data/lib/salus/zabbix.rb +30 -0
- data/salus.gemspec +29 -0
- metadata +143 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Salus
|
|
2
|
+
class ZabbixBulkRenderer < BaseRenderer
|
|
3
|
+
def initialize(opts={})
|
|
4
|
+
super(opts)
|
|
5
|
+
@group = opts.fetch(:group, nil)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def render(data)
|
|
9
|
+
# Zabbix 3.4+ with preprocessor
|
|
10
|
+
result = {}
|
|
11
|
+
re = @group.nil? ? // : /^#{Regexp.escape(@group)}\./
|
|
12
|
+
iterate(data) do |name, metric|
|
|
13
|
+
next unless name.match(re)
|
|
14
|
+
name = name.sub(re, '')
|
|
15
|
+
name = name.gsub(/\.\[/, '[')
|
|
16
|
+
|
|
17
|
+
unless metric.timestamp.nil?
|
|
18
|
+
parts = name.split(/\./)
|
|
19
|
+
node = result
|
|
20
|
+
parts[0...-1].each do |part|
|
|
21
|
+
node[part] = {} unless node.key?(part)
|
|
22
|
+
node = node[part]
|
|
23
|
+
end
|
|
24
|
+
node[parts.last] = metric.value
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
STDOUT.puts result.to_json
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Salus
|
|
2
|
+
class ZabbixSenderRenderer < BaseRenderer
|
|
3
|
+
def render(data)
|
|
4
|
+
# Top level groups are considered hostnames
|
|
5
|
+
result = {}
|
|
6
|
+
data.each do |hostname, group|
|
|
7
|
+
iterate(group) do |name, metric|
|
|
8
|
+
unless metric.timestamp.nil?
|
|
9
|
+
timestamp = metric.timestamp.to_i
|
|
10
|
+
name = name.gsub(/\.\[/, '[')
|
|
11
|
+
value = metric.value
|
|
12
|
+
value = '""' if value.nil?
|
|
13
|
+
value = value.to_json if (!value.nil? && metric.is_a?(Salus::Text))
|
|
14
|
+
|
|
15
|
+
result[timestamp] = [] unless result.key?(timestamp)
|
|
16
|
+
result[timestamp] << "#{hostname.dump} #{name} #{timestamp} #{value}"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
# Zabbix requires timestamps to be sorted
|
|
21
|
+
result.keys.sort.each { |k| STDOUT.puts result[k] }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/salus/thread.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Salus
|
|
2
|
+
class CPU
|
|
3
|
+
def self.count
|
|
4
|
+
@count ||= self.get_count
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
def self.get_count
|
|
9
|
+
return Java::Java.lang.Runtime.getRuntime.availableProcessors if RUBY_PLATFORM == "java"
|
|
10
|
+
return File.read('/proc/cpuinfo').scan(/^processor\s*:/).size if File.exist?('/proc/cpuinfo')
|
|
11
|
+
require 'win32ole'
|
|
12
|
+
WIN32OLE.connect("winmgmts://").ExecQuery("select NumberOfLogicalProcessors from Win32_Processor")
|
|
13
|
+
.to_enum.collect(&:NumberOfLogicalProcessors).reduce(:+)
|
|
14
|
+
rescue LoadError
|
|
15
|
+
Integer `sysctl -n hw.ncpu 2>/dev/null` rescue 1
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
3
|
+
# Version 2, December 2004
|
|
4
|
+
#
|
|
5
|
+
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
6
|
+
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
7
|
+
#
|
|
8
|
+
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
9
|
+
#++
|
|
10
|
+
# borrowed from https://github.com/meh/ruby-thread
|
|
11
|
+
require 'weakref'
|
|
12
|
+
|
|
13
|
+
# A future is an object that incapsulates a block which is called in a
|
|
14
|
+
# different thread, upon retrieval the caller gets blocked until the block has
|
|
15
|
+
# finished running, and its result is returned and cached.
|
|
16
|
+
module Salus
|
|
17
|
+
class Future
|
|
18
|
+
include Observable
|
|
19
|
+
include Logging
|
|
20
|
+
Cancel = Class.new(Exception)
|
|
21
|
+
|
|
22
|
+
# Create a future with the passed block and optionally using the passed pool.
|
|
23
|
+
def initialize(pool = nil, &block)
|
|
24
|
+
raise ArgumentError, 'no block given' unless block
|
|
25
|
+
|
|
26
|
+
@mutex = Mutex.new
|
|
27
|
+
|
|
28
|
+
task = proc {
|
|
29
|
+
begin
|
|
30
|
+
deliver block.call
|
|
31
|
+
notify_and_delete_observers(Time.now, @value, nil)
|
|
32
|
+
rescue Exception => e
|
|
33
|
+
@exception = e
|
|
34
|
+
notify_and_delete_observers(Time.now, nil, e)
|
|
35
|
+
log DEBUG, @exception
|
|
36
|
+
deliver nil
|
|
37
|
+
end
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@thread = pool ? pool.process(&task) : Thread.new(&task)
|
|
41
|
+
|
|
42
|
+
ObjectSpace.define_finalizer self, self.class.finalizer(WeakRef.new(@thread))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @private
|
|
46
|
+
def self.finalizer(thread)
|
|
47
|
+
proc {
|
|
48
|
+
if thread.weakref_alive?
|
|
49
|
+
if thread.is_a? Thread
|
|
50
|
+
thread.raise Cancel
|
|
51
|
+
else
|
|
52
|
+
thread.terminate! Cancel
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Check if an exception has been raised.
|
|
59
|
+
def exception?
|
|
60
|
+
@mutex.synchronize {
|
|
61
|
+
instance_variable_defined? :@exception
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Return the raised exception.
|
|
66
|
+
def exception
|
|
67
|
+
@mutex.synchronize {
|
|
68
|
+
@exception
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Check if the future has been called.
|
|
73
|
+
def delivered?
|
|
74
|
+
@mutex.synchronize {
|
|
75
|
+
instance_variable_defined? :@value
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
alias realized? delivered?
|
|
80
|
+
|
|
81
|
+
# Cancel the future, {#value} will yield a Cancel exception
|
|
82
|
+
def cancel
|
|
83
|
+
return self if delivered?
|
|
84
|
+
|
|
85
|
+
@mutex.synchronize {
|
|
86
|
+
if @thread.is_a? Thread
|
|
87
|
+
@thread.raise Cancel
|
|
88
|
+
else
|
|
89
|
+
@thread.terminate! Cancel
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
@exception = Cancel.new
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
self
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Check if the future has been cancelled
|
|
99
|
+
def cancelled?
|
|
100
|
+
@mutex.synchronize {
|
|
101
|
+
@exception.is_a? Cancel
|
|
102
|
+
}
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Get the value of the future, if it's not finished running this call will block.
|
|
106
|
+
#
|
|
107
|
+
# In case the block raises an exception, it will be raised, the exception is cached
|
|
108
|
+
# and will be raised every time you access the value.
|
|
109
|
+
#
|
|
110
|
+
# An optional timeout can be passed which will return nil if nothing has been
|
|
111
|
+
# delivered.
|
|
112
|
+
def value(timeout = nil)
|
|
113
|
+
raise @exception if exception?
|
|
114
|
+
|
|
115
|
+
return @value if delivered?
|
|
116
|
+
|
|
117
|
+
@mutex.synchronize {
|
|
118
|
+
cond.wait(@mutex, *timeout)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if exception?
|
|
122
|
+
raise @exception
|
|
123
|
+
elsif delivered?
|
|
124
|
+
return @value
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
alias ~ value
|
|
129
|
+
|
|
130
|
+
# Do the same as {#value}, but return nil in case of exception.
|
|
131
|
+
def value!(timeout = nil)
|
|
132
|
+
begin
|
|
133
|
+
value(timeout)
|
|
134
|
+
rescue Exception
|
|
135
|
+
nil
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
alias ! value!
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
def cond?
|
|
143
|
+
instance_variable_defined? :@cond
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def cond
|
|
147
|
+
@cond ||= ConditionVariable.new
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def deliver (value)
|
|
151
|
+
return if delivered?
|
|
152
|
+
|
|
153
|
+
@mutex.synchronize {
|
|
154
|
+
@value = value
|
|
155
|
+
|
|
156
|
+
cond.broadcast if cond?
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
self
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
class ThreadPool
|
|
164
|
+
def future(&block)
|
|
165
|
+
Future.new(self, &block)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Salus
|
|
2
|
+
# Based on code from https://github.com/ruby-concurrency/concurrent-ruby/
|
|
3
|
+
class CountDownLatch
|
|
4
|
+
include Lockable
|
|
5
|
+
|
|
6
|
+
def initialize(to=1)
|
|
7
|
+
synchronize { @count = to.to_i }
|
|
8
|
+
raise ArgumentError, "cannot count down from negative integer" unless @count >= 0
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def count_down
|
|
12
|
+
synchronize do
|
|
13
|
+
@count -= 1 if @count > 0
|
|
14
|
+
broadcast if @count == 0
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def count
|
|
19
|
+
synchronize { @count }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def wait(timeout=nil)
|
|
23
|
+
synchronize do
|
|
24
|
+
wait_until(timeout) { @count == 0 }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Salus
|
|
2
|
+
# Based on code from https://github.com/ruby-concurrency/concurrent-ruby/
|
|
3
|
+
module Lockable
|
|
4
|
+
def synchronize
|
|
5
|
+
if __lock.owned?
|
|
6
|
+
yield
|
|
7
|
+
else
|
|
8
|
+
__lock.synchronize { yield }
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def signal
|
|
13
|
+
__condition.signal
|
|
14
|
+
self
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def broadcast
|
|
18
|
+
__condition.broadcast
|
|
19
|
+
self
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def wait_until(timeout=nil, &condition)
|
|
23
|
+
if timeout
|
|
24
|
+
wait_until = MonotonicTime.get + timeout
|
|
25
|
+
loop do
|
|
26
|
+
now = MonotonicTime.get
|
|
27
|
+
res = condition.call
|
|
28
|
+
return res if now >= wait_until || res
|
|
29
|
+
__wait(wait_until - now)
|
|
30
|
+
end
|
|
31
|
+
else
|
|
32
|
+
__wait(timeout) until condition.call
|
|
33
|
+
true
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def wait(timeout=nil)
|
|
38
|
+
__wait(timeout)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
protected
|
|
42
|
+
def __lock
|
|
43
|
+
@__lock__ ||= ::Mutex.new
|
|
44
|
+
@__lock__
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def __condition
|
|
48
|
+
@__condition__ ||= ::ConditionVariable.new
|
|
49
|
+
@__condition__
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def __wait(timeout=nil)
|
|
53
|
+
__condition.wait __lock, timeout
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Salus
|
|
2
|
+
# Monotonic time if possible
|
|
3
|
+
class MonotonicTime
|
|
4
|
+
# Get monotonic time
|
|
5
|
+
if defined?(Process::CLOCK_MONOTONIC)
|
|
6
|
+
def self.get
|
|
7
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
8
|
+
end
|
|
9
|
+
else
|
|
10
|
+
def self.get
|
|
11
|
+
Time.now.to_f
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
module Salus
|
|
2
|
+
# Loosely based on code from https://github.com/ruby-concurrency/concurrent-ruby/
|
|
3
|
+
class ObserversSet
|
|
4
|
+
include Lockable
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
synchronize { @observers = {} }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add(observer=nil, func=:update, &block)
|
|
11
|
+
if observer.nil? && block.nil?
|
|
12
|
+
raise ArgumentError, 'should pass observer as a first argument or block'
|
|
13
|
+
elsif observer && block
|
|
14
|
+
raise ArgumentError, 'cannot provide both an observer and a block'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
if block
|
|
18
|
+
observer = block
|
|
19
|
+
func = :call
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
synchronize do
|
|
23
|
+
new_observers = @observers.dup
|
|
24
|
+
new_observers[observer] = func
|
|
25
|
+
@observers = new_observers
|
|
26
|
+
observer
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def delete(observer)
|
|
31
|
+
synchronize do
|
|
32
|
+
new_observers = @observers.dup
|
|
33
|
+
new_observers.delete(observer)
|
|
34
|
+
@observers = new_observers
|
|
35
|
+
observer
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def delete_all
|
|
40
|
+
synchronize { @observers = {} }
|
|
41
|
+
self
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def notify(*args, &block)
|
|
45
|
+
obs = synchronize { @observers }
|
|
46
|
+
notify_to(obs, *args, &block)
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def notify_and_delete(*args, &block)
|
|
51
|
+
old = synchronize do
|
|
52
|
+
old = @observers
|
|
53
|
+
@observers = {}
|
|
54
|
+
old
|
|
55
|
+
end
|
|
56
|
+
notify_to(old, *args, &block)
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def count
|
|
61
|
+
synchronize { @observers.count }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
def notify_to(obs, *args, &block)
|
|
66
|
+
raise ArgumentError.new('cannot give arguments and a block') if block_given? && !args.empty?
|
|
67
|
+
obs.each do |observer, function|
|
|
68
|
+
args = yield if block_given?
|
|
69
|
+
observer.send(function, *args)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
module Observable
|
|
75
|
+
def add_observer(observer=nil, func=:update, &block)
|
|
76
|
+
observers.add(observer, func, &block)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def with_observer(observer=nil, func=:update, &block)
|
|
80
|
+
observers.add(observer, func, &block)
|
|
81
|
+
self
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def count_observers
|
|
85
|
+
observers.count
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def delete_observer(observer)
|
|
89
|
+
observers.delete(observer)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def delete_observers
|
|
93
|
+
observers.delete_all
|
|
94
|
+
self
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def notify_observers(*args, &block)
|
|
98
|
+
observers.notify(*args, &block)
|
|
99
|
+
self
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def notify_and_delete_observers(*args, &block)
|
|
103
|
+
observers.notify_and_delete(*args, &block)
|
|
104
|
+
self
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
protected
|
|
108
|
+
def observers
|
|
109
|
+
@__observers__ ||= ObserversSet.new
|
|
110
|
+
@__observers__
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def observers=(obs)
|
|
114
|
+
@__observers__ = obs
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|