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,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,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
|