rrd-grapher 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. data/.gitignore +9 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +11 -0
  4. data/Guardfile +123 -0
  5. data/README.md +91 -0
  6. data/Rakefile +42 -0
  7. data/example_app/Gemfile +9 -0
  8. data/example_app/Guardfile +18 -0
  9. data/example_app/app.rb +38 -0
  10. data/example_app/assets/javascripts/available_rrds.coffee +13 -0
  11. data/example_app/assets/javascripts/collectd.coffee +48 -0
  12. data/example_app/config.ru +9 -0
  13. data/example_app/public/.gitignore +1 -0
  14. data/example_app/public/chosen/chosen-sprite.png +0 -0
  15. data/example_app/public/chosen/chosen.css +340 -0
  16. data/example_app/public/chosen/chosen.jquery.js +786 -0
  17. data/example_app/public/chosen/chosen.jquery.min.js +10 -0
  18. data/example_app/public/javascripts/available_rrds.js +16 -0
  19. data/example_app/public/javascripts/collectd.js +64 -0
  20. data/example_app/public/javascripts/jquery.timeago.js +148 -0
  21. data/example_app/public/stylesheets/Aristo/images/bg_fallback.png +0 -0
  22. data/example_app/public/stylesheets/Aristo/images/icon_sprite.png +0 -0
  23. data/example_app/public/stylesheets/Aristo/images/progress_bar.gif +0 -0
  24. data/example_app/public/stylesheets/Aristo/images/slider_handles.png +0 -0
  25. data/example_app/public/stylesheets/Aristo/images/ui-icons_222222_256x240.png +0 -0
  26. data/example_app/public/stylesheets/Aristo/images/ui-icons_454545_256x240.png +0 -0
  27. data/example_app/public/stylesheets/Aristo/theme.css +738 -0
  28. data/example_app/views/available_rrds.haml +26 -0
  29. data/example_app/views/collectd.haml +30 -0
  30. data/example_app/views/layout.haml +19 -0
  31. data/example_app/views/stylesheets/available_rrds.scss +7 -0
  32. data/example_notifier/Gemfile +2 -0
  33. data/example_notifier/notifier.rb +25 -0
  34. data/lib/rrd-grapher/assets/javascripts/app-dev.js +20 -0
  35. data/lib/rrd-grapher/assets/javascripts/app.js +20 -0
  36. data/lib/rrd-grapher/assets/javascripts/classes/format.coffee +35 -0
  37. data/lib/rrd-grapher/assets/javascripts/classes/graph.coffee +306 -0
  38. data/lib/rrd-grapher/assets/javascripts/classes/graph_definition.coffee +216 -0
  39. data/lib/rrd-grapher/assets/javascripts/classes/serie.coffee +13 -0
  40. data/lib/rrd-grapher/assets/javascripts/classes/size.coffee +5 -0
  41. data/lib/rrd-grapher/assets/javascripts/classes/static_line.coffee +44 -0
  42. data/lib/rrd-grapher/assets/javascripts/classes/time.coffee +17 -0
  43. data/lib/rrd-grapher/notifier/alarm_manager.rb +190 -0
  44. data/lib/rrd-grapher/notifier/alarm_trigger.rb +187 -0
  45. data/lib/rrd-grapher/notifier/alarms.rb +79 -0
  46. data/lib/rrd-grapher/notifier/collectdrb.rb +86 -0
  47. data/lib/rrd-grapher/notifier/data_struct.rb +46 -0
  48. data/lib/rrd-grapher/notifier/default_user_handler.rb +36 -0
  49. data/lib/rrd-grapher/notifier/parsers/bindata_parser.rb +144 -0
  50. data/lib/rrd-grapher/notifier/parsers/ruby_parser.rb +134 -0
  51. data/lib/rrd-grapher/notifier/structures.rb +80 -0
  52. data/lib/rrd-grapher/notifier.rb +87 -0
  53. data/lib/rrd-grapher/public/favicon.ico +0 -0
  54. data/lib/rrd-grapher/public/javascripts/app-dev.js +13709 -0
  55. data/lib/rrd-grapher/public/javascripts/app.js +4057 -0
  56. data/lib/rrd-grapher/public/javascripts/backbone/backbone.js +1155 -0
  57. data/lib/rrd-grapher/public/javascripts/backbone/backbone.min.js +32 -0
  58. data/lib/rrd-grapher/public/javascripts/backbone/underscore.js +841 -0
  59. data/lib/rrd-grapher/public/javascripts/backbone/underscore.min.js +27 -0
  60. data/lib/rrd-grapher/public/javascripts/classes/format.js +42 -0
  61. data/lib/rrd-grapher/public/javascripts/classes/graph.js +360 -0
  62. data/lib/rrd-grapher/public/javascripts/classes/graph_definition.js +298 -0
  63. data/lib/rrd-grapher/public/javascripts/classes/serie.js +32 -0
  64. data/lib/rrd-grapher/public/javascripts/classes/size.js +7 -0
  65. data/lib/rrd-grapher/public/javascripts/classes/static_line.js +48 -0
  66. data/lib/rrd-grapher/public/javascripts/classes/time.js +17 -0
  67. data/lib/rrd-grapher/public/javascripts/flot/.gitignore +4 -0
  68. data/lib/rrd-grapher/public/javascripts/flot/excanvas.min.js +1 -0
  69. data/lib/rrd-grapher/public/javascripts/flot/jquery.colorhelpers.min.js +1 -0
  70. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.crosshair.min.js +1 -0
  71. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.fillbetween.min.js +1 -0
  72. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.image.min.js +1 -0
  73. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.js +2604 -0
  74. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.navigate.min.js +1 -0
  75. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.pie.min.js +1 -0
  76. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.resize.min.js +1 -0
  77. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.selection.js +345 -0
  78. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.selection.min.js +1 -0
  79. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.stack.min.js +1 -0
  80. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.symbol.min.js +1 -0
  81. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.threshold.min.js +1 -0
  82. data/lib/rrd-grapher/public/javascripts/jquery/jquery-1.6.2.js +8981 -0
  83. data/lib/rrd-grapher/public/javascripts/jquery/jquery-1.6.2.min.js +18 -0
  84. data/lib/rrd-grapher/public/javascripts/jquery/jquery-ui-1.8.11.min.js +783 -0
  85. data/lib/rrd-grapher/public/javascripts/jquery/jquery.showtime.js +63 -0
  86. data/lib/rrd-grapher/public/javascripts/jquery/jquery.tpl.min.js +10 -0
  87. data/lib/rrd-grapher/resources.rb +14 -0
  88. data/lib/rrd-grapher/rrd.rb +238 -0
  89. data/lib/rrd-grapher/rrd_server.rb +78 -0
  90. data/lib/rrd-grapher/version.rb +4 -0
  91. data/lib/rrd-grapher/views/stylesheets/app.scss +111 -0
  92. data/lib/rrd-grapher.rb +12 -0
  93. data/rrd-grapher.gemspec +47 -0
  94. data/spec/common.rb +70 -0
  95. data/spec/data/myrouter.rrd +0 -0
  96. data/spec/data/subdata.rrd +0 -0
  97. data/spec/data/test.rrd +0 -0
  98. data/spec/factories.rb +23 -0
  99. data/spec/javascripts/helpers/jasmine-jquery-1.3.0.js +283 -0
  100. data/spec/javascripts/source/format_spec.coffee +18 -0
  101. data/spec/javascripts/source/graph_def_spec.coffee +27 -0
  102. data/spec/javascripts/source/graph_spec.coffee +63 -0
  103. data/spec/javascripts/source/serie_spec.coffee +28 -0
  104. data/spec/javascripts/source/static_line_spec.coffee +13 -0
  105. data/spec/javascripts/source/time_spec.coffee +26 -0
  106. data/spec/javascripts/support/jasmine.yml +78 -0
  107. data/spec/javascripts/support/jasmine_config.rb +23 -0
  108. data/spec/javascripts/support/jasmine_runner.rb +32 -0
  109. data/spec/unit/alarm_manager_spec.rb +252 -0
  110. data/spec/unit/alarm_trigger_spec.rb +26 -0
  111. data/spec/unit/data_struct_spec.rb +55 -0
  112. data/spec/unit/notifier_spec.rb +45 -0
  113. data/spec/unit/parsers/bindata_parser_spec.rb +184 -0
  114. data/spec/unit/parsers/ruby_parser_spec.rb +184 -0
  115. data/spec/unit/rrd_spec.rb +50 -0
  116. data/spec/unit/structures_spec.rb +28 -0
  117. data/tests/4series.rrd +0 -0
  118. data/tests/analyze_rrd.rb +62 -0
  119. data/tests/exact.rrd +0 -0
  120. data/tests/exact2.rrd +0 -0
  121. data/tests/filler.rb +46 -0
  122. 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