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,216 @@
1
+
2
+ class window.GraphController extends Backbone.View
3
+ initialize: ->
4
+ @pages = @options.pages
5
+
6
+
7
+ # a page group multiple graph definitions
8
+ class window.GraphPage
9
+ constructor: (@container, @panel_container, @host) ->
10
+ @graphs = []
11
+ @autorefresh_timer = null
12
+
13
+ @autorefresh_checkbox = $("#autorefresh", @panel_container)
14
+ @linked_zoom_checkbox = $("#linkedzoom", @panel_container)
15
+ @date_select = $("#dateselect", @panel_container)
16
+
17
+ @interval = 15*60
18
+
19
+ # 60s
20
+ @offset = 60 * 1000
21
+
22
+ addGraph: (class_name, args...) ->
23
+ g = new window[class_name](@container, @host, args...)
24
+ g.init()
25
+ g.bind "dblclick", =>
26
+ @set_simple_interval( @interval )
27
+ # disable autorefresh
28
+ @set_autorefresh(null)
29
+
30
+ g.bind "plotselection", (from, to) =>
31
+ @set_autorefresh(null)
32
+ if @linked_zoom_checkbox.attr("checked")
33
+ @set_interval(from, to)
34
+
35
+ @graphs.push( g )
36
+
37
+ set_simple_interval: (interval) ->
38
+ @interval = interval
39
+
40
+ # clear the date select
41
+ @date_select.val("")
42
+
43
+ # getTime return a timestamp in UTC so no
44
+ # conversion is required
45
+ to = (new Date().getTime()) - @offset
46
+ to = to / 1000
47
+ from = to - @interval
48
+
49
+ @set_interval(from, to)
50
+
51
+ set_interval: (from, to) ->
52
+ $.each @graphs, (i, g) ->
53
+ g.set_interval(from, to)
54
+ g.update_graph()
55
+
56
+ # set time to null to disable
57
+ set_autorefresh: (time) ->
58
+ # start by killing the timer if any
59
+ if @autorefresh_timer
60
+ window.clearInterval( @autorefresh_timer )
61
+ @autorefresh_timer = null
62
+
63
+ # and create a new one if asked
64
+ if time
65
+ @autorefresh_checkbox.attr("checked", true)
66
+ @autorefresh_timer = window.setInterval ( => @set_simple_interval(@interval) ), time
67
+ else
68
+ @autorefresh_checkbox.attr("checked", false)
69
+
70
+ refresh: ->
71
+ @set_simple_interval(@interval)
72
+
73
+
74
+ # define a graph with its series
75
+ class window.GraphDefinition
76
+ constructor: (@host, @container, @title, formatters, ymin) ->
77
+ _.extend(this, Backbone.Events);
78
+
79
+ if ymin
80
+ # create an array with the same size as the formatters
81
+ ymin = $.makeArray(ymin)
82
+ if ymin.length == 1
83
+ limits = $.map formatters, -> [[0, ymin[0]]]
84
+ else if ymin.length == 2
85
+ limits = $.map ymin, (val)-> [[0, val]]
86
+
87
+
88
+
89
+ else
90
+ limits = $.map formatters, -> [[0, 0]]
91
+
92
+ @graph = new Graph
93
+ "title" : @title
94
+ "parent_container" : @container
95
+ "formatters" : formatters
96
+ "limits" : limits
97
+
98
+ @graph.bind "dblclick", => @trigger("dblclick")
99
+ @graph.bind "plotselection", (from, to) => @trigger("plotselection", from, to)
100
+
101
+ init: ->
102
+ @graph.create()
103
+
104
+ set_interval: (from, to) ->
105
+ @graph.set_interval(from, to)
106
+
107
+ update_graph: ->
108
+ @graph.update_graph()
109
+
110
+
111
+
112
+ class window.NTPGraph extends GraphDefinition
113
+ constructor: (container, host, @remote_host, ymin = null) ->
114
+ super(host, container, "NTP", [ Format.delay, Format.identity ], ymin)
115
+
116
+ # @remote_host2 = @remote_host.replace(/\./g, '-')
117
+ @graph.addSerie("#{@host}/ntpd/time_offset-#{@remote_host}", "seconds", "Offset remote")
118
+ @graph.addSerie("#{@host}/ntpd/time_offset-loop", "seconds", "Offset local")
119
+
120
+ @graph.addSerie("#{@host}/ntpd/delay-#{@remote_host}", "seconds", "Delay remote")
121
+
122
+ @graph.addSerie("#{@host}/ntpd/time_dispersion-#{@remote_host}", "seconds", "Dispertion remote")
123
+ @graph.addSerie("#{@host}/ntpd/time_dispersion-LOCAL", "seconds", "Dispertion local")
124
+
125
+
126
+ class window.CPUGraph extends GraphDefinition
127
+ constructor: (container, host, @cpu_index, ymin = null) ->
128
+ super(host, container, "CPU #{@cpu_index}", [ Format.percent, Format.prcent ], ymin)
129
+ @graph.addSerie("#{@host}/cpu-#{@cpu_index}/cpu-user", 'value', 'User')
130
+ @graph.addSerie("#{@host}/cpu-#{@cpu_index}/cpu-system", 'value', 'System')
131
+ @graph.addSerie("#{@host}/cpu-#{@cpu_index}/cpu-interrupt", 'value', 'Interrupt')
132
+
133
+
134
+ class window.DFGraph extends GraphDefinition
135
+ constructor: (container, host, @location, ymin = null) ->
136
+ super(host, container, "Disk use (#{@location})", [ Format.size, Format.size ], ymin)
137
+ @graph.addSerie("#{@host}/df/df-#{@location}", 'used', 'Used')
138
+ @graph.addSerie("#{@host}/df/df-#{@location}", 'free', 'Free')
139
+
140
+ class window.PingGraph extends GraphDefinition
141
+ constructor: (container, host, @label, @destination, ymin = null) ->
142
+ super(host, container, "Ping #{@label}", [ Format.delay, Format.identity ], ymin)
143
+
144
+ @graph.addSerie("#{@host}/ping/ping-#{@destination}", 'ping', 'Latency')
145
+ @graph.addSerie("#{@host}/ping/ping_stddev-#{@destination}", 'value', 'stddev')
146
+ @graph.addSerie("#{@host}/ping/ping_droprate-#{@destination}", 'value', 'Loss', 2)
147
+
148
+
149
+ class window.NetworkGraph extends GraphDefinition
150
+ constructor: (container, host, @interface, ymin = null) ->
151
+ super(host, container, "Network Traffic (#{interface})", [ Format.size, Format.size ], ymin)
152
+
153
+ @graph.addSerie("#{@host}/interface/if_octets-#{@interface}", "rx", "(#{@interface}) Bytes Received")
154
+ @graph.addSerie("#{@host}/interface/if_octets-#{@interface}", "tx", "(#{@interface}) Bytes Sent")
155
+
156
+
157
+ class window.MemoryGraph extends GraphDefinition
158
+ constructor: (container, host, os, ymin = null) ->
159
+ super(host, container, "Memory", [ Format.size, Format.size ], ymin)
160
+
161
+ if os == "osx"
162
+ @graph.addSerie("#{@host}/memory/memory-active", "value", "Active")
163
+ @graph.addSerie("#{@host}/memory/memory-free", "value", "Free")
164
+ @graph.addSerie("#{@host}/memory/memory-inactive", "value", "Inactive")
165
+ @graph.addSerie("#{@host}/memory/memory-wired", "value", "Wired")
166
+ else if os == "freebsd"
167
+ @graph.addSerie("#{@host}/memory/memory-active", "value", "Active")
168
+ @graph.addSerie("#{@host}/memory/memory-cache", "value", "Cached")
169
+ @graph.addSerie("#{@host}/memory/memory-free", "value", "Free")
170
+ @graph.addSerie("#{@host}/memory/memory-inactive", "value", "Inactive")
171
+ @graph.addSerie("#{@host}/memory/memory-wired", "value", "Wired")
172
+ else if os == "linux"
173
+ @graph.addSerie("#{@host}/memory/memory-buffered", "value", "Buffered")
174
+ @graph.addSerie("#{@host}/memory/memory-cached", "value", "Caached")
175
+ @graph.addSerie("#{@host}/memory/memory-free", "value", "Free")
176
+ @graph.addSerie("#{@host}/memory/memory-used", "value", "Used")
177
+
178
+
179
+ class window.LoadGraph extends GraphDefinition
180
+ constructor: (container, host, ymin = null) ->
181
+ super(host, container, "CPU Load", [ Format.identity, Format.identity ], ymin)
182
+
183
+ @graph.addSerie("#{@host}/load/load", "shortterm", "Load (1min)")
184
+ @graph.addSerie("#{@host}/load/load", "midterm", "Load (5min)")
185
+ @graph.addSerie("#{@host}/load/load", "longterm", "Load (15min)")
186
+
187
+ class window.CollectdNetworkStats extends GraphDefinition
188
+ constructor: (container, host, can_send = true, ymin = null) ->
189
+ super(host, container, "Network Stats (collectd)", [ Format.identity, Format.identity ], ymin)
190
+
191
+ @graph.addSerie("#{@host}/network/queue_length", "value", "Queue length")
192
+
193
+ @graph.addSerie("#{@host}/network/total_values-dispatch-accepted", "value", "Values dipatch accepted", 2)
194
+ @graph.addSerie("#{@host}/network/total_values-dispatch-rejected", "value", "Values dipatch rejected", 2)
195
+
196
+ if can_send
197
+ @graph.addSerie("#{@host}/network/total_values-send-accepted", "value", "Values send accepted", 2)
198
+ @graph.addSerie("#{@host}/network/total_values-send-rejected", "value", "Values send rejected", 2)
199
+
200
+
201
+ class window.CollectdNetworkBandwidth extends GraphDefinition
202
+ constructor: (container, host, can_send = true, ymin = null) ->
203
+ super(host, container, "Network Usage (collectd)", [ Format.size, Format.identity ], ymin)
204
+
205
+ @graph.addSerie("#{@host}/network/if_octets", "rx", "Bytes Received")
206
+ @graph.addSerie("#{@host}/network/if_octets", "tx", "Bytes Sent") if can_send
207
+
208
+ @graph.addSerie("#{@host}/network/if_packets", "rx", "Packets Received", 2)
209
+ @graph.addSerie("#{@host}/network/if_packets", "tx", "Packets Sent", 2) if can_send
210
+
211
+ class window.MonitoringDriftGraph extends GraphDefinition
212
+ constructor: (container, host, ymin = null) ->
213
+ super(host, container, "Clock drift", [ Format.delay, Format.delay ], ymin)
214
+
215
+ @graph.addSerie("#{@host}/monitoring/gauge-clock_drift", "value", "Drift")
216
+
@@ -0,0 +1,13 @@
1
+
2
+ class window.Serie extends StaticLine
3
+ constructor: (@rrd_path, @ds_name, @legend, @yaxis = 1, @formatter = Format.identity, @rra = null) ->
4
+ super(null, legend, yaxis, formatter)
5
+ @static = false
6
+
7
+ get_definition: () ->
8
+ {
9
+ data: @data,
10
+ label: @legend,
11
+ yaxis: @yaxis,
12
+ color: @color
13
+ }
@@ -0,0 +1,5 @@
1
+
2
+ window.Size =
3
+ kilobyte: 1024,
4
+ megabyte: 1024*1024,
5
+ gigabyte: 1024*1024*1024
@@ -0,0 +1,44 @@
1
+
2
+ class window.StaticLine
3
+ constructor: (@yvalue, @legend, @yaxis = 1, @formatter = Format.identity) ->
4
+ @enabled = true
5
+ @data = []
6
+ @static = true
7
+ @color = 'black'
8
+
9
+ set_data: (data) ->
10
+ @data = []
11
+
12
+ $.each data, (t, v) =>
13
+ @data.push( [Time.server_to_client(t), v] )
14
+
15
+ get_data: ->
16
+ @data
17
+
18
+ format: (v) ->
19
+ @formatter(v)
20
+
21
+ set_legend_color: (element) ->
22
+ tr = $(element).parent()
23
+ tr.toggleClass('transparent', not @enabled)
24
+
25
+ set_enabled: (new_state) ->
26
+ @enabled = new_state
27
+
28
+ toggle_enabled: () ->
29
+ this.set_enabled(not @enabled)
30
+
31
+ get_definition: (from, to) ->
32
+ @data = [
33
+ [Time.server_to_client(from), @yvalue],
34
+ [Time.server_to_client(to), @yvalue]
35
+ ]
36
+
37
+ {
38
+ data: @data,
39
+ label: @legend,
40
+ yaxis: @yaxis,
41
+ color: @color
42
+ }
43
+
44
+
@@ -0,0 +1,17 @@
1
+
2
+ window.Time =
3
+ # getTimezoneOffset() returns local offset in hours
4
+ tz_offset: 60 * -( new Date() ).getTimezoneOffset()
5
+
6
+ local_to_utc: (timestamp) ->
7
+ parseInt(timestamp, 10) - Time.tz_offset
8
+
9
+ utc_to_local: (timestamp) ->
10
+ parseInt(timestamp, 10) + Time.tz_offset
11
+
12
+ server_to_client: (timestamp) ->
13
+ @utc_to_local(timestamp) * 1000
14
+
15
+ client_to_server: (timestamp) ->
16
+ Math.floor( @local_to_utc(timestamp / 1000) )
17
+
@@ -0,0 +1,190 @@
1
+
2
+ require 'fiber_pool'
3
+
4
+ require File.expand_path('../structures', __FILE__)
5
+ require File.expand_path('../alarms', __FILE__)
6
+ require File.expand_path('../alarm_trigger', __FILE__)
7
+ require File.expand_path('../default_user_handler', __FILE__)
8
+ require File.expand_path('../collectdrb', __FILE__)
9
+
10
+ module RRDNotifier
11
+
12
+ ##
13
+ # Trigger/Stop the alarms based on user configuration
14
+ #
15
+ class AlarmManager
16
+
17
+ attr_reader :fiber_pool
18
+
19
+ ##
20
+ # Create a new AlertManager object
21
+ #
22
+ # @param [Hash] opts options
23
+ # @option opts [Module,Object] notification_manager The object
24
+ # used when a new notification is triggered/stopped
25
+ # @option opts [FiberPool] fiber_pool Fiber pool to use.
26
+ # @option opts [String] :send_monitoring_to if present we can send
27
+ # our own stats to this <host>:<port>
28
+ #
29
+ def initialize(opts = {})
30
+ @notification_handler = opts.delete(:notification_handler) || DefaultNotificationHandler
31
+ send_monitoring_to = opts.delete(:send_monitoring_to)
32
+ if send_monitoring_to
33
+ @send_monitoring_to_host, @send_monitoring_to_port = send_monitoring_to.split(':')
34
+ # to just data to collectd
35
+ @collectd_socket = EM::open_datagram_socket('127.0.0.1', nil)
36
+ end
37
+
38
+ unless valid_notification_handler?(@notification_handler)
39
+ raise "notification_handler #{@notification_handler} invalid, some callbacks are missing !"
40
+ end
41
+
42
+ @fiber_pool = opts.delete(:fiber_pool) || FiberPool.new(10)
43
+ @triggers = []
44
+ @active_alarms = {}
45
+ @last_updates = {}
46
+ end
47
+
48
+ ##
49
+ # Check that the object respond to the required
50
+ # methods.
51
+ #
52
+ # @param [Object] obj the handler
53
+ #
54
+ def valid_notification_handler?(obj)
55
+ obj.respond_to?(:dispatch_notification) &&
56
+ obj.respond_to?(:alarm_started) &&
57
+ obj.respond_to?(:alarm_stopped)
58
+ end
59
+
60
+ ##
61
+ # Register a new alarm.
62
+ # @see AlarmTrigger::initialize
63
+ #
64
+ def register_alarm(*args)
65
+ @triggers << AlarmTrigger.new(self, *args)
66
+ end
67
+
68
+ ##
69
+ # Called by the Notifier when an event is successfully extracted
70
+ # from a collectd packet.
71
+ #
72
+ # @param [Packet] p packet received
73
+ #
74
+ def packet_received(p)
75
+ @fiber_pool.spawn do
76
+ if p.data?
77
+ trigger_notifications(p)
78
+ @last_updates[p.measure_id] = Time.now
79
+ else
80
+ @notification_handler.dispatch_notification(p)
81
+ end
82
+ end
83
+ end
84
+
85
+ ##
86
+ # Send a counter to collectd.
87
+ #
88
+ def send_gauge(host, interval, plugin, plugin_instance, type, type_instance, value)
89
+ data = Collectd.new(host, interval)
90
+ data.start()
91
+ data.gauge(plugin, plugin_instance, type, type_instance, value)
92
+
93
+ @collectd_socket.send_datagram(data.to_s, @send_monitoring_to_host, @send_monitoring_to_port)
94
+ end
95
+
96
+
97
+ ##
98
+ # Called all the triggers matching this packet
99
+ # to see if one or more wants to start/stop alarms.
100
+ #
101
+ # @param [Packet] p the packet
102
+ #
103
+ def trigger_notifications(p)
104
+ @triggers.each{|t| t.check_alarms(p) }
105
+ end
106
+
107
+
108
+ # Alarm api, used by the triggers
109
+
110
+ ##
111
+ # Raise an alarm.
112
+ #
113
+ # @param [String] measure_id counter ID
114
+ # @param [Alarm] alarm An alarm object
115
+ #
116
+ def raise_alarm(measure_id, alarm)
117
+ @notification_handler.alarm_started(alarm)
118
+ (@active_alarms[measure_id] ||= []) << alarm
119
+ end
120
+
121
+ ##
122
+ # Used by triggers to query the time of the last update for
123
+ # this measure.
124
+ #
125
+ # @param [String] measure_id ID Representing this particular
126
+ # counter
127
+ #
128
+ def last_update_for(measure_id)
129
+ @last_updates[measure_id]
130
+ end
131
+
132
+ def active_alarms_for(measure_id)
133
+ @active_alarms[measure_id] ||= []
134
+ end
135
+
136
+ ##
137
+ # Stop an alarm.
138
+ #
139
+ # @param [String] measure_id counter ID
140
+ # @param [Alarm] alarm An alarm object
141
+ #
142
+ def stop_specific_alarm(measure_id, alarm)
143
+ active_alarms_for(measure_id).delete(alarm)
144
+ @notification_handler.alarm_stopped(alarm)
145
+ end
146
+
147
+ ##
148
+ # Stop an alarm matching specified params.
149
+ #
150
+ # @param [String] measure_id counter ID
151
+ # @param [Class] alarm_class The alarm class
152
+ # @param [Array] args these will be given to
153
+ # Alarm::is_same? so the alarm can tell us if
154
+ # it matchs.
155
+ #
156
+ # @note This method is just a wrapper around
157
+ # active_alarm? and stop_specific_alarm.
158
+ #
159
+ def stop_alarm(measure_id, alarm_class, *args)
160
+ # check if we have an active alarm
161
+ alarm = active_alarm?(measure_id, alarm_class, *args)
162
+ if alarm
163
+ stop_specific_alarm(measure_id, alarm)
164
+ end
165
+ end
166
+
167
+
168
+ ##
169
+ # Return the active alarm matching given
170
+ # parameters.
171
+ #
172
+ # @param [String] measure_id counter ID
173
+ # @param [Class] alarm_class The alarm class
174
+ # @param [Array] args these will be given to
175
+ # Alarm::is_same? so the alarm can tell us if
176
+ # it matchs.
177
+ #
178
+ def active_alarm?(measure_id, alarm_class, *args)
179
+ # fetch active alarms
180
+ active_alarms = active_alarms_for(measure_id)
181
+
182
+ # check if we have an active alarm
183
+ active_alarms.detect do |al|
184
+ al.is_a?(alarm_class) && al.is_same?(*args)
185
+ end
186
+ end
187
+
188
+ end
189
+
190
+ end
@@ -0,0 +1,187 @@
1
+
2
+ require File.expand_path('../structures', __FILE__)
3
+ require File.expand_path('../alarms', __FILE__)
4
+
5
+ module RRDNotifier
6
+ ##
7
+ # Represents the conditions to raise an alarm.
8
+ #
9
+ class AlarmTrigger
10
+ ##
11
+ # Create a new AlarmTrigger object.
12
+ #
13
+ # @param [AlertManager] manager The manager
14
+ # (used to raise/stop alarms and query informations)
15
+ # @param [String, nil] host the hostname (nil: any host)
16
+ # @param [String] plugin plugin name
17
+ # ex: something (any plugin_instance matched)
18
+ # ex: something/inst (match both plugin and plugin_instance)
19
+ # @param [String] type type name (see plugin for format)
20
+ # @param [Hash] opts Alarm option
21
+ # @option opts [Number] min minimal allowed value
22
+ # @option opts [Number] max maximum allowed value
23
+ # @option opts [Number] monitor_presence raise an
24
+ # alarm if data is missing for x seconds
25
+ # @option opts [Boolean] monitor_drift Ensure the clock
26
+ # of the host is not drifting too far from our clock
27
+ # (default: false)
28
+ # @option opts [Integer] index for multi-values counters this
29
+ # parameter allow you to select what you want to monitor
30
+ # (default: 0)
31
+ #
32
+ def initialize(manager, host, plugin, type, opts = {})
33
+ @host = self.class.load_param(host)
34
+ @plugin, @plugin_instance = self.class.load_param(plugin)
35
+ @type, @type_instance = self.class.load_param(type)
36
+
37
+ @manager = manager
38
+
39
+ @index = opts.delete(:index) || 0
40
+ @min = opts.delete(:min)
41
+ @max = opts.delete(:max)
42
+ @monitor_drift = opts.has_key?(:monitor_drift) ? opts.delete(:monitor_drift) : false
43
+ @monitor_presence = opts.has_key?(:monitor_presence) ? opts.delete(:monitor_presence) : false
44
+
45
+ raise "Unknown arguments: #{opts.inspect}" unless opts.empty?
46
+
47
+ @timers_register = {}
48
+ end
49
+
50
+ ##
51
+ # Split if arg contains a "/" and
52
+ # return nil if a "*" is found.
53
+ #
54
+ # @param [String] str source string
55
+ # @return [Array] result
56
+ #
57
+ def self.load_param(str)
58
+ raise "nil is not a valid value" unless str
59
+
60
+ ret = str.split("/").map! do |s|
61
+ (s == '*') ? nil : s
62
+ end
63
+
64
+ (ret.size == 1) ? ret[0] : ret
65
+ end
66
+
67
+ ##
68
+ # Checks if this packet is interesting for the
69
+ # trigger.
70
+ #
71
+ # @param [Packet] p the packet
72
+ # @return [Boolean] true if the packet is interesting
73
+ #
74
+ def match?(p)
75
+ (@host.nil? || (@host == p.host)) &&
76
+ (@plugin.nil? || (@plugin == p.plugin)) &&
77
+ (@plugin_instance.nil? || (@plugin_instance == p.plugin_instance)) &&
78
+ (@type.nil? || (@type == p.type)) &&
79
+ (@type_instance.nil? || (@type_instance == p.type_instance))
80
+ end
81
+
82
+
83
+ def inspect
84
+ "#{@host}-#{@plugin}/#{@plugin_instance}-#{@type}/#{@type_instance}"
85
+ end
86
+
87
+ ##
88
+ # Called by the manager when a packet is received.
89
+ #
90
+ # @param [Packet] p the packet
91
+ #
92
+ def check_alarms(p)
93
+ # if the packet is not intersting for me, stop here
94
+ unless match?(p)
95
+ return
96
+ end
97
+
98
+ if @index >= p.values.size
99
+ puts "index #{@index} was given but only #{p.values.size} found, check disabled"
100
+ return
101
+ end
102
+
103
+ value = p.value(@index)
104
+
105
+ check_alarm_high(p, value)
106
+ check_alarm_low(p, value)
107
+ check_alarm_presence(p, value)
108
+ check_alarm_drift(p, value)
109
+ end
110
+
111
+ def check_alarm_high(p, value)
112
+ return unless @max
113
+
114
+ if value > @max
115
+ unless @manager.active_alarm?(p.measure_id, AlarmTooHigh, @max)
116
+ @manager.raise_alarm( p.measure_id, AlarmTooHigh.new(p, @max) )
117
+ end
118
+ else
119
+ @manager.stop_alarm(p.measure_id, AlarmTooHigh, @max)
120
+ end
121
+ end
122
+
123
+ def check_alarm_low(p, value)
124
+ return unless @min
125
+
126
+ if value < @min
127
+ unless @manager.active_alarm?(p.measure_id, AlarmTooLow, @min)
128
+ @manager.raise_alarm( p.measure_id, AlarmTooLow.new(p, @min) )
129
+ end
130
+ else
131
+ @manager.stop_alarm(p.measure_id, AlarmTooLow, @min)
132
+ end
133
+ end
134
+
135
+
136
+ def check_alarm_drift(p, value)
137
+ return unless @monitor_drift
138
+ now = Time.now
139
+
140
+ drift = (now - p.time).to_f.abs
141
+ # convert the time to ms before sending it
142
+ @manager.send_gauge(p.host, p.interval, 'monitoring', nil, 'gauge', 'clock_drift', drift * 1000)
143
+
144
+ # check the absolute value of the difference between
145
+ # current server time and time included in the collectd
146
+ # packet.
147
+ if drift > @monitor_drift
148
+ unless @manager.active_alarm?(p.measure_id, AlarmClockDrift, @monitor_drift)
149
+ @manager.raise_alarm( p.measure_id, AlarmClockDrift.new(p, @monitor_drift) )
150
+ end
151
+ else
152
+ @manager.stop_alarm(p.measure_id, AlarmClockDrift, @monitor_drift)
153
+ end
154
+ end
155
+
156
+
157
+ def check_alarm_presence(p, value)
158
+ return unless @monitor_presence
159
+
160
+ # stop any already active alarm
161
+ last_update = @manager.last_update_for(p.measure_id)
162
+ @manager.stop_alarm(p.measure_id, AlarmMissingData, @monitor_presence, last_update)
163
+
164
+ # reset any active timer
165
+ timer_id = @timers_register[p.measure_id]
166
+ EM::cancel_timer(timer_id) if timer_id
167
+
168
+ # and create a new one
169
+ @timers_register[p.measure_id] = EM::add_timer(@monitor_presence) do
170
+ presence_timeout(p)
171
+ end
172
+ end
173
+
174
+ ##
175
+ # Raise a presence alarm.
176
+ #
177
+ # @param [Packet] p the data point
178
+ #
179
+ def presence_timeout(p)
180
+ @manager.fiber_pool.spawn do
181
+ last_update = @manager.last_update_for(p.measure_id)
182
+ @manager.raise_alarm( p.measure_id, AlarmMissingData.new(p, @monitor_presence, last_update) )
183
+ end
184
+ end
185
+
186
+ end
187
+ end