johnf-fnordmetric 1.2.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. data/Gemfile +6 -0
  2. data/Rakefile +9 -0
  3. data/fnordmetric.gemspec +41 -0
  4. data/lib/fnordmetric/acceptors/acceptor.rb +42 -0
  5. data/lib/fnordmetric/acceptors/amqp_acceptor.rb +56 -0
  6. data/lib/fnordmetric/acceptors/fyrehose_acceptor.rb +43 -0
  7. data/lib/fnordmetric/acceptors/stomp_acceptor.rb +71 -0
  8. data/lib/fnordmetric/acceptors/tcp_acceptor.rb +58 -0
  9. data/lib/fnordmetric/acceptors/udp_acceptor.rb +37 -0
  10. data/lib/fnordmetric/api.rb +46 -0
  11. data/lib/fnordmetric/cache.rb +20 -0
  12. data/lib/fnordmetric/context.rb +96 -0
  13. data/lib/fnordmetric/defaults.rb +22 -0
  14. data/lib/fnordmetric/enterprise/compatibility_handler.rb +42 -0
  15. data/lib/fnordmetric/ext.rb +75 -0
  16. data/lib/fnordmetric/gauge.rb +98 -0
  17. data/lib/fnordmetric/gauge_calculations.rb +106 -0
  18. data/lib/fnordmetric/gauge_modifiers.rb +144 -0
  19. data/lib/fnordmetric/gauge_rendering.rb +40 -0
  20. data/lib/fnordmetric/gauge_validations.rb +15 -0
  21. data/lib/fnordmetric/gauges/distribution_gauge.rb +87 -0
  22. data/lib/fnordmetric/gauges/server_health_gauge.rb +13 -0
  23. data/lib/fnordmetric/gauges/timeseries_gauge.rb +138 -0
  24. data/lib/fnordmetric/gauges/toplist_gauge.rb +44 -0
  25. data/lib/fnordmetric/histogram.rb +64 -0
  26. data/lib/fnordmetric/logger.rb +63 -0
  27. data/lib/fnordmetric/namespace.rb +208 -0
  28. data/lib/fnordmetric/session.rb +139 -0
  29. data/lib/fnordmetric/standalone.rb +20 -0
  30. data/lib/fnordmetric/timeseries.rb +79 -0
  31. data/lib/fnordmetric/toplist.rb +61 -0
  32. data/lib/fnordmetric/udp_client.rb +22 -0
  33. data/lib/fnordmetric/util.rb +25 -0
  34. data/lib/fnordmetric/version.rb +3 -0
  35. data/lib/fnordmetric/web/app.rb +63 -0
  36. data/lib/fnordmetric/web/app_helpers.rb +42 -0
  37. data/lib/fnordmetric/web/dashboard.rb +40 -0
  38. data/lib/fnordmetric/web/event.rb +99 -0
  39. data/lib/fnordmetric/web/reactor.rb +127 -0
  40. data/lib/fnordmetric/web/web.rb +59 -0
  41. data/lib/fnordmetric/web/websocket.rb +41 -0
  42. data/lib/fnordmetric/widget.rb +82 -0
  43. data/lib/fnordmetric/widgets/bars_widget.rb +44 -0
  44. data/lib/fnordmetric/widgets/html_widget.rb +28 -0
  45. data/lib/fnordmetric/widgets/numbers_widget.rb +80 -0
  46. data/lib/fnordmetric/widgets/pie_widget.rb +23 -0
  47. data/lib/fnordmetric/widgets/timeseries_widget.rb +65 -0
  48. data/lib/fnordmetric/widgets/toplist_widget.rb +68 -0
  49. data/lib/fnordmetric/worker.rb +89 -0
  50. data/lib/fnordmetric/zero_config_gauge.rb +138 -0
  51. data/lib/fnordmetric.rb +149 -0
  52. data/run_specs.sh +11 -0
  53. data/spec/api_spec.rb +49 -0
  54. data/spec/context_spec.rb +42 -0
  55. data/spec/dashboard_spec.rb +38 -0
  56. data/spec/event_spec.rb +170 -0
  57. data/spec/ext_spec.rb +14 -0
  58. data/spec/fnordmetric_spec.rb +56 -0
  59. data/spec/gauge_like_shared.rb +56 -0
  60. data/spec/gauge_modifiers_spec.rb +583 -0
  61. data/spec/gauge_spec.rb +230 -0
  62. data/spec/namespace_spec.rb +114 -0
  63. data/spec/session_spec.rb +231 -0
  64. data/spec/spec_helper.rb +49 -0
  65. data/spec/tcp_acceptor_spec.rb +35 -0
  66. data/spec/timeseries_gauge_spec.rb +56 -0
  67. data/spec/udp_acceptor_spec.rb +35 -0
  68. data/spec/util_spec.rb +46 -0
  69. data/spec/widget_spec.rb +113 -0
  70. data/spec/worker_spec.rb +40 -0
  71. data/web/.gitignore +4 -0
  72. data/web/build.sh +34 -0
  73. data/web/css/fnordmetric.core.css +868 -0
  74. data/web/fnordmetric-core.css +1409 -0
  75. data/web/fnordmetric-core.js +3420 -0
  76. data/web/fnordmetric-ui.css +282 -0
  77. data/web/fnordmetric-ui.js +12032 -0
  78. data/web/haml/app.haml +20 -0
  79. data/web/haml/distribution_gauge.haml +118 -0
  80. data/web/haml/timeseries_gauge.haml +80 -0
  81. data/web/haml/toplist_gauge.haml +194 -0
  82. data/web/img/head.png +0 -0
  83. data/web/img/list.png +0 -0
  84. data/web/img/list_active.png +0 -0
  85. data/web/img/list_hover.png +0 -0
  86. data/web/img/loader.gif +0 -0
  87. data/web/img/loader_white.gif +0 -0
  88. data/web/img/navbar.png +0 -0
  89. data/web/img/navbar_btn.png +0 -0
  90. data/web/img/picto_gauge.png +0 -0
  91. data/web/js/fnordmetric.bars_widget.js +178 -0
  92. data/web/js/fnordmetric.dashboard_view.js +99 -0
  93. data/web/js/fnordmetric.gauge_explorer.js +173 -0
  94. data/web/js/fnordmetric.gauge_view.js +260 -0
  95. data/web/js/fnordmetric.html_widget.js +21 -0
  96. data/web/js/fnordmetric.js +315 -0
  97. data/web/js/fnordmetric.numbers_widget.js +122 -0
  98. data/web/js/fnordmetric.overview_view.js +35 -0
  99. data/web/js/fnordmetric.pie_widget.js +118 -0
  100. data/web/js/fnordmetric.realtime_timeline_widget.js +175 -0
  101. data/web/js/fnordmetric.session_view.js +342 -0
  102. data/web/js/fnordmetric.timeline_widget.js +333 -0
  103. data/web/js/fnordmetric.timeseries_widget.js +405 -0
  104. data/web/js/fnordmetric.toplist_widget.js +119 -0
  105. data/web/js/fnordmetric.ui.js +91 -0
  106. data/web/js/fnordmetric.util.js +248 -0
  107. data/web/vendor/font-awesome/css/font-awesome-ie7.min.css +22 -0
  108. data/web/vendor/font-awesome/css/font-awesome.css +540 -0
  109. data/web/vendor/font-awesome/css/font-awesome.min.css +33 -0
  110. data/web/vendor/font-awesome/font/FontAwesome.otf +0 -0
  111. data/web/vendor/font-awesome/font/fontawesome-webfont.eot +0 -0
  112. data/web/vendor/font-awesome/font/fontawesome-webfont.svg +284 -0
  113. data/web/vendor/font-awesome/font/fontawesome-webfont.ttf +0 -0
  114. data/web/vendor/font-awesome/font/fontawesome-webfont.woff +0 -0
  115. data/web/vendor/jquery-1.6.2.min.js +18 -0
  116. data/web/vendor/jquery-ui.min.js +6 -0
  117. data/web/vendor/jquery.combobox.js +129 -0
  118. data/web/vendor/jquery.maskedinput.js +252 -0
  119. 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