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.
@@ -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
@@ -0,0 +1,8 @@
1
+ require "thread"
2
+ require "salus/thread/cpu"
3
+ require "salus/thread/monotonictime"
4
+ require "salus/thread/lockable"
5
+ require "salus/thread/observable"
6
+ require "salus/thread/pool"
7
+ require "salus/thread/future"
8
+ require "salus/thread/latch"
@@ -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