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
@@ -0,0 +1,143 @@
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
+
17
+ if @calculate == :average
18
+ @calculate_proc = lambda{ |c,d| d > 0 ? (c/d.to_f).round(2) : 0 }
19
+ else
20
+ @calculate_proc = lambda{ |c,d| c }
21
+ end
22
+ end
23
+
24
+ def render(namespace, event)
25
+ @interval = parse_interval(event["interval"])
26
+ colors = FnordMetric::COLORS.dup
27
+
28
+ @series = Hash.new
29
+ @zooms = FnordMetric::TICKS[tick, @interval.size]
30
+
31
+ @total = 0
32
+
33
+ @opts[:series].each do |series|
34
+ ts = FnordMetric::Timeseries.new
35
+
36
+ fraction_values_in(@interval, series).each do |time, frac|
37
+ @total += frac.first # FIXPAUL
38
+ ts.incr_fraction(time, *frac)
39
+ end
40
+
41
+ @series[series] = {
42
+ :color => colors.unshift(colors.pop).first,
43
+ :data => Hash[@zooms.map{ |int| [int, ts.timeseries(@interval, int) ] }],
44
+ :timeseries => ts
45
+ }
46
+ end
47
+
48
+ render_page(:timeseries_gauge)
49
+ end
50
+
51
+ def execute(cmd, context, *args)
52
+ return incr(context, *args) if cmd == :incr
53
+ return incr_numerator(context, *args) if cmd == :incr_numerator
54
+ return incr_denominator(context, *args) if cmd == :incr_denominator
55
+
56
+ FnordMetric.error("gauge '#{name}': unknown command: #{cmd}")
57
+ end
58
+
59
+ def renderable?
60
+ true
61
+ end
62
+
63
+ def has_series?
64
+ true
65
+ end
66
+
67
+ private
68
+
69
+ def incr(ctx, series_name = :default, value = 1)
70
+ if @calculate == :average
71
+ incr_numerator(ctx, series_name, value)
72
+ incr_denominator(ctx, series_name, 1)
73
+ elsif @calculate == :sum
74
+ incr_numerator(ctx, series_name, value)
75
+ elsif @calculate == :progressive_sum
76
+ incr_numerator(ctx, series_name, value, true)
77
+ end
78
+ end
79
+
80
+ end
81
+
82
+ # class FnordMetric::NumericGauge < FnordMetric::MultiGauge
83
+
84
+ # def initialize(opts)
85
+ # super(opts)
86
+
87
+ # validate_series!
88
+ # validate_ticks!
89
+
90
+ # timeline_widget(
91
+ # :tab => "Overview",
92
+ # :title => "Total #{key_nouns.last}",
93
+ # :ticks => @opts[:ticks],
94
+ # :series => @opts[:series],
95
+ # :series_titles => Hash[@opts[:series].map{|s| [s, s]}],
96
+ # :autoupdate => 10,
97
+ # :height => 350
98
+ # ).on(:values_at) do |_series, _ticks, _tick|
99
+ # series_count_metrics[_series][_tick].values_at(_ticks)
100
+ # end
101
+
102
+ # numbers_widget(
103
+ # :tab => "Overview",
104
+ # :title => "Total #{key_nouns.last}",
105
+ # :series => @opts[:series],
106
+ # :series_titles => Hash[@opts[:series].map{|s| [s, s]}],
107
+ # :autoupdate => 2
108
+ # ).on(:values_for) do |_series|
109
+ # render_series_numbers(_series.to_sym)
110
+ # end
111
+
112
+ # end
113
+
114
+ # def react(event)
115
+ # if event["_class"] == "incrby" || event["_class"] == "incr"
116
+ # series = event["series"]
117
+ # series ||= @opts[:series][0] if @opts[:series].size == 1
118
+ # incr_series(series.to_sym, event["_time"], event["value"])
119
+ # end
120
+ # end
121
+
122
+
123
+
124
+
125
+
126
+ # def render_series_numbers(series)
127
+ # _t = Time.now.to_i
128
+
129
+ # {}.tap do |out|
130
+ # @opts[:ticks].each do |tick|
131
+ # out["#{tick}-now"] = {
132
+ # :value => series_count_metrics[series][tick].value_at(_t),
133
+ # :desc => "$formatTimeRangePre(#{tick}, 0)"
134
+ # }
135
+ # out["#{tick}-last"] = {
136
+ # :value => series_count_metrics[series][tick].value_at(_t-tick),
137
+ # :desc => "$formatTimeRangePre(#{tick}, -1)"
138
+ # }
139
+ # end
140
+ # end
141
+ # end
142
+
143
+ # 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
@@ -0,0 +1,57 @@
1
+ class FnordMetric::Histogram < Hash
2
+
3
+ def initialize
4
+ super{ |h,k| h[k]=0 }
5
+ end
6
+
7
+ def [](key)
8
+ super(key.to_f)
9
+ end
10
+
11
+ def []=(key, val)
12
+ super(key.to_f, val)
13
+ end
14
+
15
+ def min
16
+ keys.sort.first.to_i
17
+ end
18
+
19
+ def max
20
+ keys.sort.last.to_i
21
+ end
22
+
23
+ def histogram(windows)
24
+ windows = histogram_windows(windows) unless windows.is_a?(Array)
25
+ Hash[windows.map{ |w| [w,0] }].tap do |histo|
26
+ self.each do |k,v|
27
+ histo.detect do |win, wval|
28
+ histo[win] += v if win.include?(k)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def json_histogram(windows)
35
+ histogram(windows).to_a.sort do |a, b|
36
+ a[0].first <=> b[0].first
37
+ end.map do |r, v|
38
+ [r.size == 1.0 ? r.last.to_s :
39
+ "#{r.first.round(1).to_s}-#{r.last.round(1).to_s}", v.to_i]
40
+ end.to_json
41
+ end
42
+
43
+ private
44
+
45
+ def histogram_windows(windows)
46
+ _min = min
47
+ _max = max
48
+
49
+ return [(0..1)] if (_max-_min == 0)
50
+
51
+ windows.times
52
+ .inject((_min.._max)
53
+ .step(((_max-_min)/windows.to_f)).to_a << _max){ |a,n|
54
+ a[n]=(a[n]..a[n+1]); a }.take(windows)
55
+ end
56
+
57
+ end
@@ -1,57 +1,63 @@
1
1
  class FnordMetric::Logger
2
2
 
3
- def self.start(logfile_path)
4
- require 'json'
5
- event_ids = Queue.new
3
+ def self.import(logfile_path)
4
+ expire = FnordMetric.options[:event_queue_ttl]
5
+ redis = Redis.new
6
+
7
+ @opts[:channels] ||= []
8
+ @opts[:channels] = @opts[:channels].map(&:to_s)
9
+
10
+ dump_file = File.open(logfile_path, 'r')
11
+ num_lines = %x{wc -l #{logfile_path}}.to_i
12
+ puts "importing #{num_lines} events..."
13
+
14
+ dump_file.each_with_log(num_lines) do |line, ind|
15
+ (8**64).to_s(36).tap do |uuid|
16
+ redis.set "fnordmetric-event-#{uuid}", line
17
+ redis.lpush "fnordmetric-queue" , uuid
18
+ redis.expire "fnordmetric-event-#{uuid}", expire
19
+ end
20
+ end
21
+ end
22
+
23
+ def initialize(opts)
24
+ @opts = opts
25
+ opts.fetch(:file)
26
+
27
+ FnordMetric.register(self)
28
+ end
29
+
30
+ def initialized
31
+ logfile_path = @opts[:file]
32
+
33
+ events = Queue.new
6
34
  dump_file = File.open(logfile_path, 'a+')
7
35
 
8
36
  fetcher = Thread.new do
9
- redis = Redis.new
10
37
  loop do
11
- event_id = event_ids.pop
12
- event_data = redis.get("fnordmetric-event-#{event_id}")
13
- event_hash = JSON.parse(event_data) rescue next
38
+ event = events.pop
14
39
 
15
- event_hash.merge!(:_time => Time.now.to_i)
16
-
17
- dump_file.write(event_hash.to_json+"\n")
40
+ dump_file.write(event.to_json+"\n")
18
41
  dump_file.flush
19
-
20
- print "\033[1;34m"
21
- print event_hash.inspect
22
- print "\033[0m\n"
23
42
  end
24
43
  end
25
44
 
26
45
  listener = Thread.new do
27
- redis = Redis.new
28
- redis.subscribe("fnordmetric-announce") do |on|
29
- on.message do |channel, event_id|
30
- event_ids << event_id
31
- end
46
+ backend = FnordMetric.backend
47
+ backend.subscribe do |event|
48
+ events << event if log_channel?(event["_channel"])
32
49
  end
33
50
  end
34
51
 
35
- fetcher.join
52
+ FnordMetric.log "logging to #{logfile_path}"
36
53
  end
37
54
 
38
- def self.import(logfile_path)
39
- redis = Redis.new
40
- dump_file = File.open(logfile_path, 'r')
41
55
 
42
- puts "reading #{logfile_path}..."
43
- dump_lines = dump_file.read.split("\n")
44
-
45
- puts "importing #{dump_lines.length} events..."
46
- pre_uuid = rand(999999999999999999999)
47
- log_every = (dump_lines.length / 150)
48
- dump_lines.each_with_index do |line,n|
49
- puts "#{n}/#{dump_lines.length} (#{((n/dump_lines.length.to_f)*100).to_i}%)" if n%log_every==0
50
- my_uuid = "#{pre_uuid}-#{n}"
51
- redis.set("fnordmetric-event-#{my_uuid}", line)
52
- redis.lpush("fnordmetric-queue", my_uuid)
53
- redis.expire("fnordmetric-event-#{my_uuid}", 3600*12)
54
- end
56
+ private
57
+
58
+ def log_channel?(channel)
59
+ return !!@opts[:channels] if !channel
60
+ @opts[:channels].include?(channel.to_s)
55
61
  end
56
62
 
57
63
  end
@@ -1,38 +1,43 @@
1
1
  class FnordMetric::Namespace
2
2
 
3
- attr_reader :handlers, :gauges, :opts, :key, :dashboards
3
+ attr_reader :handlers, :gauges, :opts, :key, :dashboards, :flags
4
4
 
5
- @@opts = [:event, :gauge, :widget, :set_title, :active_users_available]
5
+ @@opts = [:event, :gauge, :widget, :set_title, :hide_active_users, :hide_overview]
6
+ @@multi_gauges = [:timeseries_gauge, :toplist_gauge, :distribution_gauge]
6
7
 
7
- def initialize(key, opts)
8
+ def initialize(key, opts)
8
9
  @gauges = Hash.new
9
10
  @dashboards = Hash.new
10
11
  @handlers = Hash.new
12
+ @flags = Hash.new
11
13
  @title = key
12
14
  @active_users_available = true
13
15
  @opts = opts
14
- @key = key
16
+ @key = key
15
17
  end
16
18
 
17
19
  def ready!(redis)
18
20
  @redis = redis
19
- @gauges.map{ |k,g| g.add_redis(@redis) }
20
21
  self
21
22
  end
22
23
 
23
- def announce(event)
24
+ def announce(event)
24
25
  announce_to_timeline(event)
25
26
  announce_to_typelist(event)
26
-
27
+
27
28
  if event[:_session]
28
29
  event[:_session_key] = announce_to_session(event).session_key
29
30
  end
30
31
 
31
- [
32
+ res = [
32
33
  @handlers[event[:_type].to_s],
33
34
  @handlers["*"]
34
- ].flatten.compact.each do |context|
35
- context.call(event, @redis)
35
+ ].flatten.compact.each do |context|
36
+ context.call(event, @redis)
37
+ end.size
38
+
39
+ if res == 0
40
+ FnordMetric.error("no handler for event-type: #{event[:_type]}")
36
41
  end
37
42
 
38
43
  self
@@ -40,11 +45,11 @@ class FnordMetric::Namespace
40
45
 
41
46
  def announce_to_session(event)
42
47
  FnordMetric::Session.create(@opts.clone.merge(
43
- :namespace_key => @key,
48
+ :namespace_key => @key,
44
49
  :namespace_prefix => key_prefix,
45
50
  :redis => @redis,
46
51
  :event => event
47
- ))
52
+ ))
48
53
  end
49
54
 
50
55
  def announce_to_timeline(event)
@@ -65,15 +70,15 @@ class FnordMetric::Namespace
65
70
  def token
66
71
  @key
67
72
  end
68
-
73
+
69
74
  def title
70
75
  @title
71
76
  end
72
-
77
+
73
78
  def active_users_available
74
79
  @active_users_available
75
80
  end
76
-
81
+
77
82
  def dashboards(name=nil)
78
83
  return @dashboards unless name
79
84
  dash = FnordMetric::Dashboard.new(:title => name)
@@ -91,29 +96,41 @@ class FnordMetric::Namespace
91
96
  end
92
97
 
93
98
  def method_missing(m, *args, &block)
99
+ return send(:opt_multigauge, *args.unshift(m), &block) if @@multi_gauges.include?(m)
94
100
  raise "unknown option: #{m}" unless @@opts.include?(m)
95
101
  send(:"opt_#{m}", *args, &block)
96
102
  end
97
103
 
98
- def hide_active_users
99
- @active_users_available = false
104
+ def opt_hide_active_users
105
+ @flags[:hide_active_users] = true
100
106
  end
101
-
102
- def set_title(key)
103
- @title = key
107
+
108
+ def opt_hide_overview
109
+ @flags[:hide_overview] = true
104
110
  end
105
-
111
+
112
+ def opt_set_title(title)
113
+ @title = title
114
+ end
115
+
106
116
  def opt_event(event_type, opts={}, &block)
107
117
  opts.merge!(:redis => @redis, :gauges => @gauges)
108
118
  FnordMetric::Context.new(opts, block).tap do |context|
109
119
  @handlers[event_type.to_s] ||= []
110
120
  @handlers[event_type.to_s] << context
111
- end
121
+ end
112
122
  end
113
123
 
114
124
  def opt_gauge(gauge_key, opts={})
115
125
  opts.merge!(:key => gauge_key, :key_prefix => key_prefix)
116
- @gauges[gauge_key] ||= FnordMetric::Gauge.new(opts)
126
+ klass = "FnordMetric::#{(opts[:type] || "").to_s.camelize}Gauge".constantize
127
+ @gauges[gauge_key] ||= klass.new(opts)
128
+ end
129
+
130
+ def opt_multigauge(gauge_type, gauge_key, opts={})
131
+ opts.merge!(:key => gauge_key, :key_prefix => key_prefix)
132
+ klass = "FnordMetric::#{gauge_type.to_s.camelize}"
133
+ @gauges[gauge_key] ||= klass.constantize.new(opts)
117
134
  end
118
135
 
119
136
  def opt_widget(dashboard, widget)
@@ -135,4 +152,11 @@ class FnordMetric::Namespace
135
152
  )
136
153
  end
137
154
 
155
+ def to_json
156
+ flags.merge(
157
+ :token => token,
158
+ :title => title
159
+ ).to_json
160
+ end
161
+
138
162
  end
@@ -1,21 +1,21 @@
1
1
  class FnordMetric::Session
2
-
2
+
3
3
  attr_accessor :updated_at, :name, :picture
4
4
 
5
5
  @@meta_attributes = %w(name picture)
6
6
 
7
- def self.create(opts)
7
+ def self.create(opts)
8
8
  redis = opts.fetch(:redis)
9
- event = opts[:event]
9
+ event = opts[:event]
10
10
 
11
11
  hash = Digest::MD5.hexdigest(event[:_session])
12
12
  set_key = "#{opts[:namespace_prefix]}-session"
13
13
 
14
14
  self.new(hash).tap do |session|
15
- session.add_redis(redis, set_key)
16
- session.add_event(event)
15
+ session.add_redis(redis, set_key)
16
+ session.add_event(event)
17
17
  session.expire(opts[:session_data_ttl])
18
- end
18
+ end
19
19
  end
20
20
 
21
21
  def self.find(session_key, opts)
@@ -1,40 +1,20 @@
1
- require 'rake'
2
- require 'redis'
1
+ _opts = FnordMetric.options
3
2
 
4
- task :run do
5
- FnordMetric.run
3
+ if _opts[:web_interface]
4
+ FnordMetric::Web.new(
5
+ :host => _opts[:web_interface][0],
6
+ :port => _opts[:web_interface][1]
7
+ )
6
8
  end
7
9
 
8
- task :worker do
9
- FnordMetric.server_configuration = {
10
- :web_interface => false,
11
- :inbound_stream => false,
12
- :start_worker => true
13
- }
14
- FnordMetric.run
10
+ if _opts[:inbound_stream]
11
+ FnordMetric::Acceptor.new(
12
+ :protocol => _opts[:inbound_protocol],
13
+ :host => _opts[:inbound_stream][0],
14
+ :port => _opts[:inbound_stream][1]
15
+ )
15
16
  end
16
17
 
17
- task :log do
18
- FnordMetric::Logger.start(dump_file_path)
19
- end
20
-
21
- task :import do
22
- FnordMetric::Logger.import(dump_file_path)
23
- end
24
-
25
- task :help do
26
- puts "usage: #{$0} {run|worker|log|import} [DUMP_FILE=fm_dump.json]"
27
- end
28
-
29
- task :default => :help
30
-
31
- def dump_file_path
32
- if ENV["DUMP_FILE"].blank?
33
- Rake::Task[:help].execute; exit!
34
- else
35
- ::File.expand_path(ENV["DUMP_FILE"], ::File.dirname($0))
36
- end
37
- end
38
-
39
- Rake.application.init('fnordmetric')
40
- Rake.application.top_level
18
+ if _opts[:start_worker]
19
+ FnordMetric::Worker.new()
20
+ end
@@ -0,0 +1,79 @@
1
+ class FnordMetric::Timeseries
2
+
3
+ def initialize(timeline = {})
4
+ @timeline = Hash.new{ |h,k| h[k] = [0,nil] }
5
+ @timeline.merge!(timeline)
6
+ end
7
+
8
+ def incr_fraction(time, numerator, denominator)
9
+ incr_numerator(time, numerator) if numerator
10
+ incr_denominator(time, denominator) if denominator
11
+ end
12
+
13
+ def incr_numerator(time, value)
14
+ @timeline[time.to_i][0] += value
15
+ end
16
+
17
+ def incr_denominator(time, value)
18
+ @timeline[time.to_i][-1] ||= 0
19
+ @timeline[time.to_i][-1] += value
20
+ end
21
+
22
+ def timeseries(range, window, &block)
23
+ res = Hash.new{ |h,k| h[k] = [0,0] }
24
+
25
+ (((range.size)/window.to_f).ceil+1).times.map do |n|
26
+ res[((range.first+window*(n-1))/window.to_f).floor*window] = [0,0]
27
+ end
28
+
29
+ @timeline.each do |time, vals|
30
+ next unless range.include?(time)
31
+ wtime = (time/window.to_f).floor * window
32
+ if block
33
+ res[wtime] = block.call(*vals)
34
+ else
35
+ res[wtime][0] += vals[0]
36
+ res[wtime][1] += vals[1]
37
+ end
38
+ end
39
+
40
+ FnordMetric::Timeseries.new(res)
41
+ end
42
+
43
+ def sum(range = (ticks.first..ticks.last))
44
+ @timeline
45
+ .inject(0){ |s,(t,v)| s + (range.include?(t) ? value_at(t) : 0) }
46
+ end
47
+
48
+ def trend(range = (ticks.first..ticks.last))
49
+ range ||= (ticks.first..ticks.last)
50
+
51
+ rvals = @timeline.to_a
52
+ .select{ |t,v| range.include?(t) }
53
+ .sort{ |a,b| a.first <=> b.first }
54
+ .map{ |t,v| value_at(t) }
55
+
56
+ return 0 if rvals.size == 0
57
+ (rvals.last - rvals.first).to_f / rvals.first
58
+ end
59
+
60
+ def ticks
61
+ @timeline.keys.sort
62
+ end
63
+
64
+ def value_at(time)
65
+ if @timeline[time][1].to_i > 0
66
+ @timeline[time][0] / @timeline[time][1].to_f
67
+ else
68
+ @timeline[time][0]
69
+ end
70
+ end
71
+
72
+ def to_json(&block)
73
+ @timeline.to_a
74
+ .sort{ |a,b| a[0] <=> b[0] }
75
+ .map { |t,v| { :x => t, :y => block.call(*v), :v0 => v[0], :v1 => v[1] } }
76
+ .to_json
77
+ end
78
+
79
+ end