fnordmetric 0.7.5 → 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. data/doc/V1.0-ROADMAP +97 -0
  2. data/doc/full_example.rb +95 -511
  3. data/doc/legacy_example.rb +640 -0
  4. data/doc/minimal_example.rb +26 -0
  5. data/doc/preview3.png +0 -0
  6. data/fnordmetric.gemspec +3 -2
  7. data/lib/fnordmetric/acceptors/acceptor.rb +29 -0
  8. data/lib/fnordmetric/{inbound_stream.rb → acceptors/tcp_acceptor.rb} +8 -5
  9. data/lib/fnordmetric/{inbound_datagram.rb → acceptors/udp_acceptor.rb} +9 -8
  10. data/lib/fnordmetric/api.rb +2 -2
  11. data/lib/fnordmetric/context.rb +37 -18
  12. data/lib/fnordmetric/defaults.rb +9 -0
  13. data/lib/fnordmetric/ext.rb +72 -0
  14. data/lib/fnordmetric/gauge.rb +37 -10
  15. data/lib/fnordmetric/gauge_calculations.rb +38 -16
  16. data/lib/fnordmetric/gauge_modifiers.rb +67 -0
  17. data/lib/fnordmetric/gauge_rendering.rb +40 -0
  18. data/lib/fnordmetric/gauge_validations.rb +15 -0
  19. data/lib/fnordmetric/gauges/distribution_gauge.rb +85 -0
  20. data/lib/fnordmetric/gauges/timeseries_gauge.rb +143 -0
  21. data/lib/fnordmetric/gauges/toplist_gauge.rb +44 -0
  22. data/lib/fnordmetric/histogram.rb +57 -0
  23. data/lib/fnordmetric/logger.rb +42 -36
  24. data/lib/fnordmetric/namespace.rb +47 -23
  25. data/lib/fnordmetric/session.rb +6 -6
  26. data/lib/fnordmetric/standalone.rb +15 -35
  27. data/lib/fnordmetric/timeseries.rb +79 -0
  28. data/lib/fnordmetric/toplist.rb +61 -0
  29. data/lib/fnordmetric/version.rb +1 -1
  30. data/lib/fnordmetric/web/app.rb +122 -0
  31. data/lib/fnordmetric/web/app_helpers.rb +42 -0
  32. data/lib/fnordmetric/{dashboard.rb → web/dashboard.rb} +4 -0
  33. data/lib/fnordmetric/{event.rb → web/event.rb} +7 -2
  34. data/lib/fnordmetric/web/reactor.rb +87 -0
  35. data/lib/fnordmetric/web/web.rb +53 -0
  36. data/lib/fnordmetric/web/websocket.rb +38 -0
  37. data/lib/fnordmetric/widgets/bars_widget.rb +44 -0
  38. data/lib/fnordmetric/{html_widget.rb → widgets/html_widget.rb} +0 -0
  39. data/lib/fnordmetric/widgets/numbers_widget.rb +56 -0
  40. data/lib/fnordmetric/{pie_widget.rb → widgets/pie_widget.rb} +0 -0
  41. data/lib/fnordmetric/widgets/timeseries_widget.rb +55 -0
  42. data/lib/fnordmetric/widgets/toplist_widget.rb +64 -0
  43. data/lib/fnordmetric/worker.rb +26 -25
  44. data/lib/fnordmetric.rb +85 -115
  45. data/readme.md +362 -0
  46. data/spec/gauge_like_shared.rb +54 -0
  47. data/spec/gauge_spec.rb +2 -36
  48. data/spec/namespace_spec.rb +25 -11
  49. data/spec/spec_helper.rb +4 -0
  50. data/spec/{inbound_stream_spec.rb → tcp_acceptor_spec.rb} +3 -3
  51. data/spec/timeseries_gauge_spec.rb +54 -0
  52. data/spec/{inbound_datagram_spec.rb → udp_acceptor_spec.rb} +3 -3
  53. data/web/fnordmetric.css +786 -0
  54. data/web/haml/app.haml +38 -0
  55. data/web/haml/distribution_gauge.haml +118 -0
  56. data/web/haml/timeseries_gauge.haml +80 -0
  57. data/web/haml/toplist_gauge.haml +194 -0
  58. data/web/img/head.png +0 -0
  59. data/web/img/list.png +0 -0
  60. data/web/img/list_active.png +0 -0
  61. data/web/img/list_hover.png +0 -0
  62. data/web/img/loader_white.gif +0 -0
  63. data/web/img/navbar.png +0 -0
  64. data/web/img/navbar_btn.png +0 -0
  65. data/web/img/picto_gauge.png +0 -0
  66. data/web/js/fnordmetric.bars_widget.js +178 -0
  67. data/web/js/fnordmetric.dashboard_view.js +99 -0
  68. data/web/js/fnordmetric.gauge_view.js +260 -0
  69. data/web/js/fnordmetric.html_widget.js +21 -0
  70. data/web/js/fnordmetric.js +255 -0
  71. data/web/js/fnordmetric.numbers_widget.js +121 -0
  72. data/web/js/fnordmetric.overview_view.js +35 -0
  73. data/web/js/fnordmetric.pie_widget.js +118 -0
  74. data/web/js/fnordmetric.realtime_timeline_widget.js +175 -0
  75. data/web/js/fnordmetric.session_view.js +343 -0
  76. data/web/js/fnordmetric.timeline_widget.js +333 -0
  77. data/web/js/fnordmetric.timeseries_widget.js +388 -0
  78. data/web/js/fnordmetric.toplist_widget.js +112 -0
  79. data/web/js/fnordmetric.ui.js +91 -0
  80. data/web/js/fnordmetric.util.js +244 -0
  81. data/{pub → web}/loader.gif +0 -0
  82. data/web/vendor/d3.v2.js +9382 -0
  83. data/web/vendor/font-awesome/css/font-awesome.css +239 -0
  84. data/web/vendor/font-awesome/font/fontawesome-webfont.eot +0 -0
  85. data/web/vendor/font-awesome/font/fontawesome-webfont.svg +175 -0
  86. data/web/vendor/font-awesome/font/fontawesome-webfont.svgz +0 -0
  87. data/web/vendor/font-awesome/font/fontawesome-webfont.ttf +0 -0
  88. data/web/vendor/font-awesome/font/fontawesome-webfont.woff +0 -0
  89. data/web/vendor/jquery-1.6.2.min.js +18 -0
  90. data/web/vendor/jquery-ui.min.js +413 -0
  91. data/web/vendor/jquery.maskedinput.js +252 -0
  92. data/web/vendor/rickshaw.css +286 -0
  93. data/web/vendor/rickshaw.fnordmetric.js +2676 -0
  94. metadata +129 -79
  95. data/Gemfile +0 -6
  96. data/README.md +0 -404
  97. data/Rakefile +0 -6
  98. data/doc/version +0 -1
  99. data/haml/app.haml +0 -79
  100. data/haml/widget.haml +0 -9
  101. data/lib/fnordmetric/app.rb +0 -163
  102. data/lib/fnordmetric/average_metric.rb +0 -7
  103. data/lib/fnordmetric/bars_widget.rb +0 -26
  104. data/lib/fnordmetric/combine_metric.rb +0 -7
  105. data/lib/fnordmetric/count_metric.rb +0 -13
  106. data/lib/fnordmetric/funnel_widget.rb +0 -2
  107. data/lib/fnordmetric/metric.rb +0 -80
  108. data/lib/fnordmetric/metric_api.rb +0 -37
  109. data/lib/fnordmetric/numbers_widget.rb +0 -26
  110. data/lib/fnordmetric/report.rb +0 -29
  111. data/lib/fnordmetric/sum_metric.rb +0 -13
  112. data/lib/fnordmetric/timeline_widget.rb +0 -30
  113. data/lib/fnordmetric/toplist_widget.rb +0 -25
  114. data/pub/fnordmetric.css +0 -145
  115. data/pub/fnordmetric.js +0 -1179
  116. data/pub/vendor/highcharts.js +0 -170
  117. data/pub/vendor/jquery-1.6.1.min.js +0 -18
@@ -1,9 +1,9 @@
1
- class FnordMetric::InboundStream < EventMachine::Connection
1
+ class FnordMetric::TCPAcceptor < EventMachine::Connection
2
2
  @@opts = nil
3
3
 
4
4
  def self.start(opts)
5
5
  @@opts = opts
6
- EM.start_server(*(opts[:inbound_stream] << self))
6
+ EM.start_server(*(opts[:listen] + [self]))
7
7
  end
8
8
 
9
9
  def self.options(opts)
@@ -30,17 +30,16 @@ class FnordMetric::InboundStream < EventMachine::Connection
30
30
  def push_next_event
31
31
  return true if @events.empty?
32
32
  @events_buffered -= 1
33
- @api.event(@events.pop)
33
+ api.event(@events.pop)
34
34
  close_connection?
35
35
  EM.next_tick(&method(:push_next_event))
36
36
  end
37
37
 
38
38
  def close_connection?
39
- @api.disconnect unless @streaming || (@events_buffered!=0)
39
+ #@backend.hangup unless @streaming || (@events_buffered!=0)
40
40
  end
41
41
 
42
42
  def post_init
43
- @api = FnordMetric::API.new(@@opts)
44
43
  @events_buffered = 0
45
44
  @streaming = true
46
45
  @buffer = ""
@@ -51,4 +50,8 @@ class FnordMetric::InboundStream < EventMachine::Connection
51
50
  @streaming = false
52
51
  close_connection?
53
52
  end
53
+
54
+ def api
55
+ @api ||= FnordMetric::API.new(FnordMetric.options)
56
+ end
54
57
  end
@@ -1,4 +1,4 @@
1
- class FnordMetric::InboundDatagram < EventMachine::Connection
1
+ class FnordMetric::UDPAcceptor < EventMachine::Connection
2
2
 
3
3
  class << self
4
4
  attr_accessor :opts
@@ -6,7 +6,8 @@ class FnordMetric::InboundDatagram < EventMachine::Connection
6
6
 
7
7
  def self.start(opts)
8
8
  self.opts = opts
9
- EM.open_datagram_socket(*(opts[:inbound_stream] << self << opts))
9
+
10
+ EM.open_datagram_socket(*(opts[:listen] << self << opts))
10
11
  end
11
12
 
12
13
  def receive_data(event)
@@ -16,20 +17,20 @@ class FnordMetric::InboundDatagram < EventMachine::Connection
16
17
 
17
18
  def push_next_event
18
19
  return true if events.empty?
19
- api.event(@events.pop)
20
+ ev = @events.pop
21
+ api.event(ev)
20
22
  EM.next_tick(&method(:push_next_event))
21
23
  end
22
24
 
23
25
  def unbind
24
- api.disconnect
25
- end
26
-
27
- def api
28
- @api ||= FnordMetric::API.new(self.class.opts)
26
+ #backend.hangup
29
27
  end
30
28
 
31
29
  def events
32
30
  @events ||= []
33
31
  end
34
32
 
33
+ def api
34
+ @api ||= FnordMetric::API.new(FnordMetric.options)
35
+ end
35
36
  end
@@ -1,7 +1,8 @@
1
1
  class FnordMetric::API
2
+
2
3
  @@opts = nil
3
4
 
4
- def initialize opts
5
+ def initialize(opts={})
5
6
  @@opts = FnordMetric.default_options(opts)
6
7
  connect
7
8
  end
@@ -32,7 +33,6 @@ class FnordMetric::API
32
33
 
33
34
  def push_event(event_id, event_data)
34
35
  prefix = @@opts[:redis_prefix]
35
- @redis.hincrby "#{prefix}-testdata", "events_received", 1
36
36
  @redis.hincrby "#{prefix}-stats", "events_received", 1
37
37
  @redis.set "#{prefix}-event-#{event_id}", event_data
38
38
  @redis.lpush "#{prefix}-queue", event_id
@@ -2,6 +2,18 @@ class FnordMetric::Context
2
2
 
3
3
  include FnordMetric::GaugeModifiers
4
4
 
5
+ class Proxy
6
+
7
+ def initialize(_ref)
8
+ @ref = _ref
9
+ end
10
+
11
+ def method_missing(method, *args, &block)
12
+ @ref.dispatch(method, *args, &block)
13
+ end
14
+
15
+ end
16
+
5
17
  def initialize(opts, block)
6
18
  @block = block
7
19
  @opts = opts
@@ -10,10 +22,31 @@ class FnordMetric::Context
10
22
  def call(event, redis)
11
23
  @redis = redis
12
24
  @event = event
13
- self.instance_eval(&@block)
25
+ proxy.instance_eval(&@block)
14
26
  rescue Exception => e
15
- raise e if ENV['FNORDMETRIC_ENV'] == 'test'
27
+ raise e if ENV['FNORDMETRIC_ENV'] == 'test'
16
28
  puts "error: #{e.message}"
29
+ puts e.backtrace.push("").join("\n") if ENV['FNORDMETRIC_ENV'] == 'dev'
30
+ end
31
+
32
+ def proxy
33
+ @proxy ||= Proxy.new(self)
34
+ end
35
+
36
+ def dispatch(method, *args, &block)
37
+ if args.size > 0 && @opts[:gauges][args[0]].try(:renderable?)
38
+ @opts[:gauges][args.delete_at(0)].execute(method, *args.unshift(self), &block)
39
+ else
40
+ send(method, *args, &block)
41
+ end
42
+ rescue Exception => e
43
+ raise e if ENV['FNORDMETRIC_ENV'] == 'test'
44
+ puts "error: #{e.message}"
45
+ puts e.backtrace.push("\n").join("\n") if ENV['FNORDMETRIC_ENV'] == 'dev'
46
+ end
47
+
48
+ def redis_exec(*args)
49
+ @redis.send(*args)
17
50
  end
18
51
 
19
52
  private
@@ -35,7 +68,7 @@ private
35
68
  end
36
69
 
37
70
  protected
38
-
71
+
39
72
  def fetch_gauge(_gauge)
40
73
  _gauge.is_a?(FnordMetric::Gauge) ? _gauge : @opts[:gauges].fetch(_gauge)
41
74
  rescue
@@ -43,23 +76,9 @@ protected
43
76
  end
44
77
 
45
78
  def error!(msg)
46
- FnordMetric.error!(msg)
79
+ FnordMetric.error(msg)
47
80
  end
48
81
 
49
- def assure_two_dimensional!(gauge)
50
- return true if gauge.two_dimensional?
51
- error! "error: #{caller[0].split(" ")[-1]} can only be used with 2-dimensional gauges"
52
- end
53
-
54
- def assure_three_dimensional!(gauge)
55
- return true unless gauge.two_dimensional?
56
- error! "error: #{caller[0].split(" ")[-1]} can only be used with 3-dimensional gauges"
57
- end
58
-
59
- def assure_non_progressive!(gauge)
60
- return true unless gauge.progressive?
61
- error! "error: #{caller[0].split(" ")[-1]} can only be used with non-progressive gauges"
62
- end
63
82
 
64
83
  end
65
84
 
@@ -0,0 +1,9 @@
1
+ FnordMetric::COLORS = ["#4572a7", "#aa4643", "#89a54e", "#80699b", "#3d96ae", "#db843d"].reverse
2
+
3
+ FnordMetric::TICKS = lambda{ |tick, span| [tick, 60, 300, 1200, 3600, 86400]
4
+ .select{ |t| (t >= tick) && ((span/t) > 5) }
5
+ .uniq }
6
+
7
+
8
+ FnordMetric::DEFAULT_PROC = lambda{ |arg| }
9
+
@@ -1,3 +1,75 @@
1
+ module Enumerable
2
+
3
+ def each_with_log(total = nil, &block)
4
+ log_every = ((total ||= self.count) / 150)
5
+ self.each_with_index do |item, index|
6
+ if index % log_every == 0
7
+ STDOUT.puts "#{index}/#{total} (#{((index/total.to_f)*100).to_i}%)"
8
+ end
9
+ block.call(item, index)
10
+ end
11
+ end
12
+
13
+ end
14
+
15
+ module Haml::Filters::Gaugejs
16
+ include Haml::Filters::Base
17
+
18
+ def render(text)
19
+ "<FNORDMETRIC-GAUGEJS>#{text}</FNORDMETRIC-GAUGEJS>"
20
+ end
21
+ end
22
+
1
23
  class Symbol
2
24
  alias :intern :to_sym
25
+ end
26
+
27
+ class Range
28
+
29
+ def size
30
+ self.last - self.first
31
+ end
32
+
33
+ end
34
+
35
+ class Array
36
+
37
+ def mean
38
+ return 0 if empty?
39
+ inject(&:+).to_f / size
40
+ end
41
+
42
+ alias :average :mean
43
+
44
+ def median
45
+ return 0 if empty?
46
+ (_sorted = self.dup.sort)[_sorted.size/2]
47
+ end
48
+
49
+ def range
50
+ return 0 if empty?
51
+ max - min
52
+ end
53
+
54
+ def mode
55
+ return 0 if empty?
56
+ inject({}){ |h,v| h[v] = h[v].to_i+1; h }.to_a
57
+ .sort{ |a,b| b.last <=> a.last }[0][0]
58
+ end
59
+
60
+ def emtpy
61
+ self.size == 0
62
+ end
63
+
64
+ end
65
+
66
+ class Thin::Connection
67
+
68
+ alias :pre_process_orig :pre_process
69
+
70
+ def pre_process
71
+ @request.env['async.connection'] = self
72
+ pre_process_orig
73
+ end
74
+
3
75
  end
@@ -1,6 +1,9 @@
1
1
  class FnordMetric::Gauge
2
2
 
3
3
  include FnordMetric::GaugeCalculations
4
+ include FnordMetric::GaugeModifiers
5
+ include FnordMetric::GaugeValidations
6
+ include FnordMetric::GaugeRendering
4
7
 
5
8
  def initialize(opts)
6
9
  opts.fetch(:key) && opts.fetch(:key_prefix)
@@ -8,11 +11,15 @@ class FnordMetric::Gauge
8
11
  end
9
12
 
10
13
  def tick
11
- (@opts[:tick] || 3600).to_i
14
+ (@opts[:tick] || @opts[:resolution] || 3600).to_i
12
15
  end
13
16
 
14
- def tick_at(time)
15
- (time/tick.to_f).floor*tick
17
+ def retention
18
+ tick * 10 # FIXPAUL!
19
+ end
20
+
21
+ def tick_at(time, _tick=tick)
22
+ (time/_tick.to_f).floor*_tick
16
23
  end
17
24
 
18
25
  def name
@@ -22,6 +29,14 @@ class FnordMetric::Gauge
22
29
  def title
23
30
  @opts[:title] || name
24
31
  end
32
+
33
+ def group
34
+ @opts[:group] || "Gauges"
35
+ end
36
+
37
+ def key_nouns
38
+ @opts[:key_nouns] || ["Key", "Keys"]
39
+ end
25
40
 
26
41
  def key(_append=nil)
27
42
  [@opts[:key_prefix], "gauge", name, tick, _append].flatten.compact.join("-")
@@ -31,6 +46,14 @@ class FnordMetric::Gauge
31
46
  key([(progressive? ? :progressive : tick_at(_time).to_s), _append])
32
47
  end
33
48
 
49
+ def tick_keys(_range, _append=nil)
50
+ ticks_in(_range).map{ |_t| tick_key(_t, _append) }
51
+ end
52
+
53
+ def retention_key(_time, _append=nil)
54
+ key([tick_at(_time, retention).to_s, _append])
55
+ end
56
+
34
57
  def two_dimensional?
35
58
  !@opts[:three_dimensional]
36
59
  end
@@ -51,16 +74,20 @@ class FnordMetric::Gauge
51
74
  !!@opts[:average]
52
75
  end
53
76
 
54
- def add_redis(_redis)
55
- @opts[:redis] = _redis
77
+ def has_series?
78
+ false
56
79
  end
57
80
 
58
- def ticks_in(r)
59
- (((r.last-r.first)/tick.to_f).ceil+1).times.map{ |n| tick_at(r.first + tick*(n-1)) }
81
+ def redis
82
+ @redis ||= EM::Hiredis.connect(FnordMetric.options[:redis_url]) # FIXPAUL
60
83
  end
61
84
 
62
- def values_in(range)
63
- values_at(ticks_in(range))
85
+ def sync_redis
86
+ @sync_redis ||= FnordMetric.mk_redis # FIXPAUL
64
87
  end
65
88
 
66
- end
89
+ def error!(msg)
90
+ FnordMetric.error(msg)
91
+ end
92
+
93
+ end
@@ -1,20 +1,35 @@
1
1
  module FnordMetric::GaugeCalculations
2
2
 
3
3
  @@avg_per_session_proc = proc{ |_v, _t|
4
- (_v.to_f / (redis.get(tick_key(_t, :"sessions-count"))||1).to_i)
4
+ (_v.to_f / (sync_redis.get(tick_key(_t, :"sessions-count"))||1).to_i)
5
5
  }
6
6
 
7
7
  @@count_per_session_proc = proc{ |_v, _t|
8
- (redis.get(tick_key(_t, :"sessions-count"))||0).to_i
8
+ (sync_redis.get(tick_key(_t, :"sessions-count"))||0).to_i
9
9
  }
10
10
 
11
11
  @@avg_per_count_proc = proc{ |_v, _t|
12
- (_v.to_f / (redis.get(tick_key(_t, :"value-count"))||1).to_i)
12
+ (_v.to_f / (sync_redis.get(tick_key(_t, :"value-count"))||1).to_i)
13
13
  }
14
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
+
15
25
  def value_at(time, opts={}, &block)
16
26
  _t = tick_at(time)
17
- _v = redis.hget(key, _t)
27
+
28
+ _v = if respond_to?(:_value_at)
29
+ _value_at(key, _t)
30
+ else
31
+ sync_redis.hget(key, _t)
32
+ end
18
33
 
19
34
  calculate_value(_v, _t, opts, block)
20
35
  end
@@ -22,17 +37,17 @@ module FnordMetric::GaugeCalculations
22
37
  def values_at(times, opts={}, &block)
23
38
  times = times.map{ |_t| tick_at(_t) }
24
39
  Hash.new.tap do |ret|
25
- redis.hmget(key, *times).each_with_index do |_v, _n|
40
+ if respond_to?(:_values_at)
41
+ _values_at(times, opts={}, &block)
42
+ else
43
+ sync_redis.hmget(key, *times)
44
+ end.each_with_index do |_v, _n|
26
45
  _t = times[_n]
27
46
  ret[_t] = calculate_value(_v, _t, opts, block)
28
47
  end
29
48
  end
30
49
  end
31
50
 
32
- def values_in(range, opts={}, &block)
33
- values_at((tick_at(range.first)..range.last).step(tick))
34
- end
35
-
36
51
  def calculate_value(_v, _t, opts, block)
37
52
  block = @@avg_per_count_proc if average?
38
53
  #block = @@count_per_session_proc if unique?
@@ -47,9 +62,9 @@ module FnordMetric::GaugeCalculations
47
62
 
48
63
  def field_values_at(time, opts={}, &block)
49
64
  opts[:max_fields] ||= 50
50
- field_values = redis.zrevrange(
51
- tick_key(time),
52
- 0, opts[:max_fields]-1,
65
+ field_values = sync_redis.zrevrange(
66
+ tick_key(time),
67
+ 0, opts[:max_fields]-1,
53
68
  :withscores => true
54
69
  )
55
70
 
@@ -65,11 +80,18 @@ module FnordMetric::GaugeCalculations
65
80
  end
66
81
 
67
82
  def field_values_total(time)
68
- (redis.get(tick_key(time, :count))||0).to_i
83
+ (sync_redis.get(tick_key(time, :count))||0).to_i
69
84
  end
70
85
 
71
- def redis
72
- @opts[:redis]
86
+ def fraction_values_in(range, _append=nil)
87
+ Hash.new{ |h,k| h[k] = [0,0] }.tap do |vals|
88
+ ticks_in(range, retention).each do |_tick|
89
+ sync_redis.hgetall(retention_key(_tick, _append)).each do |k, v|
90
+ kx = k.split("-")
91
+ vals[kx.first.to_i][kx.last == "denominator" ? 1 : 0] += v.to_f
92
+ end
93
+ end
94
+ end
73
95
  end
74
96
 
75
- end
97
+ end
@@ -72,5 +72,72 @@ module FnordMetric::GaugeModifiers
72
72
  @redis.zadd(gauge.tick_key(time), value, field_name)
73
73
  end
74
74
 
75
+ def incr_numerator(ctx, series_name=:default, value=1, prog=false)
76
+ incr_fraction(ctx, series_name, :numerator, value, prog)
77
+ end
78
+
79
+ def incr_denominator(ctx, series_name=:default, value=1, prog=false)
80
+ incr_fraction(ctx, series_name, :denominator, value, prog)
81
+ end
82
+
83
+ def incr_fraction(ctx, series_name, part, value, prog)
84
+ return unless series_name = assure_series_exists!(series_name)
85
+ assure_has_series!
86
+
87
+ at = ctx.send(:time)
88
+ value = parse_numeric(value)
89
+
90
+ if prog
91
+ raise "FIXPAUL: not yet implemented: progressive fraction gauges"
92
+ end
93
+
94
+ ctx.redis_exec(:hincrby, retention_key(at, series_name), "#{tick_at(at)}-#{part}", value).callback do
95
+ ctx.redis_exec :expire, retention_key(at, series_name)
96
+ end
97
+ end
98
+
99
+ def assure_has_series!
100
+ return true if has_series?
101
+ error! "error: #{caller[0].split(" ")[-1]} can only be used with series gauges"
102
+ end
103
+
104
+ def assure_two_dimensional!(gauge)
105
+ return true if gauge.two_dimensional?
106
+ error! "error: #{caller[0].split(" ")[-1]} can only be used with 2-dimensional gauges"
107
+ end
108
+
109
+ def assure_three_dimensional!(gauge)
110
+ return true unless gauge.two_dimensional?
111
+ error! "error: #{caller[0].split(" ")[-1]} can only be used with 3-dimensional gauges"
112
+ end
113
+
114
+ def assure_non_progressive!(gauge)
115
+ return true unless gauge.progressive?
116
+ error! "error: #{caller[0].split(" ")[-1]} can only be used with non-progressive gauges"
117
+ end
118
+
119
+ def assure_series_exists!(series_name)
120
+ if series_name == :default && @opts[:series].size > 1
121
+ error! "gauge '#{name}': don't know which series to increment"
122
+ elsif series_name == :default
123
+ return @opts[:series].first
124
+ elsif !series_name.respond_to?(:to_sym) || !@opts[:series].include?(series_name.to_sym)
125
+ error! "gauge '#{name}': unknown series: #{series_name}"
126
+ else
127
+ return series_name
128
+ end
129
+ end
130
+
131
+ def parse_numeric(val)
132
+ if val.is_a?(Numeric)
133
+ return val
134
+ elsif val.is_a?(String) && val.match(/[0-9]+/)
135
+ val.to_i
136
+ elsif val.is_a?(String) && val.match(/[0-9]+(\.|,)[0-9]+/)
137
+ val.to_f
138
+ else
139
+ error! "gauge '#{name}': incr called with non-numerical value: #{val}"
140
+ end
141
+ end
75
142
 
76
143
  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,85 @@
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
+
9
+ #@num_min =
10
+ #@num_max =
11
+
12
+ @histogram = FnordMetric::Histogram.new
13
+ @values = []
14
+
15
+ @samples = 0
16
+
17
+ @mmm_timeseries = Hash.new do |h,k|
18
+ h[k] = { :min => nil, :max => 0, :avg => [] }
19
+ end
20
+
21
+ ticks_in(@interval, tick, 1).each do |_tick|
22
+ tkey = tick_key(_tick, :histogram)
23
+
24
+ sync_redis.hgetall(tkey).each do |_val, _count|
25
+ _count = _count.to_f
26
+ _val = _val.to_f * @opts[:value_scale]
27
+
28
+ @samples += _count
29
+
30
+ @histogram[_val] += _count
31
+ @values += [_val] * _count
32
+
33
+ if !@mmm_timeseries[_tick][:min] || (_val < @mmm_timeseries[_tick][:min])
34
+ @mmm_timeseries[_tick][:min] = _val
35
+ end
36
+
37
+ if _val > @mmm_timeseries[_tick][:max]
38
+ @mmm_timeseries[_tick][:max] = _val
39
+ end
40
+
41
+ @mmm_timeseries[_tick][:avg] += [_val] * _count
42
+ end
43
+ end
44
+
45
+ if @opts[:value_ranges]
46
+ @histogram_mode = @opts[:value_ranges]
47
+ else
48
+ @histogram_mode = [23, @histogram.max || 1].min
49
+ end
50
+
51
+ @mmm_timeseries_arr = @mmm_timeseries.to_a
52
+ .map{ |k,v| [k, Hash[v.map{ |vk, vv| [vk, (vv.is_a?(Numeric) || vv.is_a?(Array)) ? vv : 0 ] }]] }
53
+ .sort{ |a,b| a.first.to_i <=> b.first.to_i}
54
+
55
+ render_page(:distribution_gauge)
56
+ end
57
+
58
+ def execute(cmd, context, *args)
59
+ return observe(context, args.first) if cmd == :observe
60
+ FnordMetric.error("gauge '#{name}': unknown command: #{cmd}")
61
+ end
62
+
63
+ def renderable?
64
+ true
65
+ end
66
+
67
+ private
68
+
69
+ def observe(ctx, value)
70
+ at = ctx.send(:time)
71
+
72
+ if value.is_a?(String) && value.match(/[0-9]+/)
73
+ value = value.to_i
74
+ elsif value.is_a?(String) && value.match(/[0-9]+(\.|,)[0-9]+/)
75
+ value = value.to_f
76
+ end
77
+
78
+ unless value.is_a?(Float) || value.is_a?(Fixnum)
79
+ return FnordMetric.error("gauge '#{name}': observe called with non-numerical value: #{value}")
80
+ end
81
+
82
+ ctx.redis_exec :hincrby, tick_key(at, :histogram), value.round(2), 1
83
+ end
84
+
85
+ end