johnf-fnordmetric 1.2.7
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +6 -0
- data/Rakefile +9 -0
- data/fnordmetric.gemspec +41 -0
- data/lib/fnordmetric/acceptors/acceptor.rb +42 -0
- data/lib/fnordmetric/acceptors/amqp_acceptor.rb +56 -0
- data/lib/fnordmetric/acceptors/fyrehose_acceptor.rb +43 -0
- data/lib/fnordmetric/acceptors/stomp_acceptor.rb +71 -0
- data/lib/fnordmetric/acceptors/tcp_acceptor.rb +58 -0
- data/lib/fnordmetric/acceptors/udp_acceptor.rb +37 -0
- data/lib/fnordmetric/api.rb +46 -0
- data/lib/fnordmetric/cache.rb +20 -0
- data/lib/fnordmetric/context.rb +96 -0
- data/lib/fnordmetric/defaults.rb +22 -0
- data/lib/fnordmetric/enterprise/compatibility_handler.rb +42 -0
- data/lib/fnordmetric/ext.rb +75 -0
- data/lib/fnordmetric/gauge.rb +98 -0
- data/lib/fnordmetric/gauge_calculations.rb +106 -0
- data/lib/fnordmetric/gauge_modifiers.rb +144 -0
- data/lib/fnordmetric/gauge_rendering.rb +40 -0
- data/lib/fnordmetric/gauge_validations.rb +15 -0
- data/lib/fnordmetric/gauges/distribution_gauge.rb +87 -0
- data/lib/fnordmetric/gauges/server_health_gauge.rb +13 -0
- data/lib/fnordmetric/gauges/timeseries_gauge.rb +138 -0
- data/lib/fnordmetric/gauges/toplist_gauge.rb +44 -0
- data/lib/fnordmetric/histogram.rb +64 -0
- data/lib/fnordmetric/logger.rb +63 -0
- data/lib/fnordmetric/namespace.rb +208 -0
- data/lib/fnordmetric/session.rb +139 -0
- data/lib/fnordmetric/standalone.rb +20 -0
- data/lib/fnordmetric/timeseries.rb +79 -0
- data/lib/fnordmetric/toplist.rb +61 -0
- data/lib/fnordmetric/udp_client.rb +22 -0
- data/lib/fnordmetric/util.rb +25 -0
- data/lib/fnordmetric/version.rb +3 -0
- data/lib/fnordmetric/web/app.rb +63 -0
- data/lib/fnordmetric/web/app_helpers.rb +42 -0
- data/lib/fnordmetric/web/dashboard.rb +40 -0
- data/lib/fnordmetric/web/event.rb +99 -0
- data/lib/fnordmetric/web/reactor.rb +127 -0
- data/lib/fnordmetric/web/web.rb +59 -0
- data/lib/fnordmetric/web/websocket.rb +41 -0
- data/lib/fnordmetric/widget.rb +82 -0
- data/lib/fnordmetric/widgets/bars_widget.rb +44 -0
- data/lib/fnordmetric/widgets/html_widget.rb +28 -0
- data/lib/fnordmetric/widgets/numbers_widget.rb +80 -0
- data/lib/fnordmetric/widgets/pie_widget.rb +23 -0
- data/lib/fnordmetric/widgets/timeseries_widget.rb +65 -0
- data/lib/fnordmetric/widgets/toplist_widget.rb +68 -0
- data/lib/fnordmetric/worker.rb +89 -0
- data/lib/fnordmetric/zero_config_gauge.rb +138 -0
- data/lib/fnordmetric.rb +149 -0
- data/run_specs.sh +11 -0
- data/spec/api_spec.rb +49 -0
- data/spec/context_spec.rb +42 -0
- data/spec/dashboard_spec.rb +38 -0
- data/spec/event_spec.rb +170 -0
- data/spec/ext_spec.rb +14 -0
- data/spec/fnordmetric_spec.rb +56 -0
- data/spec/gauge_like_shared.rb +56 -0
- data/spec/gauge_modifiers_spec.rb +583 -0
- data/spec/gauge_spec.rb +230 -0
- data/spec/namespace_spec.rb +114 -0
- data/spec/session_spec.rb +231 -0
- data/spec/spec_helper.rb +49 -0
- data/spec/tcp_acceptor_spec.rb +35 -0
- data/spec/timeseries_gauge_spec.rb +56 -0
- data/spec/udp_acceptor_spec.rb +35 -0
- data/spec/util_spec.rb +46 -0
- data/spec/widget_spec.rb +113 -0
- data/spec/worker_spec.rb +40 -0
- data/web/.gitignore +4 -0
- data/web/build.sh +34 -0
- data/web/css/fnordmetric.core.css +868 -0
- data/web/fnordmetric-core.css +1409 -0
- data/web/fnordmetric-core.js +3420 -0
- data/web/fnordmetric-ui.css +282 -0
- data/web/fnordmetric-ui.js +12032 -0
- data/web/haml/app.haml +20 -0
- data/web/haml/distribution_gauge.haml +118 -0
- data/web/haml/timeseries_gauge.haml +80 -0
- data/web/haml/toplist_gauge.haml +194 -0
- data/web/img/head.png +0 -0
- data/web/img/list.png +0 -0
- data/web/img/list_active.png +0 -0
- data/web/img/list_hover.png +0 -0
- data/web/img/loader.gif +0 -0
- data/web/img/loader_white.gif +0 -0
- data/web/img/navbar.png +0 -0
- data/web/img/navbar_btn.png +0 -0
- data/web/img/picto_gauge.png +0 -0
- data/web/js/fnordmetric.bars_widget.js +178 -0
- data/web/js/fnordmetric.dashboard_view.js +99 -0
- data/web/js/fnordmetric.gauge_explorer.js +173 -0
- data/web/js/fnordmetric.gauge_view.js +260 -0
- data/web/js/fnordmetric.html_widget.js +21 -0
- data/web/js/fnordmetric.js +315 -0
- data/web/js/fnordmetric.numbers_widget.js +122 -0
- data/web/js/fnordmetric.overview_view.js +35 -0
- data/web/js/fnordmetric.pie_widget.js +118 -0
- data/web/js/fnordmetric.realtime_timeline_widget.js +175 -0
- data/web/js/fnordmetric.session_view.js +342 -0
- data/web/js/fnordmetric.timeline_widget.js +333 -0
- data/web/js/fnordmetric.timeseries_widget.js +405 -0
- data/web/js/fnordmetric.toplist_widget.js +119 -0
- data/web/js/fnordmetric.ui.js +91 -0
- data/web/js/fnordmetric.util.js +248 -0
- data/web/vendor/font-awesome/css/font-awesome-ie7.min.css +22 -0
- data/web/vendor/font-awesome/css/font-awesome.css +540 -0
- data/web/vendor/font-awesome/css/font-awesome.min.css +33 -0
- data/web/vendor/font-awesome/font/FontAwesome.otf +0 -0
- data/web/vendor/font-awesome/font/fontawesome-webfont.eot +0 -0
- data/web/vendor/font-awesome/font/fontawesome-webfont.svg +284 -0
- data/web/vendor/font-awesome/font/fontawesome-webfont.ttf +0 -0
- data/web/vendor/font-awesome/font/fontawesome-webfont.woff +0 -0
- data/web/vendor/jquery-1.6.2.min.js +18 -0
- data/web/vendor/jquery-ui.min.js +6 -0
- data/web/vendor/jquery.combobox.js +129 -0
- data/web/vendor/jquery.maskedinput.js +252 -0
- metadata +444 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
class FnordMetric::Gauge
|
2
|
+
|
3
|
+
include FnordMetric::GaugeCalculations
|
4
|
+
include FnordMetric::GaugeModifiers
|
5
|
+
include FnordMetric::GaugeValidations
|
6
|
+
include FnordMetric::GaugeRendering
|
7
|
+
|
8
|
+
def initialize(opts)
|
9
|
+
opts.fetch(:key) && opts.fetch(:key_prefix)
|
10
|
+
@opts = opts
|
11
|
+
end
|
12
|
+
|
13
|
+
def tick
|
14
|
+
(@opts[:tick] || @opts[:resolution] || @opts[:flush_interval] ||
|
15
|
+
FnordMetric.options[:default_flush_interval]).to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
def retention
|
19
|
+
tick * 10 # FIXPAUL!
|
20
|
+
end
|
21
|
+
|
22
|
+
def tick_at(time, _tick=tick)
|
23
|
+
(time/_tick.to_f).floor*_tick
|
24
|
+
end
|
25
|
+
|
26
|
+
def name
|
27
|
+
@opts[:key]
|
28
|
+
end
|
29
|
+
|
30
|
+
def title
|
31
|
+
@opts[:title] || name
|
32
|
+
end
|
33
|
+
|
34
|
+
def group
|
35
|
+
@opts[:group] || "Gauges"
|
36
|
+
end
|
37
|
+
|
38
|
+
def unit
|
39
|
+
@opts[:unit]
|
40
|
+
end
|
41
|
+
|
42
|
+
def key_nouns
|
43
|
+
@opts[:key_nouns] || ["Key", "Keys"]
|
44
|
+
end
|
45
|
+
|
46
|
+
def key(_append=nil)
|
47
|
+
[@opts[:key_prefix], "gauge", name, tick, _append].flatten.compact.join("-")
|
48
|
+
end
|
49
|
+
|
50
|
+
def tick_key(_time, _append=nil)
|
51
|
+
key([(progressive? ? :progressive : tick_at(_time).to_s), _append])
|
52
|
+
end
|
53
|
+
|
54
|
+
def tick_keys(_range, _append=nil)
|
55
|
+
ticks_in(_range).map{ |_t| tick_key(_t, _append) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def retention_key(_time, _append=nil)
|
59
|
+
key([tick_at(_time, retention).to_s, _append])
|
60
|
+
end
|
61
|
+
|
62
|
+
def two_dimensional?
|
63
|
+
!@opts[:three_dimensional]
|
64
|
+
end
|
65
|
+
|
66
|
+
def three_dimensional?
|
67
|
+
!!@opts[:three_dimensional]
|
68
|
+
end
|
69
|
+
|
70
|
+
def progressive?
|
71
|
+
!!@opts[:progressive]
|
72
|
+
end
|
73
|
+
|
74
|
+
def unique?
|
75
|
+
!!@opts[:unique]
|
76
|
+
end
|
77
|
+
|
78
|
+
def average?
|
79
|
+
!!@opts[:average]
|
80
|
+
end
|
81
|
+
|
82
|
+
def has_series?
|
83
|
+
false
|
84
|
+
end
|
85
|
+
|
86
|
+
def redis
|
87
|
+
@redis ||= EM::Hiredis.connect(FnordMetric.options[:redis_url]) # FIXPAUL
|
88
|
+
end
|
89
|
+
|
90
|
+
def sync_redis
|
91
|
+
@sync_redis ||= FnordMetric.mk_redis # FIXPAUL
|
92
|
+
end
|
93
|
+
|
94
|
+
def error!(msg)
|
95
|
+
FnordMetric.error(msg)
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module FnordMetric::GaugeCalculations
|
2
|
+
|
3
|
+
@@avg_per_session_proc = proc{ |_v, _t|
|
4
|
+
(_v.to_f / (sync_redis.get(tick_key(_t, :"sessions-count"))||1).to_i)
|
5
|
+
}
|
6
|
+
|
7
|
+
@@count_per_session_proc = proc{ |_v, _t|
|
8
|
+
(sync_redis.get(tick_key(_t, :"sessions-count"))||0).to_i
|
9
|
+
}
|
10
|
+
|
11
|
+
@@avg_per_count_proc = proc{ |_v, _t|
|
12
|
+
(_v.to_f / (sync_redis.get(tick_key(_t, :"value-count"))||1).to_i)
|
13
|
+
}
|
14
|
+
|
15
|
+
def ticks_in(r, _tick=tick, overflow=0)
|
16
|
+
(((r.last-r.first)/_tick.to_f).ceil+1+overflow).times.map{ |n| tick_at(r.first + _tick*(n-1), _tick) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def values_in(range)
|
20
|
+
ticks = ticks_in(range)
|
21
|
+
ticks << tick_at(range.last) if ticks.size == 0
|
22
|
+
values_at(ticks)
|
23
|
+
end
|
24
|
+
|
25
|
+
def value_at(time, opts={}, &block)
|
26
|
+
_t = tick_at(time)
|
27
|
+
|
28
|
+
_v = if respond_to?(:_value_at)
|
29
|
+
_value_at(key, _t)
|
30
|
+
else
|
31
|
+
_c = sync_redis.hget(key(:"mean-counts"), _t)
|
32
|
+
sync_redis.hget(key, _t)
|
33
|
+
end
|
34
|
+
|
35
|
+
calculate_value(_v, _t, opts, block, _c)
|
36
|
+
end
|
37
|
+
|
38
|
+
def values_at(times, opts={}, &block)
|
39
|
+
times = times.map{ |_t| tick_at(_t) }
|
40
|
+
Hash.new.tap do |ret|
|
41
|
+
if respond_to?(:_values_at)
|
42
|
+
_values_at(times, opts={}, &block)
|
43
|
+
else
|
44
|
+
ret_counts = sync_redis.hmget(key(:"mean-counts"), *times)
|
45
|
+
sync_redis.hmget(key, *times)
|
46
|
+
end.each_with_index do |_v, _n|
|
47
|
+
_t = times[_n]
|
48
|
+
_c = ret_counts ? ret_counts[_n] : nil
|
49
|
+
ret[_t] = calculate_value(_v, _t, opts, block, _c)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def calculate_value(_v, _t, opts, block, _c = nil)
|
55
|
+
block = @@avg_per_session_proc if unique? && average?
|
56
|
+
|
57
|
+
calc = if average? && _c
|
58
|
+
(_v.to_f / (_c||1).to_i)
|
59
|
+
elsif block
|
60
|
+
instance_exec(_v, _t, &block)
|
61
|
+
else
|
62
|
+
_v
|
63
|
+
end
|
64
|
+
|
65
|
+
if calc && @opts[:scale_by]
|
66
|
+
calc = calc.to_f * @opts[:scale_by].to_f
|
67
|
+
end
|
68
|
+
|
69
|
+
calc
|
70
|
+
end
|
71
|
+
|
72
|
+
def field_values_at(time, opts={}, &block)
|
73
|
+
opts[:max_fields] ||= 50
|
74
|
+
field_values = sync_redis.zrevrange(
|
75
|
+
tick_key(time, opts[:append]),
|
76
|
+
0, opts[:max_fields]-1,
|
77
|
+
:withscores => true
|
78
|
+
)
|
79
|
+
|
80
|
+
unless field_values.first.is_a?(Array)
|
81
|
+
field_values = field_values.in_groups_of(2).map do |key, val|
|
82
|
+
[key, Float(val)]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
field_values.map do |key, val|
|
87
|
+
[key, calculate_value("%.f" % val, time, opts, block)]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def field_values_total(time)
|
92
|
+
(sync_redis.get(tick_key(time, :count))||0).to_i
|
93
|
+
end
|
94
|
+
|
95
|
+
def fraction_values_in(range, _append=nil)
|
96
|
+
Hash.new{ |h,k| h[k] = [0,0] }.tap do |vals|
|
97
|
+
ticks_in(range, retention).each do |_tick|
|
98
|
+
sync_redis.hgetall(retention_key(_tick, _append)).each do |k, v|
|
99
|
+
kx = k.split("-")
|
100
|
+
vals[kx.first.to_i][kx.last == "denominator" ? 1 : 0] += v.to_f
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module FnordMetric::GaugeModifiers
|
2
|
+
|
3
|
+
def incr(gauge_name, value=1)
|
4
|
+
value = value.to_i
|
5
|
+
gauge = fetch_gauge(gauge_name)
|
6
|
+
assure_two_dimensional!(gauge)
|
7
|
+
if gauge.unique?
|
8
|
+
incr_uniq(gauge, value)
|
9
|
+
elsif gauge.average?
|
10
|
+
incr_avg(gauge, value)
|
11
|
+
else
|
12
|
+
incr_tick(gauge, value)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def incr_tick(gauge, value)
|
17
|
+
if gauge.progressive?
|
18
|
+
@redis.incrby(gauge.key(:head), value).callback do |head|
|
19
|
+
@redis.hsetnx(gauge.key, gauge.tick_at(time), head).callback do |_new|
|
20
|
+
@redis.hincrby(gauge.key, gauge.tick_at(time), value) unless _new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
else
|
24
|
+
@redis.hsetnx(gauge.key, gauge.tick_at(time), 0).callback do
|
25
|
+
@redis.hincrby(gauge.key, gauge.tick_at(time), value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def incr_uniq(gauge, value, field_name=nil)
|
31
|
+
return false if session_key.blank?
|
32
|
+
@redis.sadd(gauge.tick_key(time, :sessions), session_key).callback do |_new|
|
33
|
+
@redis.expire(gauge.tick_key(time, :sessions), gauge.tick)
|
34
|
+
if (_new == 1) || (_new == true) #redis vs. em-redis
|
35
|
+
@redis.incr(gauge.tick_key(time, :"sessions-count")).callback do |sc|
|
36
|
+
field_name ? incr_field_by(gauge, field_name, value) : incr_tick(gauge, value)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def incr_avg(gauge, value)
|
43
|
+
@redis.hincrby(gauge.key(:"mean-counts"), gauge.tick_at(time), 1).callback do
|
44
|
+
incr_tick(gauge, value)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def incr_field(gauge_name, field_name, value=1)
|
49
|
+
gauge = fetch_gauge(gauge_name)
|
50
|
+
assure_three_dimensional!(gauge)
|
51
|
+
if gauge.unique?
|
52
|
+
incr_uniq(gauge, value, field_name)
|
53
|
+
else
|
54
|
+
incr_field_by(gauge, field_name, value)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def incr_field_by(gauge, field_name, value)
|
59
|
+
@redis.zincrby(gauge.tick_key(time), value, field_name).callback do
|
60
|
+
@redis.incrby(gauge.tick_key(time, :count), 1)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def set_value(gauge_name, value)
|
65
|
+
gauge = fetch_gauge(gauge_name)
|
66
|
+
assure_two_dimensional!(gauge)
|
67
|
+
@redis.hset(gauge.key, gauge.tick_at(time), value)
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_field(gauge_name, field_name, value)
|
71
|
+
gauge = fetch_gauge(gauge_name)
|
72
|
+
assure_three_dimensional!(gauge)
|
73
|
+
@redis.zadd(gauge.tick_key(time), value, field_name)
|
74
|
+
end
|
75
|
+
|
76
|
+
def incr_numerator(ctx, series_name=:default, value=1, prog=false)
|
77
|
+
incr_fraction(ctx, series_name, :numerator, value, prog)
|
78
|
+
end
|
79
|
+
|
80
|
+
def incr_denominator(ctx, series_name=:default, value=1, prog=false)
|
81
|
+
incr_fraction(ctx, series_name, :denominator, value, prog)
|
82
|
+
end
|
83
|
+
|
84
|
+
def incr_fraction(ctx, series_name, part, value, prog)
|
85
|
+
return unless series_name = assure_series_exists!(series_name)
|
86
|
+
assure_has_series!
|
87
|
+
|
88
|
+
at = ctx.send(:time)
|
89
|
+
value = parse_numeric(value)
|
90
|
+
|
91
|
+
if prog
|
92
|
+
raise "FIXPAUL: not yet implemented: progressive fraction gauges"
|
93
|
+
end
|
94
|
+
|
95
|
+
ctx.redis_exec(:hincrby, retention_key(at, series_name), "#{tick_at(at)}-#{part}", value).callback do
|
96
|
+
ctx.redis_exec :expire, retention_key(at, series_name), retention
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def assure_has_series!
|
101
|
+
return true if has_series?
|
102
|
+
error! "error: #{caller[0].split(" ")[-1]} can only be used with series gauges"
|
103
|
+
end
|
104
|
+
|
105
|
+
def assure_two_dimensional!(gauge)
|
106
|
+
return true if gauge.two_dimensional?
|
107
|
+
error! "error: #{caller[0].split(" ")[-1]} can only be used with 2-dimensional gauges"
|
108
|
+
end
|
109
|
+
|
110
|
+
def assure_three_dimensional!(gauge)
|
111
|
+
return true unless gauge.two_dimensional?
|
112
|
+
error! "error: #{caller[0].split(" ")[-1]} can only be used with 3-dimensional gauges"
|
113
|
+
end
|
114
|
+
|
115
|
+
def assure_non_progressive!(gauge)
|
116
|
+
return true unless gauge.progressive?
|
117
|
+
error! "error: #{caller[0].split(" ")[-1]} can only be used with non-progressive gauges"
|
118
|
+
end
|
119
|
+
|
120
|
+
def assure_series_exists!(series_name)
|
121
|
+
if series_name == :default && @opts[:series].size > 1
|
122
|
+
error! "gauge '#{name}': don't know which series to increment"
|
123
|
+
elsif series_name == :default
|
124
|
+
return @opts[:series].first
|
125
|
+
elsif !series_name.respond_to?(:to_sym) || !@opts[:series].include?(series_name.to_sym)
|
126
|
+
error! "gauge '#{name}': unknown series: #{series_name}"
|
127
|
+
else
|
128
|
+
return series_name
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def parse_numeric(val)
|
133
|
+
if val.is_a?(Numeric)
|
134
|
+
return val
|
135
|
+
elsif val.is_a?(String) && val.match(/[0-9]+/)
|
136
|
+
val.to_i
|
137
|
+
elsif val.is_a?(String) && val.match(/[0-9]+(\.|,)[0-9]+/)
|
138
|
+
val.to_f
|
139
|
+
else
|
140
|
+
error! "gauge '#{name}': incr called with non-numerical value: #{val}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module FnordMetric::GaugeRendering
|
2
|
+
|
3
|
+
def renderable?
|
4
|
+
false
|
5
|
+
end
|
6
|
+
|
7
|
+
def render_to_event(*args)
|
8
|
+
{ :title => name }.merge(render(*args))
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def render_page(in_file)
|
14
|
+
exec_js = []
|
15
|
+
content = render_haml(in_file)
|
16
|
+
content.scan(/<FNORDMETRIC-GAUGEJS>(.*)<\/FNORDMETRIC-GAUGEJS>/m){ |x| exec_js << x }
|
17
|
+
content.gsub!(/<FNORDMETRIC-GAUGEJS>(.*)<\/FNORDMETRIC-GAUGEJS>/m, "")
|
18
|
+
{ :html => content, :exec => exec_js.flatten * "" }
|
19
|
+
end
|
20
|
+
|
21
|
+
def render_haml(in_file)
|
22
|
+
haml_engine = Haml::Engine.new(File.read(
|
23
|
+
File.expand_path("../../../web/haml/#{in_file}.haml", __FILE__)
|
24
|
+
)).render(binding)
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_interval(interval_str)
|
28
|
+
match = interval_str.match(/([0-9]+)-([0-9]+)/)
|
29
|
+
raise "invalid interval: #{interval_str}" unless match
|
30
|
+
(match[1].to_i..match[2].to_i)
|
31
|
+
end
|
32
|
+
|
33
|
+
# FIXPAUL: move to apphelper or something
|
34
|
+
def fancy_timerange(range)
|
35
|
+
[range.first, range.last].map do |time|
|
36
|
+
Time.at(time).strftime("%d.%m.%y %H:%M")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module FnordMetric::GaugeValidations
|
2
|
+
|
3
|
+
def validate_series!
|
4
|
+
if !@opts[:series].is_a?(Array) || @opts[:series].size == 0
|
5
|
+
raise "#{@opts[:key]}: missing option series"
|
6
|
+
end
|
7
|
+
|
8
|
+
if @opts[:series].size != @opts[:series].uniq.size
|
9
|
+
raise "#{@opts[:key]}: series are not unique"
|
10
|
+
end
|
11
|
+
|
12
|
+
@opts[:series] = @opts[:series].map(&:to_sym)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
class FnordMetric::DistributionGauge < FnordMetric::Gauge
|
2
|
+
|
3
|
+
def render(namespace, event)
|
4
|
+
@interval = parse_interval(event["interval"])
|
5
|
+
colors = ["#2F635E", "#606B36", "#727070", "#936953", "#CD645A", "#FACE4F", "#42436B"]
|
6
|
+
|
7
|
+
@opts[:value_scale] ||= 1
|
8
|
+
@opts[:precision] ||= 1
|
9
|
+
|
10
|
+
#@num_min =
|
11
|
+
#@num_max =
|
12
|
+
|
13
|
+
@histogram = FnordMetric::Histogram.new
|
14
|
+
@values = []
|
15
|
+
@histogram.set_opts(@opts)
|
16
|
+
|
17
|
+
@samples = 0
|
18
|
+
|
19
|
+
@mmm_timeseries = Hash.new do |h,k|
|
20
|
+
h[k] = { :min => nil, :max => 0, :avg => [] }
|
21
|
+
end
|
22
|
+
|
23
|
+
ticks_in(@interval, tick, 1).each do |_tick|
|
24
|
+
tkey = tick_key(_tick, :histogram)
|
25
|
+
|
26
|
+
sync_redis.hgetall(tkey).each do |_val, _count|
|
27
|
+
_count = _count.to_f
|
28
|
+
_val = _val.to_f * @opts[:value_scale]
|
29
|
+
|
30
|
+
@samples += _count
|
31
|
+
|
32
|
+
@histogram[_val] += _count
|
33
|
+
@values += [_val] * _count
|
34
|
+
|
35
|
+
if !@mmm_timeseries[_tick][:min] || (_val < @mmm_timeseries[_tick][:min])
|
36
|
+
@mmm_timeseries[_tick][:min] = _val
|
37
|
+
end
|
38
|
+
|
39
|
+
if _val > @mmm_timeseries[_tick][:max]
|
40
|
+
@mmm_timeseries[_tick][:max] = _val
|
41
|
+
end
|
42
|
+
|
43
|
+
@mmm_timeseries[_tick][:avg] += [_val] * _count
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
if @opts[:value_ranges]
|
48
|
+
@histogram_mode = @opts[:value_ranges]
|
49
|
+
else
|
50
|
+
@histogram_mode = [23, @histogram.max || 1].min
|
51
|
+
end
|
52
|
+
|
53
|
+
@mmm_timeseries_arr = @mmm_timeseries.to_a
|
54
|
+
.map{ |k,v| [k, Hash[v.map{ |vk, vv| [vk, (vv.is_a?(Numeric) || vv.is_a?(Array)) ? vv : 0 ] }]] }
|
55
|
+
.sort{ |a,b| a.first.to_i <=> b.first.to_i}
|
56
|
+
|
57
|
+
render_page(:distribution_gauge)
|
58
|
+
end
|
59
|
+
|
60
|
+
def execute(cmd, context, *args)
|
61
|
+
return observe(context, args.first) if cmd == :observe
|
62
|
+
FnordMetric.error("gauge '#{name}': unknown command: #{cmd}")
|
63
|
+
end
|
64
|
+
|
65
|
+
def renderable?
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def observe(ctx, value)
|
72
|
+
at = ctx.send(:time)
|
73
|
+
|
74
|
+
if value.is_a?(String) && value.match(/[0-9]+/)
|
75
|
+
value = value.to_i
|
76
|
+
elsif value.is_a?(String) && value.match(/[0-9]+(\.|,)[0-9]+/)
|
77
|
+
value = value.to_f
|
78
|
+
end
|
79
|
+
|
80
|
+
unless value.is_a?(Float) || value.is_a?(Fixnum)
|
81
|
+
return FnordMetric.error("gauge '#{name}': observe called with non-numerical value: #{value}")
|
82
|
+
end
|
83
|
+
|
84
|
+
ctx.redis_exec :hincrby, tick_key(at, :histogram), value.round(2), 1
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# ServerHealthGauge
|
2
|
+
# here be dragons....
|
3
|
+
#
|
4
|
+
# This gauge listens for JSON events that look like this:
|
5
|
+
#
|
6
|
+
# {
|
7
|
+
# "hostname": "my-hostname",
|
8
|
+
# "uptime": 1231523,
|
9
|
+
# "load_avg": [0.32, 0.16, 0.13],
|
10
|
+
# "mem": { "total": 4069, "free": 1096 },
|
11
|
+
# "swap": { "total": 0, "free": 0 },
|
12
|
+
# "disk_usage": "43%"
|
13
|
+
# }
|
@@ -0,0 +1,138 @@
|
|
1
|
+
class FnordMetric::TimeseriesGauge < FnordMetric::Gauge
|
2
|
+
|
3
|
+
def initialize(opts)
|
4
|
+
super(opts)
|
5
|
+
|
6
|
+
@opts[:series] = @opts[:series].map(&:to_sym)
|
7
|
+
|
8
|
+
if @opts[:calculate]
|
9
|
+
unless [:sum, :average, :progressive_sum].include?(@opts[:calculate].to_sym)
|
10
|
+
raise "unknown calculate option: #{@opts[:calculate]}"
|
11
|
+
end
|
12
|
+
@calculate = @opts[:calculate].to_sym
|
13
|
+
end
|
14
|
+
|
15
|
+
@calculate ||= :sum
|
16
|
+
@calculate_proc = lambda{ |c,d| d > 0 ? (c/d.to_f).round(2) : c }
|
17
|
+
end
|
18
|
+
|
19
|
+
def render(namespace, event)
|
20
|
+
@interval = parse_interval(event["interval"])
|
21
|
+
colors = FnordMetric::COLORS.dup
|
22
|
+
|
23
|
+
@series = Hash.new
|
24
|
+
@zooms = FnordMetric::TICKS[tick, @interval.size]
|
25
|
+
|
26
|
+
@total = 0
|
27
|
+
|
28
|
+
@opts[:series].each do |series|
|
29
|
+
ts = FnordMetric::Timeseries.new
|
30
|
+
|
31
|
+
fraction_values_in(@interval, series).each do |time, frac|
|
32
|
+
@total += frac.first # FIXPAUL
|
33
|
+
ts.incr_fraction(time, *frac)
|
34
|
+
end
|
35
|
+
|
36
|
+
@series[series] = {
|
37
|
+
:color => colors.unshift(colors.pop).first,
|
38
|
+
:data => Hash[@zooms.map{ |int| [int, ts.timeseries(@interval, int) ] }],
|
39
|
+
:timeseries => ts
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
render_page(:timeseries_gauge)
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute(cmd, context, *args)
|
47
|
+
return incr(context, *args) if cmd == :incr
|
48
|
+
return incr_numerator(context, *args) if cmd == :incr_numerator
|
49
|
+
return incr_denominator(context, *args) if cmd == :incr_denominator
|
50
|
+
|
51
|
+
FnordMetric.error("gauge '#{name}': unknown command: #{cmd}")
|
52
|
+
end
|
53
|
+
|
54
|
+
def renderable?
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
def has_series?
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def incr(ctx, series_name = :default, value = 1)
|
65
|
+
if @calculate == :average
|
66
|
+
incr_numerator(ctx, series_name, value)
|
67
|
+
incr_denominator(ctx, series_name, 1)
|
68
|
+
elsif @calculate == :sum
|
69
|
+
incr_numerator(ctx, series_name, value)
|
70
|
+
elsif @calculate == :progressive_sum
|
71
|
+
incr_numerator(ctx, series_name, value, true)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
# class FnordMetric::NumericGauge < FnordMetric::MultiGauge
|
78
|
+
|
79
|
+
# def initialize(opts)
|
80
|
+
# super(opts)
|
81
|
+
|
82
|
+
# validate_series!
|
83
|
+
# validate_ticks!
|
84
|
+
|
85
|
+
# timeline_widget(
|
86
|
+
# :tab => "Overview",
|
87
|
+
# :title => "Total #{key_nouns.last}",
|
88
|
+
# :ticks => @opts[:ticks],
|
89
|
+
# :series => @opts[:series],
|
90
|
+
# :series_titles => Hash[@opts[:series].map{|s| [s, s]}],
|
91
|
+
# :autoupdate => 10,
|
92
|
+
# :height => 350
|
93
|
+
# ).on(:values_at) do |_series, _ticks, _tick|
|
94
|
+
# series_count_metrics[_series][_tick].values_at(_ticks)
|
95
|
+
# end
|
96
|
+
|
97
|
+
# numbers_widget(
|
98
|
+
# :tab => "Overview",
|
99
|
+
# :title => "Total #{key_nouns.last}",
|
100
|
+
# :series => @opts[:series],
|
101
|
+
# :series_titles => Hash[@opts[:series].map{|s| [s, s]}],
|
102
|
+
# :autoupdate => 2
|
103
|
+
# ).on(:values_for) do |_series|
|
104
|
+
# render_series_numbers(_series.to_sym)
|
105
|
+
# end
|
106
|
+
|
107
|
+
# end
|
108
|
+
|
109
|
+
# def react(event)
|
110
|
+
# if event["_class"] == "incrby" || event["_class"] == "incr"
|
111
|
+
# series = event["series"]
|
112
|
+
# series ||= @opts[:series][0] if @opts[:series].size == 1
|
113
|
+
# incr_series(series.to_sym, event["_time"], event["value"])
|
114
|
+
# end
|
115
|
+
# end
|
116
|
+
|
117
|
+
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
# def render_series_numbers(series)
|
122
|
+
# _t = Time.now.to_i
|
123
|
+
|
124
|
+
# {}.tap do |out|
|
125
|
+
# @opts[:ticks].each do |tick|
|
126
|
+
# out["#{tick}-now"] = {
|
127
|
+
# :value => series_count_metrics[series][tick].value_at(_t),
|
128
|
+
# :desc => "$formatTimeRangePre(#{tick}, 0)"
|
129
|
+
# }
|
130
|
+
# out["#{tick}-last"] = {
|
131
|
+
# :value => series_count_metrics[series][tick].value_at(_t-tick),
|
132
|
+
# :desc => "$formatTimeRangePre(#{tick}, -1)"
|
133
|
+
# }
|
134
|
+
# end
|
135
|
+
# end
|
136
|
+
# end
|
137
|
+
|
138
|
+
# end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class FnordMetric::ToplistGauge < FnordMetric::Gauge
|
2
|
+
|
3
|
+
def render(namespace, event)
|
4
|
+
@interval = parse_interval(event["interval"])
|
5
|
+
|
6
|
+
@toplist = FnordMetric::Toplist.new
|
7
|
+
@all_ticks = ticks_in(@interval, tick, 1)
|
8
|
+
|
9
|
+
@all_ticks.each do |_tick|
|
10
|
+
field_values_at(_tick, :limit => top_k, :append => :toplist).each do |*args|
|
11
|
+
item, count = args.flatten[0..1] # what the fnord... ~paul
|
12
|
+
@toplist.incr_item(_tick, item, count)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
@toplist.total = @all_ticks.inject(0){ |s,t| s + sync_redis.get(tick_key(t, :total)).to_i }
|
17
|
+
|
18
|
+
render_page(:toplist_gauge)
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute(cmd, context, *args)
|
22
|
+
return observe(context, args.first) if cmd == :observe
|
23
|
+
FnordMetric.error("gauge '#{name}': unknown command: #{cmd}")
|
24
|
+
end
|
25
|
+
|
26
|
+
def renderable?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def observe(ctx, item)
|
33
|
+
at = ctx.send(:time)
|
34
|
+
ctx.redis_exec :zincrby, tick_key(at, :toplist), 1, item
|
35
|
+
ctx.redis_exec :incrby, tick_key(at, :total), 1
|
36
|
+
ctx.redis_exec :zremrangebyrank, tick_key(at, :toplist), 0, -top_k
|
37
|
+
ctx.redis_exec :expire, tick_key(at, :toplist), retention
|
38
|
+
end
|
39
|
+
|
40
|
+
def top_k
|
41
|
+
(@opts[:top_k] || 1000).to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|