solanum 0.2.0 → 1.0.0
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.
- 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
|