rrd-grapher 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/.rvmrc +1 -0
- data/Gemfile +11 -0
- data/Guardfile +123 -0
- data/README.md +91 -0
- data/Rakefile +42 -0
- data/example_app/Gemfile +9 -0
- data/example_app/Guardfile +18 -0
- data/example_app/app.rb +38 -0
- data/example_app/assets/javascripts/available_rrds.coffee +13 -0
- data/example_app/assets/javascripts/collectd.coffee +48 -0
- data/example_app/config.ru +9 -0
- data/example_app/public/.gitignore +1 -0
- data/example_app/public/chosen/chosen-sprite.png +0 -0
- data/example_app/public/chosen/chosen.css +340 -0
- data/example_app/public/chosen/chosen.jquery.js +786 -0
- data/example_app/public/chosen/chosen.jquery.min.js +10 -0
- data/example_app/public/javascripts/available_rrds.js +16 -0
- data/example_app/public/javascripts/collectd.js +64 -0
- data/example_app/public/javascripts/jquery.timeago.js +148 -0
- data/example_app/public/stylesheets/Aristo/images/bg_fallback.png +0 -0
- data/example_app/public/stylesheets/Aristo/images/icon_sprite.png +0 -0
- data/example_app/public/stylesheets/Aristo/images/progress_bar.gif +0 -0
- data/example_app/public/stylesheets/Aristo/images/slider_handles.png +0 -0
- data/example_app/public/stylesheets/Aristo/images/ui-icons_222222_256x240.png +0 -0
- data/example_app/public/stylesheets/Aristo/images/ui-icons_454545_256x240.png +0 -0
- data/example_app/public/stylesheets/Aristo/theme.css +738 -0
- data/example_app/views/available_rrds.haml +26 -0
- data/example_app/views/collectd.haml +30 -0
- data/example_app/views/layout.haml +19 -0
- data/example_app/views/stylesheets/available_rrds.scss +7 -0
- data/example_notifier/Gemfile +2 -0
- data/example_notifier/notifier.rb +25 -0
- data/lib/rrd-grapher/assets/javascripts/app-dev.js +20 -0
- data/lib/rrd-grapher/assets/javascripts/app.js +20 -0
- data/lib/rrd-grapher/assets/javascripts/classes/format.coffee +35 -0
- data/lib/rrd-grapher/assets/javascripts/classes/graph.coffee +306 -0
- data/lib/rrd-grapher/assets/javascripts/classes/graph_definition.coffee +216 -0
- data/lib/rrd-grapher/assets/javascripts/classes/serie.coffee +13 -0
- data/lib/rrd-grapher/assets/javascripts/classes/size.coffee +5 -0
- data/lib/rrd-grapher/assets/javascripts/classes/static_line.coffee +44 -0
- data/lib/rrd-grapher/assets/javascripts/classes/time.coffee +17 -0
- data/lib/rrd-grapher/notifier/alarm_manager.rb +190 -0
- data/lib/rrd-grapher/notifier/alarm_trigger.rb +187 -0
- data/lib/rrd-grapher/notifier/alarms.rb +79 -0
- data/lib/rrd-grapher/notifier/collectdrb.rb +86 -0
- data/lib/rrd-grapher/notifier/data_struct.rb +46 -0
- data/lib/rrd-grapher/notifier/default_user_handler.rb +36 -0
- data/lib/rrd-grapher/notifier/parsers/bindata_parser.rb +144 -0
- data/lib/rrd-grapher/notifier/parsers/ruby_parser.rb +134 -0
- data/lib/rrd-grapher/notifier/structures.rb +80 -0
- data/lib/rrd-grapher/notifier.rb +87 -0
- data/lib/rrd-grapher/public/favicon.ico +0 -0
- data/lib/rrd-grapher/public/javascripts/app-dev.js +13709 -0
- data/lib/rrd-grapher/public/javascripts/app.js +4057 -0
- data/lib/rrd-grapher/public/javascripts/backbone/backbone.js +1155 -0
- data/lib/rrd-grapher/public/javascripts/backbone/backbone.min.js +32 -0
- data/lib/rrd-grapher/public/javascripts/backbone/underscore.js +841 -0
- data/lib/rrd-grapher/public/javascripts/backbone/underscore.min.js +27 -0
- data/lib/rrd-grapher/public/javascripts/classes/format.js +42 -0
- data/lib/rrd-grapher/public/javascripts/classes/graph.js +360 -0
- data/lib/rrd-grapher/public/javascripts/classes/graph_definition.js +298 -0
- data/lib/rrd-grapher/public/javascripts/classes/serie.js +32 -0
- data/lib/rrd-grapher/public/javascripts/classes/size.js +7 -0
- data/lib/rrd-grapher/public/javascripts/classes/static_line.js +48 -0
- data/lib/rrd-grapher/public/javascripts/classes/time.js +17 -0
- data/lib/rrd-grapher/public/javascripts/flot/.gitignore +4 -0
- data/lib/rrd-grapher/public/javascripts/flot/excanvas.min.js +1 -0
- data/lib/rrd-grapher/public/javascripts/flot/jquery.colorhelpers.min.js +1 -0
- data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.crosshair.min.js +1 -0
- data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.fillbetween.min.js +1 -0
- data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.image.min.js +1 -0
- data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.js +2604 -0
- data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.navigate.min.js +1 -0
- data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.pie.min.js +1 -0
- data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.resize.min.js +1 -0
- data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.selection.js +345 -0
- data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.selection.min.js +1 -0
- data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.stack.min.js +1 -0
- data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.symbol.min.js +1 -0
- data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.threshold.min.js +1 -0
- data/lib/rrd-grapher/public/javascripts/jquery/jquery-1.6.2.js +8981 -0
- data/lib/rrd-grapher/public/javascripts/jquery/jquery-1.6.2.min.js +18 -0
- data/lib/rrd-grapher/public/javascripts/jquery/jquery-ui-1.8.11.min.js +783 -0
- data/lib/rrd-grapher/public/javascripts/jquery/jquery.showtime.js +63 -0
- data/lib/rrd-grapher/public/javascripts/jquery/jquery.tpl.min.js +10 -0
- data/lib/rrd-grapher/resources.rb +14 -0
- data/lib/rrd-grapher/rrd.rb +238 -0
- data/lib/rrd-grapher/rrd_server.rb +78 -0
- data/lib/rrd-grapher/version.rb +4 -0
- data/lib/rrd-grapher/views/stylesheets/app.scss +111 -0
- data/lib/rrd-grapher.rb +12 -0
- data/rrd-grapher.gemspec +47 -0
- data/spec/common.rb +70 -0
- data/spec/data/myrouter.rrd +0 -0
- data/spec/data/subdata.rrd +0 -0
- data/spec/data/test.rrd +0 -0
- data/spec/factories.rb +23 -0
- data/spec/javascripts/helpers/jasmine-jquery-1.3.0.js +283 -0
- data/spec/javascripts/source/format_spec.coffee +18 -0
- data/spec/javascripts/source/graph_def_spec.coffee +27 -0
- data/spec/javascripts/source/graph_spec.coffee +63 -0
- data/spec/javascripts/source/serie_spec.coffee +28 -0
- data/spec/javascripts/source/static_line_spec.coffee +13 -0
- data/spec/javascripts/source/time_spec.coffee +26 -0
- data/spec/javascripts/support/jasmine.yml +78 -0
- data/spec/javascripts/support/jasmine_config.rb +23 -0
- data/spec/javascripts/support/jasmine_runner.rb +32 -0
- data/spec/unit/alarm_manager_spec.rb +252 -0
- data/spec/unit/alarm_trigger_spec.rb +26 -0
- data/spec/unit/data_struct_spec.rb +55 -0
- data/spec/unit/notifier_spec.rb +45 -0
- data/spec/unit/parsers/bindata_parser_spec.rb +184 -0
- data/spec/unit/parsers/ruby_parser_spec.rb +184 -0
- data/spec/unit/rrd_spec.rb +50 -0
- data/spec/unit/structures_spec.rb +28 -0
- data/tests/4series.rrd +0 -0
- data/tests/analyze_rrd.rb +62 -0
- data/tests/exact.rrd +0 -0
- data/tests/exact2.rrd +0 -0
- data/tests/filler.rb +46 -0
- metadata +414 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
|
2
|
+
module RRDNotifier
|
3
|
+
##
|
4
|
+
# An active alarm.
|
5
|
+
#
|
6
|
+
class Alarm
|
7
|
+
# the packet which triggered the alarm
|
8
|
+
attr_accessor :packet
|
9
|
+
|
10
|
+
def initialize(p)
|
11
|
+
@packet = p
|
12
|
+
end
|
13
|
+
|
14
|
+
def method_missing(m, *args)
|
15
|
+
if packet
|
16
|
+
packet.send(m, *args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class AlarmTooHigh < Alarm
|
22
|
+
def reason; :too_high; end
|
23
|
+
|
24
|
+
attr_accessor :threshold
|
25
|
+
|
26
|
+
def initialize(p, threshold)
|
27
|
+
super(p)
|
28
|
+
@threshold = threshold
|
29
|
+
end
|
30
|
+
|
31
|
+
def is_same?(threshold)
|
32
|
+
@threshold == threshold
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class AlarmTooLow < AlarmTooHigh
|
37
|
+
def reason; :too_low; end
|
38
|
+
end
|
39
|
+
|
40
|
+
class AlarmMissingData < Alarm
|
41
|
+
def reason; :missing_data; end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Time allowed between updates.
|
45
|
+
attr_accessor :allowed_interval
|
46
|
+
|
47
|
+
##
|
48
|
+
# When was this measure last updated ?
|
49
|
+
attr_accessor :last_update
|
50
|
+
|
51
|
+
def initialize(p, allowed_interval ,last_update)
|
52
|
+
super(p)
|
53
|
+
@allowed_interval = allowed_interval
|
54
|
+
@last_update = last_update
|
55
|
+
end
|
56
|
+
|
57
|
+
def is_same?(allowed_interval ,last_update)
|
58
|
+
(@allowed_interval == allowed_interval) &&
|
59
|
+
(@last_update == last_update)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class AlarmClockDrift < Alarm
|
64
|
+
def reason; :clock_drift; end
|
65
|
+
|
66
|
+
# difference in seconds between our clock
|
67
|
+
# and the one of the host
|
68
|
+
attr_accessor :allowed_drift
|
69
|
+
|
70
|
+
def initialize(p, allowed_drift)
|
71
|
+
super(p)
|
72
|
+
@allowed_drift = allowed_drift
|
73
|
+
end
|
74
|
+
|
75
|
+
def is_same?(drift)
|
76
|
+
@allowed_drift == drift
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
#
|
2
|
+
# This file was not written by me but it did not come with a license
|
3
|
+
# and I cannot find itw owner so if you know the author let me know.
|
4
|
+
#
|
5
|
+
class Collectd
|
6
|
+
# Encode a string (type 0, null terminated string)
|
7
|
+
def self.string(type, str)
|
8
|
+
str += "\000"
|
9
|
+
[type, str.length+4].pack("nn") + str
|
10
|
+
end
|
11
|
+
|
12
|
+
# Encode an integer
|
13
|
+
def self.number(type, num)
|
14
|
+
[type, 12].pack("nn") + [num >> 32, num & 0xffffffff].pack("NN")
|
15
|
+
end
|
16
|
+
|
17
|
+
# Create a collectd collection object tied to a specific destination server and port.
|
18
|
+
# The host parameter is the hostname sent to collectd, typically `hostname -f`.strip
|
19
|
+
# The use of the interval is unclear, it's simply sent to collectd with every packet...
|
20
|
+
def initialize(host, interval)
|
21
|
+
@interval = interval
|
22
|
+
@host = host || %x{hostname -s}.strip
|
23
|
+
start
|
24
|
+
end
|
25
|
+
|
26
|
+
# Start a fresh packet, this is usually not called directly. Issues a time marker using either
|
27
|
+
# the passed time (unix time integer) or the same time as the previous packet (useful when
|
28
|
+
# overrunning from one packet to the next)
|
29
|
+
def start(time=nil)
|
30
|
+
@pkt = Collectd.string(0, @host)
|
31
|
+
@pkt << Collectd.number(1, Time.new.to_i)
|
32
|
+
@pkt << Collectd.number(7, @interval)
|
33
|
+
@plugin = @plugin_instance = @tipe = @tipe_instance = nil
|
34
|
+
end
|
35
|
+
# Send the current packet
|
36
|
+
def flush
|
37
|
+
raise "need to be redefined"
|
38
|
+
end
|
39
|
+
# Check the length of the current packet and flush it if we reach a high-water mark
|
40
|
+
def chk
|
41
|
+
if @pkt.size > 900 # arbitrary flush, 1024 is the max allowed
|
42
|
+
flush
|
43
|
+
#sleep(0.01) # don't overwhelm output buffers
|
44
|
+
start
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def force_send
|
49
|
+
flush
|
50
|
+
start
|
51
|
+
end
|
52
|
+
|
53
|
+
# Issue time, plugin, plugin_instance, type, and type_instance markers. These are not typically called
|
54
|
+
# directly
|
55
|
+
def time(t) @pkt << Collectd.number(1, @time = t) unless @time == t; end
|
56
|
+
def plugin(p) @pkt << Collectd.string(2, @plugin = p) unless @plugin == p; end
|
57
|
+
def plugin_instance(p) @pkt << Collectd.string(3, @plugin_instance = p) unless @plugin_instance == p; end
|
58
|
+
def tipe(p) @pkt << Collectd.string(4, @tipe = p) unless @tipe == p; end
|
59
|
+
def tipe_instance(p) @pkt << Collectd.string(5, @tipe_instance = p) unless @tipe_instance == p; end
|
60
|
+
|
61
|
+
# Send a data point consisting of one or multiple values. Multiple values are used for RRDs with multiple
|
62
|
+
# data series (DS's in RRD terms). An examples of a multi-valued RRDs in the collectd types is disk_write
|
63
|
+
# with a 'read' and a 'write' value.
|
64
|
+
# Arguments: pl=plugin, pi=plugin_instance, t=type, ti=type_instance, values: array of [type, value]
|
65
|
+
# Eg.: values('disk', 'sda0', 'disk', 'ops', [[:counter, 1034], [:counter, 345]])
|
66
|
+
@@type_code = {:gauge => 1, :counter => 0, :derive => 2, :absolute => 3}
|
67
|
+
def values(pl, pi, t, ti, values)
|
68
|
+
chk
|
69
|
+
plugin(pl); plugin_instance(pi)
|
70
|
+
tipe(t); tipe_instance(ti)
|
71
|
+
@pkt << [6, 4+2+values.size*9, values.size].pack("nnn")
|
72
|
+
@pkt << values.map{|t,v| [@@type_code[t]].pack("C")}.join
|
73
|
+
@pkt << values.map{|t,v| t == :gauge ? [v].pack("E") : [v>>32, v & 0xffffffff].pack("NN")}.join
|
74
|
+
end
|
75
|
+
|
76
|
+
# Send a data point with one or multiple gauge values
|
77
|
+
def gauge (pl, pi, t, ti, value) values(pl, pi, t, ti, Array(value).map{|v| [:gauge, v]}); end
|
78
|
+
# Send a data point with one or multiple counter values
|
79
|
+
def counter (pl, pi, t, ti, value) values(pl, pi, t, ti, Array(value).map{|v| [:counter, v]}); end
|
80
|
+
# Send a data point with one or multiple derive values
|
81
|
+
def derive (pl, pi, t, ti, value) values(pl, pi, t, ti, Array(value).map{|v| [:derive, v]}); end
|
82
|
+
# Send a data point with one or multiple absolute values
|
83
|
+
def absolute(pl, pi, t, ti, value) values(pl, pi, t, ti, Array(value).map{|v| [:absolute, v]}); end
|
84
|
+
|
85
|
+
def to_s; @pkt; end
|
86
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
class DataStruct
|
3
|
+
|
4
|
+
def initialize(*args)
|
5
|
+
merge_data_from(*args)
|
6
|
+
end
|
7
|
+
|
8
|
+
##
|
9
|
+
# Merge new data in the structure.
|
10
|
+
#
|
11
|
+
# @param [Object,Hash] opts_or_obj Source
|
12
|
+
# @param [Array] only_fields an array of symbol
|
13
|
+
# specifying which fields to copy
|
14
|
+
# @param [Boolean] allow_nil If false nil values from
|
15
|
+
# the source will not be copied in object
|
16
|
+
#
|
17
|
+
def merge_data_from(opts_or_obj = {}, only_fields = nil, allow_nil = false)
|
18
|
+
self.class.attributes.select{|attr_name| selected_field?(attr_name, only_fields) }.each do |attr_name|
|
19
|
+
v = opts_or_obj.is_a?(Hash) ? (opts_or_obj[attr_name.to_s] || opts_or_obj[attr_name]) : opts_or_obj.send(attr_name)
|
20
|
+
if allow_nil || !v.nil?
|
21
|
+
send("#{attr_name}=", v)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def selected_field?(field, list)
|
27
|
+
list.nil? || list.include?(field.to_sym)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
class <<self
|
32
|
+
def properties(*names)
|
33
|
+
names.each do |name|
|
34
|
+
attr_accessor(name)
|
35
|
+
(@attributes ||= []) << name
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
alias :property :properties
|
40
|
+
|
41
|
+
def attributes
|
42
|
+
@attributes
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
module RRDNotifier
|
3
|
+
module DefaultNotificationHandler
|
4
|
+
def self.dispatch_notification(notification)
|
5
|
+
puts "[#{ev.time.strftime('%H:%m:%S')} - #{ev.host}] #{ev.severity} "
|
6
|
+
puts %{
|
7
|
+
Host: {ev.host}
|
8
|
+
Plugin: #{ev.plugin}
|
9
|
+
Type: #{ev.type}
|
10
|
+
TypeInstance: #{ev.type_instance}
|
11
|
+
Severity: #{ev.severity}
|
12
|
+
Current Value: #{ev.value}
|
13
|
+
Warning thresholds: #{ev.warn_min} - #{ev.warn_max}
|
14
|
+
Failure thresholds: #{ev.failure_min} - #{ev.failure_max}
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# A new alarm was triggered
|
20
|
+
#
|
21
|
+
# @param [Alarm] alarm the alarm
|
22
|
+
#
|
23
|
+
def self.alarm_started(alarm)
|
24
|
+
puts "an alarm was started: #{alarm.inspect}"
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# An alarm was stopped
|
29
|
+
#
|
30
|
+
# @param [Alarm] alarm the alarm
|
31
|
+
#
|
32
|
+
def self.alarm_stopped(alarm)
|
33
|
+
puts "an alarm was stopped: #{alarm.inspect}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
|
2
|
+
require 'bindata'
|
3
|
+
|
4
|
+
require File.expand_path('../../structures', __FILE__)
|
5
|
+
|
6
|
+
module RRDNotifier
|
7
|
+
module BindataParser
|
8
|
+
|
9
|
+
# part type
|
10
|
+
HOST = 0x0000
|
11
|
+
TIME = 0x0001
|
12
|
+
PLUGIN = 0x0002
|
13
|
+
PLUGIN_INSTANCE = 0x0003
|
14
|
+
TYPE = 0x0004
|
15
|
+
TYPE_INSTANCE = 0x0005
|
16
|
+
VALUES = 0x0006
|
17
|
+
INTERVAL = 0x0007
|
18
|
+
MESSAGE = 0x0100
|
19
|
+
SEVERITY = 0x0101
|
20
|
+
|
21
|
+
PART_TYPE_AS_STRING = {
|
22
|
+
'host' => HOST,
|
23
|
+
'time' => TIME,
|
24
|
+
'plugin' => PLUGIN,
|
25
|
+
'plugin_instance' => PLUGIN_INSTANCE,
|
26
|
+
'type' => TYPE,
|
27
|
+
'type_instance' => TYPE_INSTANCE,
|
28
|
+
'values' => VALUES,
|
29
|
+
'interval' => INTERVAL,
|
30
|
+
'message' => MESSAGE,
|
31
|
+
'severity' => SEVERITY
|
32
|
+
}.freeze
|
33
|
+
|
34
|
+
class ValuePartData < BinData::Record
|
35
|
+
COUNTER = 0
|
36
|
+
GAUGE = 1
|
37
|
+
DERIVE = 2
|
38
|
+
ABSOLUTE = 3
|
39
|
+
|
40
|
+
attr_accessor :types
|
41
|
+
|
42
|
+
endian :big
|
43
|
+
|
44
|
+
choice :val, :selection => proc{ types[index] }, :choices => {
|
45
|
+
COUNTER => :uint64,
|
46
|
+
GAUGE => :double_le,
|
47
|
+
DERIVE => :int64,
|
48
|
+
ABSOLUTE => :uint64
|
49
|
+
}
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
class ValuePart < BinData::Record
|
54
|
+
endian :big
|
55
|
+
|
56
|
+
int16 :values_count
|
57
|
+
array :types, :type => :uint8, :initial_length => proc{ values_count }
|
58
|
+
array :vals, :type => [:value_part_data, {:types => :types}], :initial_length => proc{ values_count }
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
class Part < BinData::Record
|
65
|
+
endian :big
|
66
|
+
|
67
|
+
uint16 :part_type
|
68
|
+
uint16 :part_length
|
69
|
+
|
70
|
+
STR_FIELDS = [HOST, PLUGIN, PLUGIN_INSTANCE, TYPE, TYPE_INSTANCE, MESSAGE]
|
71
|
+
INT_FIELDS = [TIME, INTERVAL, SEVERITY]
|
72
|
+
|
73
|
+
int64 :integer_value, :onlyif => proc{ INT_FIELDS.include?(part_type) }
|
74
|
+
string :string_value, :onlyif => proc{ STR_FIELDS.include?(part_type) }, :length => proc{ part_length - 4 }, :trim_padding => true
|
75
|
+
value_part :vals, :onlyif => proc { part_type == BindataParser::VALUES }
|
76
|
+
|
77
|
+
def get_value
|
78
|
+
case
|
79
|
+
when STR_FIELDS.include?(part_type) then string_value
|
80
|
+
when INT_FIELDS.include?(part_type) then integer_value
|
81
|
+
else
|
82
|
+
vals.vals.map(&:val)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
class Packet < BinData::Record
|
89
|
+
array :parts, :type => :part, :read_until => :complete
|
90
|
+
|
91
|
+
def method_missing(m, *args)
|
92
|
+
type = PART_TYPE_AS_STRING[m.to_s]
|
93
|
+
if type
|
94
|
+
p = parts.detect{|p| p.part_type == type }
|
95
|
+
p.get_value if p
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def has_part?(type)
|
100
|
+
parts.detect{|p| p.part_type == type }
|
101
|
+
end
|
102
|
+
|
103
|
+
def complete
|
104
|
+
eof ||
|
105
|
+
has_part?(VALUES) ||
|
106
|
+
has_part?(MESSAGE)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class Reader < BinData::Record
|
111
|
+
array :packets, :type => :packet, :read_until => :eof
|
112
|
+
end
|
113
|
+
|
114
|
+
COMPRESSED_FIELDS = [
|
115
|
+
:host,
|
116
|
+
:time,
|
117
|
+
:interval
|
118
|
+
].freeze
|
119
|
+
|
120
|
+
def self.parse(data)
|
121
|
+
ret = []
|
122
|
+
reader = Reader.new
|
123
|
+
packets = reader.read(data).packets
|
124
|
+
last_packet = {}
|
125
|
+
|
126
|
+
if packets.size >= 1
|
127
|
+
|
128
|
+
packets.each_with_index do |packet, i|
|
129
|
+
|
130
|
+
p = RRDNotifier::Packet.new(last_packet)
|
131
|
+
p.merge_data_from(packet)
|
132
|
+
|
133
|
+
last_packet = p if p.data?
|
134
|
+
|
135
|
+
ret << p
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
ret
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require File.expand_path('../../structures', __FILE__)
|
2
|
+
|
3
|
+
module RRDNotifier
|
4
|
+
|
5
|
+
module RubyParser
|
6
|
+
|
7
|
+
# part type
|
8
|
+
HOST = 0x0000
|
9
|
+
TIME = 0x0001
|
10
|
+
PLUGIN = 0x0002
|
11
|
+
PLUGIN_INSTANCE = 0x0003
|
12
|
+
TYPE = 0x0004
|
13
|
+
TYPE_INSTANCE = 0x0005
|
14
|
+
VALUES = 0x0006
|
15
|
+
INTERVAL = 0x0007
|
16
|
+
MESSAGE = 0x0100
|
17
|
+
SEVERITY = 0x0101
|
18
|
+
|
19
|
+
PART_TYPE_AS_STRING = {
|
20
|
+
HOST => 'host',
|
21
|
+
TIME => 'time',
|
22
|
+
PLUGIN => 'plugin',
|
23
|
+
PLUGIN_INSTANCE => 'plugin_instance',
|
24
|
+
TYPE => 'type',
|
25
|
+
TYPE_INSTANCE => 'type_instance',
|
26
|
+
VALUES => 'values',
|
27
|
+
INTERVAL => 'interval',
|
28
|
+
MESSAGE => 'message',
|
29
|
+
SEVERITY => 'severity'
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
STR_FIELDS = [HOST, PLUGIN, PLUGIN_INSTANCE, TYPE, TYPE_INSTANCE, MESSAGE]
|
33
|
+
INT_FIELDS = [TIME, INTERVAL, SEVERITY]
|
34
|
+
|
35
|
+
COUNTER = 0x00
|
36
|
+
GAUGE = 0x01
|
37
|
+
DERIVE = 0x02
|
38
|
+
ABSOLUTE = 0x03
|
39
|
+
|
40
|
+
def self.parse_part_header(buffer)
|
41
|
+
type, length, rest = buffer.unpack('nna*')
|
42
|
+
[type, length - 4, rest]
|
43
|
+
end
|
44
|
+
|
45
|
+
INT64_MAX = (1 << 63)
|
46
|
+
INT64_SIGN_BIT = (1 << 64)
|
47
|
+
|
48
|
+
# uint to int
|
49
|
+
# "val = val - #{1 << nbits} if (val >= #{1 << (nbits - 1)})"
|
50
|
+
def self.parse_int64(buffer, signed = false)
|
51
|
+
# [v>>32, v & 0xffffffff].pack("NN")}.join
|
52
|
+
|
53
|
+
hi, lo, buffer = buffer.unpack("NNa*")
|
54
|
+
n = (hi << 32 | lo)
|
55
|
+
|
56
|
+
if signed && (n >= INT64_MAX)
|
57
|
+
n = n - INT64_SIGN_BIT
|
58
|
+
end
|
59
|
+
|
60
|
+
[n, buffer]
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.parse_part(buffer)
|
64
|
+
type, length, buffer = parse_part_header(buffer)
|
65
|
+
case
|
66
|
+
when INT_FIELDS.include?(type) then val, buffer = parse_int64(buffer)
|
67
|
+
when STR_FIELDS.include?(type) then val, buffer = buffer.unpack("Z#{length}a*")
|
68
|
+
when type == VALUES then val, buffer = parse_part_values(length, buffer)
|
69
|
+
end
|
70
|
+
|
71
|
+
[
|
72
|
+
PART_TYPE_AS_STRING[type],
|
73
|
+
val,
|
74
|
+
buffer
|
75
|
+
]
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.parse_part_values(length, buffer)
|
79
|
+
# first we need to read the types of all the values
|
80
|
+
values_count, buffer = buffer.unpack("na*")
|
81
|
+
*types, buffer = buffer.unpack("C#{values_count}a*")
|
82
|
+
values = types.map! do |type|
|
83
|
+
case type
|
84
|
+
when COUNTER, ABSOLUTE then val, buffer = parse_int64(buffer)
|
85
|
+
when GAUGE then val, buffer = buffer.unpack("Ea*")
|
86
|
+
when DERIVE then val, buffer = parse_int64(buffer, true)
|
87
|
+
end
|
88
|
+
|
89
|
+
val
|
90
|
+
end
|
91
|
+
|
92
|
+
[values, buffer]
|
93
|
+
end
|
94
|
+
|
95
|
+
COPY_FIELDS = [
|
96
|
+
:time,
|
97
|
+
:host,
|
98
|
+
:plugin,
|
99
|
+
:plugin_instance,
|
100
|
+
:type,
|
101
|
+
:type_instance,
|
102
|
+
:interval
|
103
|
+
].freeze
|
104
|
+
|
105
|
+
def self.parse_packet(buffer, initial_values = {})
|
106
|
+
packet = Packet.new(initial_values, COPY_FIELDS)
|
107
|
+
|
108
|
+
begin
|
109
|
+
type, value, buffer = parse_part(buffer)
|
110
|
+
packet.send("#{type}=", value)
|
111
|
+
end until packet.message || packet.values
|
112
|
+
|
113
|
+
[packet, buffer]
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.parse(buffer)
|
117
|
+
packets = []
|
118
|
+
last_packet = {}
|
119
|
+
|
120
|
+
# 4 = part header size
|
121
|
+
while buffer.bytesize >= 4
|
122
|
+
packet, buffer = parse_packet(buffer, last_packet)
|
123
|
+
packets << packet
|
124
|
+
|
125
|
+
last_packet = packet if packet.data?
|
126
|
+
end
|
127
|
+
|
128
|
+
packets
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
|
2
|
+
require File.expand_path('../data_struct', __FILE__)
|
3
|
+
|
4
|
+
module RRDNotifier
|
5
|
+
# from collectd source code
|
6
|
+
# plugin.h
|
7
|
+
#
|
8
|
+
### notification:
|
9
|
+
# int severity;
|
10
|
+
# cdtime_t time;
|
11
|
+
# char message[NOTIF_MAX_MSG_LEN];
|
12
|
+
# char host[DATA_MAX_NAME_LEN];
|
13
|
+
# char plugin[DATA_MAX_NAME_LEN];
|
14
|
+
# char plugin_instance[DATA_MAX_NAME_LEN];
|
15
|
+
# char type[DATA_MAX_NAME_LEN];
|
16
|
+
# char type_instance[DATA_MAX_NAME_LEN];
|
17
|
+
#
|
18
|
+
### data
|
19
|
+
# value_t *values;
|
20
|
+
# int values_len;
|
21
|
+
# cdtime_t time;
|
22
|
+
# cdtime_t interval;
|
23
|
+
# char host[DATA_MAX_NAME_LEN];
|
24
|
+
# char plugin[DATA_MAX_NAME_LEN];
|
25
|
+
# char plugin_instance[DATA_MAX_NAME_LEN];
|
26
|
+
# char type[DATA_MAX_NAME_LEN];
|
27
|
+
# char type_instance[DATA_MAX_NAME_LEN];
|
28
|
+
#
|
29
|
+
class Packet < DataStruct
|
30
|
+
properties :time,
|
31
|
+
:host,
|
32
|
+
:plugin,
|
33
|
+
:plugin_instance,
|
34
|
+
:type,
|
35
|
+
:type_instance,
|
36
|
+
|
37
|
+
# data
|
38
|
+
:interval,
|
39
|
+
:values,
|
40
|
+
|
41
|
+
# notification
|
42
|
+
:message,
|
43
|
+
:severity
|
44
|
+
|
45
|
+
def notification?
|
46
|
+
!self.message.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
def data?
|
50
|
+
self.message.nil?
|
51
|
+
end
|
52
|
+
|
53
|
+
def value(index = 0)
|
54
|
+
values[index]
|
55
|
+
end
|
56
|
+
|
57
|
+
def plugin_display
|
58
|
+
if plugin_instance && !plugin_instance.empty?
|
59
|
+
"#{plugin}/#{plugin_instance}"
|
60
|
+
else
|
61
|
+
plugin
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def type_display
|
66
|
+
if type_instance && !type_instance.empty?
|
67
|
+
"#{type}/#{type_instance}"
|
68
|
+
else
|
69
|
+
type
|
70
|
+
end
|
71
|
+
end
|
72
|
+
##
|
73
|
+
# return a unique id for the measured data.
|
74
|
+
#
|
75
|
+
def measure_id
|
76
|
+
"#{host}-#{plugin_display}-#{type_display}"
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
|
2
|
+
require 'eventmachine'
|
3
|
+
|
4
|
+
require File.expand_path('../notifier/parsers/ruby_parser', __FILE__)
|
5
|
+
require File.expand_path('../notifier/alarm_manager', __FILE__)
|
6
|
+
|
7
|
+
module RRDNotifier
|
8
|
+
|
9
|
+
class Server < EM::Connection
|
10
|
+
|
11
|
+
##
|
12
|
+
# Called by eventmachine when the connection is created.
|
13
|
+
#
|
14
|
+
# @param [String,nil] redirect_to Redirect to this host any packet received
|
15
|
+
#
|
16
|
+
def initialize(alarm_manager, on_init_block = nil, redirect_to = nil)
|
17
|
+
@alarm_manager = alarm_manager
|
18
|
+
@on_init_block = on_init_block
|
19
|
+
@redirect_to = redirect_to ? redirect_to.split(':') : nil
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Start the notifier handler, it will open an UDP socket
|
24
|
+
# to which collectd should send its data using the network module.
|
25
|
+
#
|
26
|
+
# @param [Hash] opts options hash
|
27
|
+
# @option opts [String] :host UDP address to bind on (default: 127.0.0.1)
|
28
|
+
# @option opts [Integer] :port UDP port to bind on
|
29
|
+
# @option opts [Object] :notification_handler This object
|
30
|
+
# @option opts [String] :redirect_to Retransmit packets once received to
|
31
|
+
# this <host>:<port>
|
32
|
+
#
|
33
|
+
def self.start(opts = {}, &block)
|
34
|
+
host = opts.delete(:host) || '127.0.0.1'
|
35
|
+
port = opts.delete(:port) || 10000
|
36
|
+
redirect_to = opts.delete(:redirect_to)
|
37
|
+
|
38
|
+
if redirect_to
|
39
|
+
# TODO: make it separate from redirect_to ?
|
40
|
+
opts.merge!(:send_monitoring_to => redirect_to)
|
41
|
+
end
|
42
|
+
|
43
|
+
alarm_manager = AlarmManager.new(opts)
|
44
|
+
|
45
|
+
unless opts.empty?
|
46
|
+
raise "Unknown arguments: #{opts}"
|
47
|
+
|
48
|
+
end
|
49
|
+
EM::open_datagram_socket(host, port, Server, alarm_manager, block, redirect_to)
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
##
|
54
|
+
# Register a new monitoring alert.
|
55
|
+
# @see AlertManager::register_alert
|
56
|
+
#
|
57
|
+
def register_alarm(*args)
|
58
|
+
@alarm_manager.register_alarm(*args)
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# Eventmachine callbacks
|
63
|
+
|
64
|
+
def post_init
|
65
|
+
@on_init_block.call(self) if @on_init_block
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# EventMachine callback called when a packet is available
|
70
|
+
#
|
71
|
+
# @param [String] data data received
|
72
|
+
#
|
73
|
+
def receive_data(data)
|
74
|
+
if packets = RubyParser::parse(data)
|
75
|
+
packets.each do |p|
|
76
|
+
@alarm_manager.packet_received(p)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
if @redirect_to
|
81
|
+
send_datagram(data, @redirect_to[0], @redirect_to[1])
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
Binary file
|