fnordmetric 1.0.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/Rakefile +1 -1
  2. data/fnordmetric.gemspec +1 -0
  3. data/lib/fnordmetric.rb +6 -13
  4. data/lib/fnordmetric/acceptors/acceptor.rb +20 -7
  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 +1 -0
  9. data/lib/fnordmetric/acceptors/udp_acceptor.rb +2 -1
  10. data/lib/fnordmetric/context.rb +21 -9
  11. data/lib/fnordmetric/defaults.rb +16 -3
  12. data/lib/fnordmetric/gauge.rb +8 -3
  13. data/lib/fnordmetric/gauge_calculations.rb +8 -2
  14. data/lib/fnordmetric/gauge_modifiers.rb +5 -4
  15. data/lib/fnordmetric/gauges/server_health_gauge.rb +13 -0
  16. data/lib/fnordmetric/namespace.rb +53 -17
  17. data/lib/fnordmetric/util.rb +25 -0
  18. data/lib/fnordmetric/version.rb +1 -1
  19. data/lib/fnordmetric/web/app.rb +7 -66
  20. data/lib/fnordmetric/web/reactor.rb +32 -2
  21. data/lib/fnordmetric/web/websocket.rb +1 -1
  22. data/lib/fnordmetric/widgets/bars_widget.rb +1 -1
  23. data/lib/fnordmetric/widgets/numbers_widget.rb +28 -4
  24. data/lib/fnordmetric/widgets/timeseries_widget.rb +19 -9
  25. data/lib/fnordmetric/widgets/toplist_widget.rb +8 -4
  26. data/lib/fnordmetric/worker.rb +5 -1
  27. data/lib/fnordmetric/zero_config_gauge.rb +138 -0
  28. data/spec/context_spec.rb +4 -4
  29. data/spec/event_spec.rb +11 -11
  30. data/spec/gauge_modifiers_spec.rb +135 -29
  31. data/spec/gauge_spec.rb +7 -2
  32. data/spec/namespace_spec.rb +8 -19
  33. data/spec/util_spec.rb +46 -0
  34. data/web/.gitignore +4 -0
  35. data/web/build.sh +34 -0
  36. data/web/{fnordmetric.css → css/fnordmetric.core.css} +121 -58
  37. data/web/haml/app.haml +4 -22
  38. data/web/{loader.gif → img/loader.gif} +0 -0
  39. data/web/js/fnordmetric.bars_widget.js +3 -3
  40. data/web/js/fnordmetric.dashboard_view.js +1 -1
  41. data/web/js/fnordmetric.gauge_explorer.js +173 -0
  42. data/web/js/fnordmetric.js +93 -33
  43. data/web/js/fnordmetric.numbers_widget.js +15 -14
  44. data/web/js/fnordmetric.session_view.js +0 -1
  45. data/web/js/fnordmetric.timeline_widget.js +3 -3
  46. data/web/js/fnordmetric.timeseries_widget.js +46 -29
  47. data/web/js/fnordmetric.toplist_widget.js +23 -16
  48. data/web/js/fnordmetric.util.js +12 -8
  49. data/web/vendor/font-awesome/css/font-awesome-ie7.min.css +22 -0
  50. data/web/vendor/font-awesome/css/font-awesome.css +522 -221
  51. data/web/vendor/font-awesome/css/font-awesome.min.css +33 -0
  52. data/web/vendor/font-awesome/font/FontAwesome.otf +0 -0
  53. data/web/vendor/font-awesome/font/fontawesome-webfont.eot +0 -0
  54. data/web/vendor/font-awesome/font/fontawesome-webfont.svg +278 -169
  55. data/web/vendor/font-awesome/font/fontawesome-webfont.ttf +0 -0
  56. data/web/vendor/font-awesome/font/fontawesome-webfont.woff +0 -0
  57. data/web/vendor/jquery-ui.min.js +6 -413
  58. data/web/vendor/jquery.combobox.js +129 -0
  59. metadata +55 -48
  60. data/doc/V1.0-ROADMAP +0 -97
  61. data/doc/full_example.rb +0 -224
  62. data/doc/legacy_example.rb +0 -640
  63. data/doc/minimal_example.rb +0 -26
  64. data/doc/preview1.png +0 -0
  65. data/doc/preview2.png +0 -0
  66. data/doc/preview3.png +0 -0
  67. data/readme.md +0 -365
  68. data/web/vendor/d3.v2.js +0 -9382
  69. data/web/vendor/font-awesome/font/fontawesome-webfont.svgz +0 -0
  70. data/web/vendor/rickshaw.css +0 -286
  71. data/web/vendor/rickshaw.fnordmetric.js +0 -2700
@@ -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
+ # }
@@ -1,39 +1,54 @@
1
1
  class FnordMetric::Namespace
2
-
2
+
3
3
  attr_reader :handlers, :gauges, :opts, :key, :dashboards, :flags
4
4
 
5
- @@opts = [:event, :gauge, :widget, :set_title, :hide_active_users, :hide_overview, :dashboard]
5
+ @@opts = [:event, :gauge, :widget, :set_title, :hide_active_users, :hide_overview,
6
+ :hide_gauge_explorer, :dashboard]
7
+
6
8
  @@multi_gauges = [:timeseries_gauge, :toplist_gauge, :distribution_gauge]
7
9
 
8
10
  def initialize(key, opts)
9
11
  @gauges = Hash.new
10
12
  @dashboards = Hash.new
11
- @handlers = Hash.new
12
- @flags = Hash.new
13
+ @handlers = Hash.new.with_indifferent_access
13
14
  @title = key
14
- @active_users_available = true
15
15
  @opts = opts
16
16
  @key = key
17
+
18
+ @flags = {
19
+ :hide_active_users => (FnordMetric.options[:enable_active_users] == false),
20
+ :hide_gauge_explorer => (FnordMetric.options[:enable_gauge_explorer] == false)
21
+ }
17
22
  end
18
23
 
19
- def ready!(redis)
24
+ def ready!(redis, sync_redis = nil)
20
25
  @redis = redis
26
+ @sync_redis = sync_redis
27
+ load_gauges
21
28
  self
22
29
  end
23
30
 
24
31
  def announce(event)
25
- announce_to_timeline(event)
26
- announce_to_typelist(event)
32
+ if !@flags[:hide_active_users]
33
+ announce_to_timeline(event)
34
+ announce_to_typelist(event)
35
+ end
27
36
 
28
37
  if event[:_session]
29
38
  event[:_session_key] = announce_to_session(event).session_key
30
39
  end
31
40
 
41
+ if FnordMetric::ZeroConfigGauge::TYPES.include?(event[:_type].to_sym)
42
+ ctx = FnordMetric::Context.new(opts, FnordMetric::ZeroConfigGauge::Handler)
43
+ ctx.call(event, @redis, self)
44
+ return self
45
+ end
46
+
32
47
  res = [
33
48
  @handlers[event[:_type].to_s],
34
49
  @handlers["*"]
35
50
  ].flatten.compact.each do |context|
36
- context.call(event, @redis)
51
+ context.call(event, @redis, self)
37
52
  end.size
38
53
 
39
54
  if res == 0
@@ -75,10 +90,6 @@ class FnordMetric::Namespace
75
90
  @title
76
91
  end
77
92
 
78
- def active_users_available
79
- @active_users_available
80
- end
81
-
82
93
  def dashboards(name=nil, opts = {})
83
94
  return @dashboards unless name
84
95
  dash = FnordMetric::Dashboard.new(opts.merge(:title => name))
@@ -105,6 +116,10 @@ class FnordMetric::Namespace
105
116
  @flags[:hide_active_users] = true
106
117
  end
107
118
 
119
+ def opt_hide_gauge_explorer
120
+ @flags[:hide_gauge_explorer] = true
121
+ end
122
+
108
123
  def opt_hide_overview
109
124
  @flags[:hide_overview] = true
110
125
  end
@@ -113,8 +128,7 @@ class FnordMetric::Namespace
113
128
  @title = title
114
129
  end
115
130
 
116
- def opt_event(event_type, opts={}, &block)
117
- opts.merge!(:redis => @redis, :gauges => @gauges)
131
+ def opt_event(event_type, opts={}, &block)
118
132
  FnordMetric::Context.new(opts, block).tap do |context|
119
133
  @handlers[event_type.to_s] ||= []
120
134
  @handlers[event_type.to_s] << context
@@ -122,7 +136,9 @@ class FnordMetric::Namespace
122
136
  end
123
137
 
124
138
  def opt_gauge(gauge_key, opts={})
125
- opts.merge!(:key => gauge_key, :key_prefix => key_prefix)
139
+ opts.merge!(:key => gauge_key)
140
+ store_gauge(gauge_key, opts) if opts[:zero_config]
141
+ opts.merge!(:key_prefix => key_prefix)
126
142
  klass = "FnordMetric::#{(opts[:type] || "").to_s.camelize}Gauge".constantize
127
143
  @gauges[gauge_key] ||= klass.new(opts)
128
144
  end
@@ -143,11 +159,31 @@ class FnordMetric::Namespace
143
159
  end
144
160
 
145
161
  def build_widget(opts)
146
- _gauges = [opts[:gauges]].flatten.map{ |g| @gauges.fetch(g) }
162
+ _gauges = [opts[:gauges]].flatten.map do |g|
163
+ @gauges[g] || FnordMetric::ZeroConfigGauge.new(g, self)
164
+ end
147
165
  widget_klass = "FnordMetric::#{opts.fetch(:type).to_s.capitalize}Widget"
148
166
  widget_klass.constantize.new(opts.merge(:gauges => _gauges))
149
167
  end
150
168
 
169
+ def store_gauge(gauge_key, opts)
170
+ gaugelist_key = key_prefix("zero-config-gauges")
171
+ sync_redis.hset(gaugelist_key, gauge_key, opts.to_json)
172
+ end
173
+
174
+ def load_gauges
175
+ gaugelist_key = key_prefix("zero-config-gauges")
176
+ sync_redis.hgetall(gaugelist_key).each do |gauge_key, gauge_opts|
177
+ gopts = JSON.parse(gauge_opts).symbolize_keys
178
+ gopts.delete(:zero_config)
179
+ opt_gauge(gauge_key.to_sym, gopts)
180
+ end
181
+ end
182
+
183
+ def sync_redis
184
+ @sync_redis || @redis
185
+ end
186
+
151
187
  def extend_opts(opts)
152
188
  opts.merge(
153
189
  :namespace_prefix => key_prefix,
@@ -0,0 +1,25 @@
1
+ class FnordMetric::Util
2
+
3
+ def self.parse_time(str)
4
+ str = str.downcase
5
+
6
+ if (str == "now")
7
+ Time.now.to_i
8
+ elsif str =~ /^([0-9]+(?:\.[0-9]+)?)$/
9
+ $1.to_i
10
+ elsif str =~ /^-([0-9]+(?:\.[0-9]+)?)$/
11
+ Time.now.to_i - $1.to_i
12
+ elsif str =~ /^-([0-9]+(?:\.[0-9]+)?)s(ec(ond)?(s?))?$/
13
+ Time.now.to_i - $1.to_f
14
+ elsif str =~ /^-([0-9]+(?:\.[0-9]+)?)m(in(ute)?(s?))?$/
15
+ Time.now.to_i - ($1.to_f * 60)
16
+ elsif str =~ /^-([0-9]+(?:\.[0-9]+)?)h(our(s?))?$/
17
+ Time.now.to_i - ($1.to_f * 3600)
18
+ elsif str =~ /^-([0-9]+(?:\.[0-9]+)?)d(ay(s?))?$/
19
+ Time.now.to_i - ($1.to_i * 86400)
20
+ else
21
+ raise "invalid time specifiation: #{str}"
22
+ end
23
+ end
24
+
25
+ end
@@ -1,3 +1,3 @@
1
1
  module FnordMetric
2
- VERSION = "1.0.1"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -22,6 +22,11 @@ class FnordMetric::App < Sinatra::Base
22
22
  include FnordMetric::AppHelpers
23
23
  end
24
24
 
25
+ %w(fnordmetric-ui.js fnordmetric-ui.css fnordmetric-core.css fnordmetric-core.js).each do |f|
26
+ next if ::File.exists?(::File.expand_path("../../../../web/#{f}", __FILE__))
27
+ raise "error: file 'web/#{f}' does not exist, please run build.sh in web/"
28
+ end
29
+
25
30
  def initialize(opts = {})
26
31
  @opts = FnordMetric.default_options(opts)
27
32
 
@@ -37,10 +42,12 @@ class FnordMetric::App < Sinatra::Base
37
42
 
38
43
  get '/:namespace' do
39
44
  pass unless current_namespace
45
+ current_namespace.ready!(@redis)
40
46
  haml :app
41
47
  end
42
48
 
43
49
  post '/events' do
50
+ params = JSON.parse(request.body.read) unless params
44
51
  halt 400, 'please specify the event_type (_type)' unless params["_type"]
45
52
  track_event((8**32).to_s(36), parse_params(params))
46
53
  end
@@ -52,71 +59,5 @@ class FnordMetric::App < Sinatra::Base
52
59
  dashboard.to_json
53
60
  end
54
61
 
55
-
56
- # get '/favicon.ico' do
57
- # ""
58
- # end
59
-
60
- # get '/:namespace/gauge/:name' do
61
- # gauge = if params[:name].include?("++")
62
- # parts = params[:name].split("++")
63
- # current_namespace.gauges[parts.first.to_sym].fetch_gauge(parts.last, params[:tick].to_i)
64
- # else
65
- # current_namespace.gauges[params[:name].to_sym]
66
- # end
67
-
68
- # if !gauge
69
- # status 404
70
- # return ""
71
- # end
72
-
73
- # data = if gauge.three_dimensional?
74
- # _t = (params[:at] || Time.now).to_i
75
- # { :count => gauge.field_values_total(_t), :values => gauge.field_values_at(_t) }
76
- # elsif params[:at] && params[:at] =~ /^[0-9]+$/
77
- # { (_t = gauge.tick_at(params[:at].to_i)) => gauge.value_at(_t) }
78
- # elsif params[:at] && params[:at] =~ /^([0-9]+)-([0-9]+)$/
79
- # _range = params[:at].split("-").map(&:to_i)
80
- # _values = gauge.values_in(_range.first.._range.last)
81
- # params[:sum] ? { :sum => _values.values.compact.map(&:to_i).sum } : _values
82
- # else
83
- # { (_t = gauge.tick_at(Time.now.to_i-gauge.tick)) => gauge.value_at(_t) }
84
- # end
85
-
86
- # data.to_json
87
- # end
88
-
89
- # get '/:namespace/sessions' do
90
- # sessions = current_namespace.sessions(:all, :limit => 100).map do |session|
91
- # session.fetch_data!
92
- # session.to_json
93
- # end
94
-
95
- # { :sessions => sessions }.to_json
96
- # end
97
-
98
- # get '/:namespace/events' do
99
- # events = if params[:type]
100
- # current_namespace.events(:by_type, :type => params[:type])
101
- # elsif params[:session_key]
102
- # current_namespace.events(:by_session_key, :session_key => params[:session_key])
103
- # else
104
- # find_opts = { :limit => 100 }
105
- # find_opts.merge!(:since => params[:since].to_i+1) if params[:since]
106
- # current_namespace.events(:all, find_opts)
107
- # end
108
-
109
- # { :events => events.map(&:to_json) }.to_json
110
- # end
111
-
112
- # get '/:namespace/event_types' do
113
- # types_key = current_namespace.key_prefix("type-")
114
- # keys = @redis.keys("#{types_key}*").map{ |k| k.gsub(types_key,'') }
115
-
116
- # { :types => keys }.to_json
117
- # end
118
-
119
-
120
-
121
62
  end
122
63
 
@@ -22,11 +22,23 @@ private
22
22
  messages << widget(ns, event) if event["type"] == "widget_request"
23
23
  messages << gauge(ns, event) if event["type"] == "render_request"
24
24
  messages << active_users(ns, event) if event["type"] == "active_users_request"
25
- messages.flatten.compact
25
+ messages << gauge_list(ns, event) if event["type"] == "gauge_list_request"
26
+
27
+ messages.flatten.compact.map do |m|
28
+ m["namespace"] = event["namespace"]; m
29
+ end
26
30
  end
27
31
 
28
32
  def widget(namespace, event)
29
- "FnordMetric::#{event["klass"]}".constantize.execute(namespace, event) # FIXPAUL
33
+ klass = if event["klass"] == "generic" && event["cmd"] == "values_for"
34
+ FnordMetric::NumbersWidget
35
+ elsif event["klass"] == "generic" && event["cmd"] == "values_at"
36
+ FnordMetric::TimeseriesWidget
37
+ else
38
+ "FnordMetric::#{event["klass"]}".constantize
39
+ end
40
+
41
+ klass.execute(namespace, event)
30
42
  end
31
43
 
32
44
  def gauge(namespace, event)
@@ -40,6 +52,8 @@ private
40
52
  end
41
53
 
42
54
  def discover(namespace)
55
+ namespace.ready!(@redis)
56
+
43
57
  [namespace.dashboards.map do |dash_key, dash|
44
58
  { "type" => "discover_response", "gauge_key" => dash_key, "view" => "dashboard",
45
59
  "group" => dash.group }
@@ -84,4 +98,20 @@ private
84
98
  }
85
99
  end
86
100
 
101
+ def gauge_list(namespace, event)
102
+ namespace.ready!(@redis)
103
+
104
+ gauges = namespace.gauges.map do |name, gauge|
105
+ {
106
+ "key" => gauge.name,
107
+ "title" => gauge.title
108
+ }
109
+ end
110
+
111
+ {
112
+ :type => "gauge_list_response",
113
+ :gauges => gauges
114
+ }
115
+ end
116
+
87
117
  end
@@ -35,4 +35,4 @@ class FnordMetric::WebSocket < Rack::WebSocket::Application
35
35
  rand(8**64).to_s(36)
36
36
  end
37
37
 
38
- end
38
+ end
@@ -11,7 +11,7 @@ class FnordMetric::BarsWidget < FnordMetric::Widget
11
11
  return false unless resp
12
12
 
13
13
  resp.merge(
14
- :class => "widget_response",
14
+ :type => "widget_response",
15
15
  :widget_key => event["widget_key"]
16
16
  )
17
17
  end
@@ -8,14 +8,36 @@ class FnordMetric::NumbersWidget < FnordMetric::Widget
8
8
  return false unless resp
9
9
 
10
10
  resp.merge(
11
- :class => "widget_response",
11
+ :type => "widget_response",
12
12
  :widget_key => event["widget_key"]
13
13
  )
14
14
  end
15
15
 
16
16
  def self.execute_values_for(gauge, event)
17
+ unless gauge
18
+ return { "error" => "gauge not found..." }
19
+ end
20
+
17
21
  _t = Time.now.to_i
18
22
 
23
+ if at = event["at"]
24
+ value = if at =~ /sum\((.+)\)/
25
+ vals = gauge.values_in(FnordMetric::Util.parse_time($1).._t+gauge.tick)
26
+ vals.values.compact.map(&:to_f).inject(&:+)
27
+ elsif at =~ /avg\((.+)\)/
28
+ vals = gauge.values_in(FnordMetric::Util.parse_time($1).._t+gauge.tick)
29
+ (vals.values.compact.map(&:to_f).inject(&:+) || 0) / vals.size.to_f
30
+ else
31
+ gauge.value_at(FnordMetric::Util.parse_time(at)).to_i
32
+ end
33
+
34
+ return({
35
+ "cmd" => "values_for",
36
+ "at" => event["at"],
37
+ "gauge" => gauge.name,
38
+ "value" => value })
39
+ end
40
+
19
41
  values = {}.tap do |out|
20
42
  event["offsets"].each do |off|
21
43
  if off.to_s.starts_with?("s")
@@ -36,14 +58,16 @@ class FnordMetric::NumbersWidget < FnordMetric::Widget
36
58
  end
37
59
  end
38
60
 
39
- { "cmd" => "values_for",
40
- "series" => gauge.name,
61
+ { "cmd" => "values_for",
62
+ "gauge" => gauge.name,
41
63
  "values" => values }
42
64
  end
43
65
 
44
66
  def data
45
67
  super.merge(
46
68
  :series => gauges.map(&:name),
69
+ :series_titles => gauges.map(&:title),
70
+ :series_units => Hash[gauges.map{ |g| [g.name, g.unit] }],
47
71
  :offsets => (@opts[:offsets] || [0, 1, "s30"]),
48
72
  :autoupdate => (@opts[:autoupdate] || 60)
49
73
  )
@@ -53,4 +77,4 @@ class FnordMetric::NumbersWidget < FnordMetric::Widget
53
77
  false
54
78
  end
55
79
 
56
- end
80
+ end
@@ -5,8 +5,16 @@ class FnordMetric::TimeseriesWidget < FnordMetric::Widget
5
5
  {
6
6
  :cmd => :values_at,
7
7
  :gauges => event["gauges"].map{ |gkey|
8
- vals = namespace.gauges[gkey.to_sym].values_in(event["since"]..event["until"])
9
- { :key => gkey, :vals => vals }
8
+ _gauge = namespace.gauges[gkey.to_sym]
9
+ unless _gauge
10
+ return { :error => "gauge not found: #{gkey}" }
11
+ end
12
+
13
+ t_since = FnordMetric::Util.parse_time(event["since"].to_s)
14
+ t_until = FnordMetric::Util.parse_time(event["until"].to_s)
15
+
16
+ vals = _gauge.values_in(t_since..t_until)
17
+ { :key => gkey, :vals => vals, :title => _gauge.title }
10
18
  }
11
19
  }
12
20
  end
@@ -14,17 +22,18 @@ class FnordMetric::TimeseriesWidget < FnordMetric::Widget
14
22
  return false unless resp
15
23
 
16
24
  resp.merge(
17
- :class => "widget_response",
25
+ :type => "widget_response",
18
26
  :widget_key => event["widget_key"]
19
27
  )
20
28
  end
21
29
 
22
- def data
30
+ def data
23
31
  super.merge(
24
32
  :series => series,
25
33
  :gauges => gauges.map(&:name),
26
34
  :start_timestamp => ticks.first,
27
35
  :end_timestamp => ticks.last,
36
+ :xticks => (@opts[:xticks] || 30),
28
37
  :autoupdate => (@opts[:autoupdate] || 60),
29
38
  :include_current => !!@opts[:include_current],
30
39
  :default_style => (@opts[:plot_style] || 'line'),
@@ -35,12 +44,13 @@ class FnordMetric::TimeseriesWidget < FnordMetric::Widget
35
44
 
36
45
  def series
37
46
  colors = FnordMetric::COLORS.dup
38
-
47
+
39
48
  gauges.map do |gauge|
40
- {
41
- :name => gauge.name,
42
- :data => [{:x => ticks.first, :y => 0}],
43
- :color => colors.unshift(colors.pop).first
49
+ {
50
+ :name => gauge.name,
51
+ :title => gauge.title,
52
+ :data => [{:x => ticks.first, :y => 0}],
53
+ :color => colors.unshift(colors.pop).first
44
54
  }
45
55
  end
46
56
  end