solanum 0.2.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +51 -45
- data/bin/solanum +11 -53
- data/lib/solanum.rb +109 -54
- data/lib/solanum/config.rb +67 -74
- data/lib/solanum/output/print.rb +18 -0
- data/lib/solanum/output/riemann.rb +20 -0
- data/lib/solanum/schedule.rb +55 -0
- data/lib/solanum/source.rb +13 -106
- data/lib/solanum/source/certificate.rb +88 -0
- data/lib/solanum/source/cpu.rb +119 -0
- data/lib/solanum/source/diskstats.rb +118 -0
- data/lib/solanum/source/load.rb +44 -0
- data/lib/solanum/source/memory.rb +70 -0
- data/lib/solanum/source/network.rb +65 -0
- data/lib/solanum/source/uptime.rb +28 -0
- data/lib/solanum/util.rb +40 -0
- metadata +31 -16
- checksums.yaml +0 -7
- data/lib/solanum/matcher.rb +0 -70
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'solanum/source'
|
2
|
+
require 'solanum/util'
|
3
|
+
|
4
|
+
class Solanum::Source::Load < Solanum::Source
|
5
|
+
attr_reader :load_states
|
6
|
+
|
7
|
+
STAT_FILE = '/proc/loadavg'
|
8
|
+
|
9
|
+
|
10
|
+
def initialize(opts)
|
11
|
+
super(opts)
|
12
|
+
@load_states = opts['load_states'] || {}
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def collect!
|
17
|
+
events = []
|
18
|
+
|
19
|
+
loadavg = File.read(STAT_FILE).chomp.split(' ')
|
20
|
+
|
21
|
+
load1m = loadavg[0].to_f
|
22
|
+
|
23
|
+
events << {
|
24
|
+
service: 'process load',
|
25
|
+
metric: load1m,
|
26
|
+
state: state_over(@load_states, load1m),
|
27
|
+
}
|
28
|
+
|
29
|
+
running, count = *loadavg[3].split('/')
|
30
|
+
|
31
|
+
events << {
|
32
|
+
service: 'process running',
|
33
|
+
metric: running.to_i,
|
34
|
+
}
|
35
|
+
|
36
|
+
events << {
|
37
|
+
service: 'process count',
|
38
|
+
metric: count.to_i,
|
39
|
+
}
|
40
|
+
|
41
|
+
events
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'solanum/source'
|
2
|
+
require 'solanum/util'
|
3
|
+
|
4
|
+
class Solanum::Source::Memory < Solanum::Source
|
5
|
+
attr_reader :thresholds, :swap_thresholds
|
6
|
+
|
7
|
+
|
8
|
+
def initialize(opts)
|
9
|
+
super(opts)
|
10
|
+
@thresholds = opts['thresholds'] || {}
|
11
|
+
@swap_thresholds = opts['swap_thresholds'] || {}
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def collect!
|
16
|
+
events = []
|
17
|
+
|
18
|
+
meminfo = Hash.new(0)
|
19
|
+
File.readlines('/proc/meminfo').each do |line|
|
20
|
+
measure, quantity = *line.chomp.split(/: +/)
|
21
|
+
value, unit = *quantity.split(' ')
|
22
|
+
meminfo[measure] = value.to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
mem_total = meminfo['MemTotal'].to_f
|
26
|
+
record_usage = lambda do |type|
|
27
|
+
if meminfo[type]
|
28
|
+
usage_pct = meminfo[type]/mem_total.to_f
|
29
|
+
events << {
|
30
|
+
service: "memory #{type.downcase}",
|
31
|
+
metric: usage_pct,
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
total_used = mem_total - meminfo['MemFree']
|
37
|
+
mem_used = total_used - meminfo['Buffers'] - meminfo['Cached']
|
38
|
+
usage = mem_used/mem_total
|
39
|
+
events << {
|
40
|
+
service: 'memory usage',
|
41
|
+
metric: usage,
|
42
|
+
state: state_over(@thresholds, usage),
|
43
|
+
}
|
44
|
+
|
45
|
+
events << {
|
46
|
+
service: 'memory buffers',
|
47
|
+
metric: meminfo['Buffers']/mem_total
|
48
|
+
}
|
49
|
+
|
50
|
+
cached = meminfo['Cached'] + meminfo['SReclaimable'] - meminfo['Shmem']
|
51
|
+
events << {
|
52
|
+
service: 'memory cached',
|
53
|
+
metric: cached/mem_total
|
54
|
+
}
|
55
|
+
|
56
|
+
if meminfo['SwapTotal'] && meminfo['SwapTotal'] > 0
|
57
|
+
swap_total = meminfo['SwapTotal']
|
58
|
+
swap_free = meminfo['SwapFree']
|
59
|
+
usage = 1.0 - swap_free/swap_total.to_f
|
60
|
+
events << {
|
61
|
+
service: 'swap usage',
|
62
|
+
metric: usage,
|
63
|
+
state: state_over(@swap_thresholds, usage),
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
events
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'solanum/source'
|
2
|
+
|
3
|
+
# bytes - The total number of bytes of data transmitted or received by the interface.
|
4
|
+
# packets - The total number of packets of data transmitted or received by the interface.
|
5
|
+
# errs - The total number of transmit or receive errors detected by the device driver.
|
6
|
+
# drop - The total number of packets dropped by the device driver.
|
7
|
+
# fifo - The number of FIFO buffer errors.
|
8
|
+
# frame - The number of packet framing errors.
|
9
|
+
# colls - The number of collisions detected on the interface.
|
10
|
+
# compressed - The number of compressed packets transmitted or received by the device driver. (This appears to be unused in the 2.2.15 kernel.)
|
11
|
+
# carrier - The number of carrier losses detected by the device driver.
|
12
|
+
# multicast - The number of multicast frames transmitted or received by the device driver.
|
13
|
+
class Solanum::Source::Network < Solanum::Source
|
14
|
+
attr_reader :interfaces, :detailed
|
15
|
+
|
16
|
+
STAT_FILE = '/proc/net/dev'
|
17
|
+
|
18
|
+
FIELDS = %w{
|
19
|
+
rx_bytes rx_packets rx_errs rx_drop rx_fifo rx_frame rx_compressed rx_multicast
|
20
|
+
tx_bytes tx_packets tx_errs tx_drop tx_fifo tx_colls tx_carrier tx_compressed
|
21
|
+
}
|
22
|
+
|
23
|
+
SIMPLE_FIELDS = %w{rx_bytes rx_packets tx_bytes tx_packets}
|
24
|
+
|
25
|
+
|
26
|
+
def initialize(opts)
|
27
|
+
super(opts)
|
28
|
+
@interfaces = opts['interfaces'] || []
|
29
|
+
@detailed = opts['detailed'] || false
|
30
|
+
@last = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def parse_stats(line)
|
35
|
+
columns = line.strip.split(/\s+/)
|
36
|
+
iface = columns.shift.chomp(':')
|
37
|
+
return iface, Hash[FIELDS.zip(columns.map(&:to_i))]
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def collect!
|
42
|
+
events = []
|
43
|
+
|
44
|
+
File.readlines(STAT_FILE).drop(2).each do |line|
|
45
|
+
iface, stats = parse_stats(line)
|
46
|
+
|
47
|
+
if @interfaces.empty? || @interfaces.include?(iface)
|
48
|
+
if @last[iface]
|
49
|
+
FIELDS.each do |field|
|
50
|
+
next unless @detailed || SIMPLE_FIELDS.include?(field)
|
51
|
+
diff = stats[field] - @last[iface][field]
|
52
|
+
events << {
|
53
|
+
service: "net #{iface} #{field.gsub('_', ' ')}",
|
54
|
+
metric: diff,
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
@last[iface] = stats
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
events
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'solanum/source'
|
2
|
+
require 'solanum/util'
|
3
|
+
|
4
|
+
class Solanum::Source::Uptime < Solanum::Source
|
5
|
+
|
6
|
+
STAT_FILE = '/proc/uptime'
|
7
|
+
|
8
|
+
|
9
|
+
def initialize(opts)
|
10
|
+
super(opts)
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
def collect!
|
15
|
+
events = []
|
16
|
+
|
17
|
+
uptime = File.read(STAT_FILE).split(' ').first.to_f
|
18
|
+
|
19
|
+
events << {
|
20
|
+
service: 'uptime',
|
21
|
+
metric: uptime,
|
22
|
+
description: "Up for #{duration_str(uptime)}",
|
23
|
+
}
|
24
|
+
|
25
|
+
events
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/lib/solanum/util.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# Return a human-friendly duration string for the given duration in seconds.
|
2
|
+
def duration_str(duration)
|
3
|
+
days = (duration/86400).to_i
|
4
|
+
hours = ((duration % 86400)/3600).to_i
|
5
|
+
minutes = ((duration % 3600)/60).to_i
|
6
|
+
seconds = (duration % 60).to_i
|
7
|
+
hms = "%02d:%02d:%02d" % [hours, minutes, seconds]
|
8
|
+
|
9
|
+
if 0 < days
|
10
|
+
"#{days} days, #{hms}"
|
11
|
+
else
|
12
|
+
hms
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
# Calculate the state of a metric by comparing it to the given thresholds. The
|
18
|
+
# metric is compared to each threshold in turn, largest to smallest. The first
|
19
|
+
# threshold the metric is larger than is returned, or the 'min_sate' is
|
20
|
+
# returned.
|
21
|
+
def state_over(thresholds, metric, min_state='ok')
|
22
|
+
thresholds.sort_by {|e| -e[1] }.each do |threshold_entry|
|
23
|
+
key, threshold = *threshold_entry
|
24
|
+
return key if threshold <= metric
|
25
|
+
end
|
26
|
+
return min_state
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
# Calculate the state of a metric by comparing it to the given thresholds. The
|
31
|
+
# metric is compared to each threshold in turn, smallest to largest. The first
|
32
|
+
# threshold the metric is smaller than is returned, or the 'max_state' is
|
33
|
+
# returned.
|
34
|
+
def state_under(thresholds, metric, max_state='ok')
|
35
|
+
thresholds.sort_by {|e| e[1] }.each do |threshold_entry|
|
36
|
+
key, threshold = *threshold_entry
|
37
|
+
return key if threshold > metric
|
38
|
+
end
|
39
|
+
return max_state
|
40
|
+
end
|
metadata
CHANGED
@@ -1,64 +1,79 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solanum
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Greg Look
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2017-12-23 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: riemann-client
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
16
18
|
requirements:
|
17
|
-
- - '>='
|
19
|
+
- - ! '>='
|
18
20
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.2.
|
21
|
+
version: 0.2.6
|
20
22
|
type: :runtime
|
21
23
|
prerelease: false
|
22
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
23
26
|
requirements:
|
24
|
-
- - '>='
|
27
|
+
- - ! '>='
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.2.
|
29
|
+
version: 0.2.6
|
27
30
|
description:
|
28
|
-
email: greg@
|
31
|
+
email: greg@greglook.net
|
29
32
|
executables:
|
30
33
|
- solanum
|
31
34
|
extensions: []
|
32
35
|
extra_rdoc_files: []
|
33
36
|
files:
|
34
|
-
- lib/solanum/config.rb
|
35
|
-
- lib/solanum/matcher.rb
|
36
37
|
- lib/solanum/source.rb
|
38
|
+
- lib/solanum/schedule.rb
|
39
|
+
- lib/solanum/config.rb
|
40
|
+
- lib/solanum/source/cpu.rb
|
41
|
+
- lib/solanum/source/memory.rb
|
42
|
+
- lib/solanum/source/diskstats.rb
|
43
|
+
- lib/solanum/source/uptime.rb
|
44
|
+
- lib/solanum/source/network.rb
|
45
|
+
- lib/solanum/source/load.rb
|
46
|
+
- lib/solanum/source/certificate.rb
|
47
|
+
- lib/solanum/output/print.rb
|
48
|
+
- lib/solanum/output/riemann.rb
|
49
|
+
- lib/solanum/util.rb
|
37
50
|
- lib/solanum.rb
|
38
51
|
- bin/solanum
|
39
52
|
- README.md
|
40
53
|
homepage: https://github.com/greglook/solanum
|
41
54
|
licenses:
|
42
55
|
- Public Domain
|
43
|
-
metadata: {}
|
44
56
|
post_install_message:
|
45
57
|
rdoc_options: []
|
46
58
|
require_paths:
|
47
59
|
- lib
|
48
60
|
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
49
62
|
requirements:
|
50
|
-
- - '>='
|
63
|
+
- - ! '>='
|
51
64
|
- !ruby/object:Gem::Version
|
52
|
-
version: 1.9.
|
65
|
+
version: 1.9.3
|
53
66
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
54
68
|
requirements:
|
55
|
-
- - '>='
|
69
|
+
- - ! '>='
|
56
70
|
- !ruby/object:Gem::Version
|
57
71
|
version: '0'
|
58
72
|
requirements: []
|
59
73
|
rubyforge_project:
|
60
|
-
rubygems_version:
|
74
|
+
rubygems_version: 1.8.23
|
61
75
|
signing_key:
|
62
|
-
specification_version:
|
63
|
-
summary:
|
76
|
+
specification_version: 3
|
77
|
+
summary: Extensible monitoring daemon
|
64
78
|
test_files: []
|
79
|
+
has_rdoc:
|
checksums.yaml
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
---
|
2
|
-
SHA1:
|
3
|
-
metadata.gz: e9ebaaa5f2e140d9a0b2d821bc685a363f103ea1
|
4
|
-
data.tar.gz: 4fc6fbb8e60b919d8a9d1d8c8d36d557a80269ca
|
5
|
-
SHA512:
|
6
|
-
metadata.gz: 30e99d6aa3fba2cf78f4ba69bde44e799212ca457a7de325ee40585e111c237188b3b1d0770c642a86641da5c253eab09a64310ae4a39fc2f7be6cee19a7d4f6
|
7
|
-
data.tar.gz: 254e37a291a4e3925e3a0e38cb7fde12f4a177508ba0354ed6ba035d2844fd086411ed040eedb63d421f51f2841f4d9277d9b8b32a26f232497f547a2a7e0160
|
data/lib/solanum/matcher.rb
DELETED
@@ -1,70 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
|
3
|
-
# A matcher takes in an input string and returns a hash of measurement names to
|
4
|
-
# numeric values.
|
5
|
-
#
|
6
|
-
# Author:: Greg Look
|
7
|
-
class Solanum::Matcher
|
8
|
-
attr_reader :fn
|
9
|
-
|
10
|
-
# Creates a new Matcher which will run the given function on input.
|
11
|
-
def initialize(fn)
|
12
|
-
raise "function must be provided" if fn.nil?
|
13
|
-
@fn = fn
|
14
|
-
end
|
15
|
-
|
16
|
-
# Attempts to match the given input, returning a hash of metrics.
|
17
|
-
def call(input)
|
18
|
-
{}
|
19
|
-
end
|
20
|
-
|
21
|
-
|
22
|
-
### MATCHER TYPES ###
|
23
|
-
|
24
|
-
public
|
25
|
-
|
26
|
-
# LinePattern matchers define a regular expression which is tested against
|
27
|
-
# each line of input. The given function is called for **each** matched line,
|
28
|
-
# and the resulting measurements are merged together.
|
29
|
-
class LinePattern < Solanum::Matcher
|
30
|
-
def initialize(fn, pattern)
|
31
|
-
super fn
|
32
|
-
raise "pattern must be provided" if pattern.nil?
|
33
|
-
@pattern = pattern
|
34
|
-
end
|
35
|
-
|
36
|
-
def call(input)
|
37
|
-
raise "No input provided!" if input.nil?
|
38
|
-
lines = input.split("\n")
|
39
|
-
metrics = {}
|
40
|
-
|
41
|
-
lines.each do |line|
|
42
|
-
begin
|
43
|
-
if @pattern === line
|
44
|
-
measurements = @fn.call($~)
|
45
|
-
metrics.merge!(measurements) if measurements
|
46
|
-
end
|
47
|
-
rescue => e
|
48
|
-
STDERR.puts("Error calculating metrics from line match: #{e.inspect}")
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
metrics
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
|
57
|
-
# JsonReader matches a JSON-formatted input string and passes the parsed
|
58
|
-
# object to the block body.
|
59
|
-
class JSONReader < Solanum::Matcher
|
60
|
-
def call(input)
|
61
|
-
begin
|
62
|
-
json = JSON.parse(input)
|
63
|
-
@fn.call(json)
|
64
|
-
rescue => e
|
65
|
-
STDERR.puts("Error matching JSON input: #{e.inspect}")
|
66
|
-
{}
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|