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
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 0adb5863d10056a25ec392981e682cdc8a27fe88
|
|
4
|
+
data.tar.gz: 54011bd7dc54a824778e604ea85f54040ad1445c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: bfc09ce8d330fea7668c58062fba29cff5d80d6cfd4394fa80f922d6a97d6fe636cb49cef1f2c344d3915578ab715200adb28a871857c9de87b1da2daf53b262
|
|
7
|
+
data.tar.gz: a23b0c69dcd2e6e5f6caf23f8513a5a8678f58857aa30b710652096ef121c0946fa1f4fbf32b85f62e88f2df2621f26f84d5a176deb24a2a08cffa0904e3b5a2
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Salus is licensed under the BSD license.
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
|
4
|
+
modification, are permitted provided that the following conditions are
|
|
5
|
+
met:
|
|
6
|
+
|
|
7
|
+
(1) Redistributions of source code must retain the above copyright
|
|
8
|
+
notice, this list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
(2) Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
notice, this list of conditions and the following disclaimer in
|
|
12
|
+
the documentation and/or other materials provided with the
|
|
13
|
+
distribution.
|
|
14
|
+
|
|
15
|
+
(3) The name of the author may not be used to
|
|
16
|
+
endorse or promote products derived from this software without
|
|
17
|
+
specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
20
|
+
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
21
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
|
|
23
|
+
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
24
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
26
|
+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
27
|
+
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
|
28
|
+
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
29
|
+
POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# Salus
|
|
2
|
+
|
|
3
|
+
Salus is a simple DSL for writing collector agents for different monitoring systems. I'm just tired of rewriting those primitives from scratch for every new check I'm willing to add to a monitoring system.
|
|
4
|
+
|
|
5
|
+
_This is alpha quality software right now, but you might help to improve it_
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add this line to your application's Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'salus'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
And then execute:
|
|
16
|
+
|
|
17
|
+
$ bundle
|
|
18
|
+
|
|
19
|
+
Or install it yourself as:
|
|
20
|
+
|
|
21
|
+
$ gem install salus
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
The gem can be used from your own script or by using CLI.
|
|
26
|
+
|
|
27
|
+
Quick sample:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
require "json"
|
|
31
|
+
default ttl: 60
|
|
32
|
+
var state_file: "system.state.yml"
|
|
33
|
+
|
|
34
|
+
group "cpu" do
|
|
35
|
+
data = File.open("/proc/stat").read.split(/\n/).grep(/^cpu /)
|
|
36
|
+
data.each do |l|
|
|
37
|
+
name, user, nice, csystem, idle, iowait, irq, softirq = l.split(/\s+/)
|
|
38
|
+
|
|
39
|
+
busy = user.to_i + nice.to_i + csystem.to_i + iowait.to_i + irq.to_i + softirq.to_i
|
|
40
|
+
total = busy + idle.to_i
|
|
41
|
+
|
|
42
|
+
counter "busy", value: busy, mute: true
|
|
43
|
+
counter "total", value: total, mute: true
|
|
44
|
+
gauge "usage" do
|
|
45
|
+
value("busy") / value("total") * 100
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
group "memory" do
|
|
51
|
+
data = File.open("/proc/meminfo").read.split(/\n/).grep(/^Mem/)
|
|
52
|
+
data.each do |l|
|
|
53
|
+
name, value = l.match(/Mem(?<name>.+):\s+(?<value>\d+)\s/)[1,2]
|
|
54
|
+
gauge name.downcase, value: value.to_i, mute: true
|
|
55
|
+
end
|
|
56
|
+
gauge "usage" do
|
|
57
|
+
(value("total") - value("free")) / value("total").to_f * 100
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
render do |data|
|
|
62
|
+
iterate(data) do |name, metric|
|
|
63
|
+
puts ({:name => name, :value => metric.value, :timestamp => metric.timestamp, :ttl => metric.ttl}.to_json)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Save it as `sample.salus` and run `salus`.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
$ salus -f sample.salus
|
|
72
|
+
{"name":"cpu.usage","value":6.433521607455635,"timestamp":1510699623.8592708,"ttl":60}
|
|
73
|
+
{"name":"memory.usage","value":25.60121068625779,"timestamp":1510699623.863265,"ttl":60}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Because `cpu.usage` is made of counters, you'll have to run the command at least twice. Be aware of `salus` making `system.state.yml` for persisting it's state. You may redefine file name and location with `-s` switch.
|
|
77
|
+
|
|
78
|
+
You can also run in infinite loop mode
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
$ salus loop -f sample.salus
|
|
82
|
+
{"name":"cpu.usage","value":12.528473606797895,"timestamp":1510700076.4455612,"ttl":60}
|
|
83
|
+
{"name":"memory.usage","value":25.57683405209171,"timestamp":1510700076.447059,"ttl":60}
|
|
84
|
+
{"name":"cpu.usage","value":8.239946939924048,"timestamp":1510700106.3334389,"ttl":60}
|
|
85
|
+
{"name":"memory.usage","value":25.535366304014968,"timestamp":1510700106.33444,"ttl":60}
|
|
86
|
+
{"name":"cpu.usage","value":2.840158185017875,"timestamp":1510700136.364328,"ttl":60}
|
|
87
|
+
{"name":"memory.usage","value":25.537265316671707,"timestamp":1510700136.36533,"ttl":60}
|
|
88
|
+
^C
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
You may invoke several salus scripts at once, just specify all of them in a space delimited list. You might also specify a directory with `Salusfile` or many `*.salus` files. By default `salus` does search `*.salus` and `Salusfile` in the current directory.
|
|
92
|
+
|
|
93
|
+
### Primitives
|
|
94
|
+
|
|
95
|
+
#### Group
|
|
96
|
+
|
|
97
|
+
Group is the base unit of work. You should write your metric collecting code inside groups. Groups can be nested. Top level groups are run in separate threads. Group might include metrics. Groups must be named.
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
group "test" do
|
|
101
|
+
gauge "test1", value: 10
|
|
102
|
+
gauge "test2", value: 30, mute: true
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### Metrics
|
|
107
|
+
|
|
108
|
+
Could be one of the following (mimicking RRDtool data sources):
|
|
109
|
+
* Gauge: values stored as is
|
|
110
|
+
* Derive: a rate of something per second, best suites for values that rarely overflow
|
|
111
|
+
* Counter: almost same as derive, but with overflow detection for 32- and 64-bit counters
|
|
112
|
+
* Absolute: a rate of a counter, which resets on reading
|
|
113
|
+
* Text: just a text stored as is
|
|
114
|
+
|
|
115
|
+
A metric should have a name and a value at the very minimum. You might also specify a custom or default TTL. A metric can be also `mute`, which means it wouldn't appear in the output, unless told to do so.
|
|
116
|
+
|
|
117
|
+
An expired metric is considered invalid, so if you use counter or derive with ttl less than collecting interval, you'll always get nils.
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
group "test" do
|
|
121
|
+
gauge "test1", value: 10, ttl: 50
|
|
122
|
+
counter "test2", value: 10, mute: true
|
|
123
|
+
derive "test3", value: 30, timestamp: Time.now.to_f + 10
|
|
124
|
+
end
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
You might also use a block to calculate metrics value. In this case any exception which happened in the block would be muted, and the nil value returned instead.
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
group "test" do
|
|
131
|
+
# this would always produce nil
|
|
132
|
+
gauge "division by zero" do
|
|
133
|
+
100 / 0
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
#### Renderers
|
|
139
|
+
|
|
140
|
+
A renderer is a class, which is used to render the actual output, would it be just STDOUT, a file or tcp/ip service.
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
render(StdoutRenderer.new(separator: "/"))
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
This code would produce something like that
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
[2017-11-15 02:25:16 +0300] cpu/usage - 4.00
|
|
150
|
+
[2017-11-15 02:25:16 +0300] memory/usage - 26.23
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
You may add more than one renderer at once and send your data to as many monitoring services as you want.
|
|
154
|
+
|
|
155
|
+
Check sample renderers for examples.
|
|
156
|
+
|
|
157
|
+
### Pipeline
|
|
158
|
+
|
|
159
|
+
Salus pipeline is rather straightforward and consists of two stages:
|
|
160
|
+
* Collect data (execute groups' code)
|
|
161
|
+
* Send data (execute renderers' code)
|
|
162
|
+
|
|
163
|
+
You might run it once by cron or in infinite loop mode. Each stage is executed using embed thread pool with pre-set timeouts.
|
|
164
|
+
|
|
165
|
+
Thread pool and timeouts could be configured using `configure`
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
Salus.configure do |config|
|
|
169
|
+
# Thread pool settings
|
|
170
|
+
config.min_threads = (CPU.count / 2 == 0) ? 1 : CPU.count / 2
|
|
171
|
+
config.max_threads = CPU.count * 2
|
|
172
|
+
|
|
173
|
+
config.interval = 30 # Interval between runs in loop mode
|
|
174
|
+
config.tick_timeout = 15 # Data collection timeout
|
|
175
|
+
config.render_timeout = 10 # Data rendering timeout
|
|
176
|
+
config.logger = Logger.new(STDERR) # Default logger
|
|
177
|
+
end
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Zabbix
|
|
181
|
+
|
|
182
|
+
Zabbix uses two stage collecting. First of all, it queries (discovers) the list of objects to be checked. Next, it would ask for exact values of specified metrics of an object one by one. Sometimes this means making a lot of requests to a monitored service. So many script writers use some kind of result caching to lower unnecessary work. Salus also writes a result cache file (`-c` flag). Cache TTL for a metric is a half of it's real TTL or 60 seconds if TTL is unspecified. Upon parameter request it is loaded from cache and if it's expired, whole cache is invalidated and recalculated using salus script.
|
|
183
|
+
|
|
184
|
+
Sample Salus script for collecting CPU usage ratio on Linux for Zabbix Agent is something like that:
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
require "salus/zabbix"
|
|
188
|
+
|
|
189
|
+
default ttl: 60
|
|
190
|
+
var state_file: "cpu.state.yml"
|
|
191
|
+
var zabbix_cache_file: "cpu.cache.yml"
|
|
192
|
+
|
|
193
|
+
discover "cpus" do |data|
|
|
194
|
+
stat = File.open("/proc/stat").read.split(/\n/).grep(/^cpu\d/)
|
|
195
|
+
stat.each do |l|
|
|
196
|
+
name, = l.split(/\s+/)
|
|
197
|
+
data << {"\#{CPUNAME}" => name}
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
group "cpu" do
|
|
202
|
+
stat = File.open("/proc/stat").read.split(/\n/).grep(/^cpu\d/)
|
|
203
|
+
stat.each do |l|
|
|
204
|
+
name, user, nice, csystem, idle, iowait, irq, softirq = l.split(/\s+/)
|
|
205
|
+
|
|
206
|
+
busy = user.to_i + nice.to_i + csystem.to_i + iowait.to_i + irq.to_i + softirq.to_i
|
|
207
|
+
total = busy + idle.to_i
|
|
208
|
+
|
|
209
|
+
counter "busy[#{name}]", value: busy, mute: true
|
|
210
|
+
counter "total[#{name}]", value: total, mute: true
|
|
211
|
+
gauge "usage[#{name}]" do
|
|
212
|
+
value("busy[#{name}]") / value("total[#{name}]") * 100
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
You can run it using command `salus`.
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
$ salus zabbix discover cpus -f zabbix.salus
|
|
222
|
+
{"data":[{"#{CPUNAME}":"cpu0"},{"#{CPUNAME}":"cpu1"},{"#{CPUNAME}":"cpu2"},{"#{CPUNAME}":"cpu3"},{"#{CPUNAME}":"cpu4"},{"#{CPUNAME}":"cpu5"},{"#{CPUNAME}":"cpu6"},{"#{CPUNAME}":"cpu7"}]}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Later you can get a cpu usage ratio
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
$ salus zabbix parameter cpu.usage[cpu5] -f zabbix.salus && sleep 31 && salus zabbix parameter cpu.usage[cpu5] -f zabbix.salus
|
|
229
|
+
8.619550926524335
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**NOTE!** You won't get the result on the first run, because cpu usage on Linux needs to get two points in time to be calculated. `zabbix` subcommand uses caching of the results, so you have to wait for 30 seconds to get next result. But if you'll wait for more than TTL (60 seconds), you'll get empty result again.
|
|
233
|
+
|
|
234
|
+
New dependant items mode of Zabbix 3.4+ is also supported. Output is in JSON.
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
$ salus zabbix bulk cpu -f zabbix.salus
|
|
238
|
+
{"usage[cpu0]":8.362702709145317,"usage[cpu1]":2.2395325673158424,"usage[cpu2]":3.570268951237728,"usage[cpu3]":4.641349636608478,"usage[cpu4]":3.7313418117547834,"usage[cpu5]":4.023359928822469,"usage[cpu6]":3.0834133549568365,"usage[cpu7]":4.47470699450407}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Development
|
|
242
|
+
|
|
243
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
244
|
+
|
|
245
|
+
## Special thanks
|
|
246
|
+
* meh for [ruby-thread](https://github.com/meh/ruby-thread)
|
|
247
|
+
* all folks of [concurrent](https://github.com/ruby-concurrency/concurrent-ruby) project
|
|
248
|
+
* all folks of [thor](https://github.com/erikhuda/thor) project
|
|
249
|
+
|
|
250
|
+
Salus uses portions of code (meh's thread pool and future implementation) and concepts from both project and thor for CLI implementation.
|
|
251
|
+
|
|
252
|
+
## Contributing
|
|
253
|
+
|
|
254
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/divanikus/salus.
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "salus"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/salus
ADDED
data/lib/salus.rb
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
require "salus/version"
|
|
2
|
+
require "salus/logging"
|
|
3
|
+
require "salus/thread"
|
|
4
|
+
require "salus/group"
|
|
5
|
+
require "salus/configuration"
|
|
6
|
+
require "salus/renderer"
|
|
7
|
+
|
|
8
|
+
module Salus
|
|
9
|
+
extend Configuration
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
include Logging
|
|
13
|
+
@@_groups = {}
|
|
14
|
+
@@_renders= []
|
|
15
|
+
@@_opts = {}
|
|
16
|
+
@@_vars = {}
|
|
17
|
+
@@_lazy = []
|
|
18
|
+
|
|
19
|
+
def on_win?
|
|
20
|
+
@@_win ||= !(RUBY_PLATFORM =~ /bccwin|cygwin|djgpp|mingw|mswin|wince/i).nil?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def group(title, &block)
|
|
24
|
+
unless @@_groups.key?(title)
|
|
25
|
+
@@_groups[title] = Group.new(@@_opts, &block)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def groups
|
|
30
|
+
@@_groups
|
|
31
|
+
end
|
|
32
|
+
alias root groups
|
|
33
|
+
|
|
34
|
+
def default(opts)
|
|
35
|
+
return unless opts.is_a?(Hash)
|
|
36
|
+
opts.each do |k, v|
|
|
37
|
+
next if [:value, :timestamp].include?(k)
|
|
38
|
+
@@_opts[k] = v
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def defaults
|
|
43
|
+
@@_opts
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def var(arg, default=nil, &block)
|
|
47
|
+
if arg.is_a?(Hash)
|
|
48
|
+
arg.each {|k, v| @@_vars[k] = v}
|
|
49
|
+
elsif block_given?
|
|
50
|
+
@@_vars[arg.to_sym] = block
|
|
51
|
+
else
|
|
52
|
+
value = @@_vars.fetch(arg.to_sym, default)
|
|
53
|
+
# Dumb lazy loading
|
|
54
|
+
@@_vars[arg.to_sym] = if value.is_a?(Proc)
|
|
55
|
+
begin
|
|
56
|
+
value = value.call
|
|
57
|
+
rescue Exception => e
|
|
58
|
+
log DEBUG, e
|
|
59
|
+
value = default
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
value
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
alias let var
|
|
66
|
+
|
|
67
|
+
def vars
|
|
68
|
+
@@_vars
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def render(obj=nil, &block)
|
|
72
|
+
if block_given?
|
|
73
|
+
@@_renders << BlockRenderer.new(&block)
|
|
74
|
+
else
|
|
75
|
+
unless obj.is_a? Salus::BaseRenderer
|
|
76
|
+
log ERROR, "#{obj.class} must be a subclass of Salus::BaseRenderer"
|
|
77
|
+
return
|
|
78
|
+
end
|
|
79
|
+
@@_renders << obj
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def renders
|
|
84
|
+
@@_renders
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def reset
|
|
88
|
+
@@_groups = {}
|
|
89
|
+
@@_renders = []
|
|
90
|
+
@@_opts = {}
|
|
91
|
+
@@_vars = {}
|
|
92
|
+
@@_lazy = []
|
|
93
|
+
if defined?(@@_pool) && @@_pool.is_a?(Salus::ThreadPool)
|
|
94
|
+
@@_pool.shutdown!
|
|
95
|
+
@@_pool = nil
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def lazy(&block)
|
|
100
|
+
raise ArgumentError, "Block should be given" unless block_given?
|
|
101
|
+
@@_lazy << block
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def lazy_eval
|
|
105
|
+
# Lazy eval blocks once
|
|
106
|
+
return if @@_lazy.empty?
|
|
107
|
+
@@_lazy.each { |block| instance_eval(&block) }
|
|
108
|
+
@@_lazy.clear
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def load(file)
|
|
112
|
+
instance_eval(File.read(file), File.basename(file), 0) if File.exists?(file)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def load_state(&block)
|
|
116
|
+
data = block.call
|
|
117
|
+
return unless data
|
|
118
|
+
return if data.empty?
|
|
119
|
+
lazy_eval
|
|
120
|
+
data.each do |k, v|
|
|
121
|
+
@@_groups[k].load(v) if @@_groups.key?(k)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def save_state(&block)
|
|
126
|
+
data = {}
|
|
127
|
+
@@_groups.each { |k, v| data[k] = v.to_h }
|
|
128
|
+
block.call(data)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def tick
|
|
132
|
+
lazy_eval
|
|
133
|
+
return if @@_groups.empty?
|
|
134
|
+
pause = (Salus.interval - Salus.tick_timeout - Salus.render_timeout) / 2
|
|
135
|
+
pause = 1 if (pause <= 0)
|
|
136
|
+
|
|
137
|
+
latch = CountDownLatch.new(@@_groups.count)
|
|
138
|
+
@@_groups.each do |k, v|
|
|
139
|
+
pool.process do
|
|
140
|
+
begin
|
|
141
|
+
v.tick
|
|
142
|
+
latch.count_down
|
|
143
|
+
rescue Exception => e
|
|
144
|
+
log ERROR, e
|
|
145
|
+
latch.count_down
|
|
146
|
+
end
|
|
147
|
+
end.timeout_after(Salus.tick_timeout)
|
|
148
|
+
end
|
|
149
|
+
latch.wait(Salus.tick_timeout + pause)
|
|
150
|
+
log DEBUG, "Collection finished. Threads: #{pool.spawned} spawned, #{pool.waiting} waiting, #{Thread.list.count} total"
|
|
151
|
+
|
|
152
|
+
return if @@_renders.empty?
|
|
153
|
+
latch = CountDownLatch.new(@@_renders.count)
|
|
154
|
+
@@_renders.each do |v|
|
|
155
|
+
pool.process do
|
|
156
|
+
begin
|
|
157
|
+
v.render(root)
|
|
158
|
+
latch.count_down
|
|
159
|
+
rescue Exception => e
|
|
160
|
+
log ERROR, e
|
|
161
|
+
latch.count_down
|
|
162
|
+
end
|
|
163
|
+
end.timeout_after(Salus.render_timeout)
|
|
164
|
+
end
|
|
165
|
+
latch.wait(Salus.render_timeout + pause)
|
|
166
|
+
log DEBUG, "Rendering finished. Threads: #{pool.spawned} spawned, #{pool.waiting} waiting, #{Thread.list.count} total"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def run
|
|
170
|
+
loop do
|
|
171
|
+
pool.process do
|
|
172
|
+
tick
|
|
173
|
+
end
|
|
174
|
+
sleep Salus.interval
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
protected
|
|
179
|
+
def pool
|
|
180
|
+
@@_pool ||= ThreadPool.new(self.min_threads, self.max_threads).auto_trim!
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|