bp-fnordmetric 1.2.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. data/Gemfile +6 -0
  2. data/Rakefile +6 -0
  3. data/fnordmetric.gemspec +41 -0
  4. data/lib/fnordmetric.rb +149 -0
  5. data/lib/fnordmetric/acceptors/acceptor.rb +42 -0
  6. data/lib/fnordmetric/acceptors/amqp_acceptor.rb +56 -0
  7. data/lib/fnordmetric/acceptors/fyrehose_acceptor.rb +43 -0
  8. data/lib/fnordmetric/acceptors/stomp_acceptor.rb +71 -0
  9. data/lib/fnordmetric/acceptors/tcp_acceptor.rb +58 -0
  10. data/lib/fnordmetric/acceptors/udp_acceptor.rb +37 -0
  11. data/lib/fnordmetric/api.rb +46 -0
  12. data/lib/fnordmetric/cache.rb +20 -0
  13. data/lib/fnordmetric/context.rb +96 -0
  14. data/lib/fnordmetric/defaults.rb +22 -0
  15. data/lib/fnordmetric/enterprise/compatibility_handler.rb +42 -0
  16. data/lib/fnordmetric/ext.rb +75 -0
  17. data/lib/fnordmetric/gauge.rb +98 -0
  18. data/lib/fnordmetric/gauge_calculations.rb +106 -0
  19. data/lib/fnordmetric/gauge_modifiers.rb +144 -0
  20. data/lib/fnordmetric/gauge_rendering.rb +40 -0
  21. data/lib/fnordmetric/gauge_validations.rb +15 -0
  22. data/lib/fnordmetric/gauges/distribution_gauge.rb +87 -0
  23. data/lib/fnordmetric/gauges/server_health_gauge.rb +13 -0
  24. data/lib/fnordmetric/gauges/timeseries_gauge.rb +138 -0
  25. data/lib/fnordmetric/gauges/toplist_gauge.rb +44 -0
  26. data/lib/fnordmetric/histogram.rb +64 -0
  27. data/lib/fnordmetric/logger.rb +63 -0
  28. data/lib/fnordmetric/namespace.rb +208 -0
  29. data/lib/fnordmetric/session.rb +139 -0
  30. data/lib/fnordmetric/standalone.rb +20 -0
  31. data/lib/fnordmetric/timeseries.rb +79 -0
  32. data/lib/fnordmetric/toplist.rb +61 -0
  33. data/lib/fnordmetric/udp_client.rb +22 -0
  34. data/lib/fnordmetric/util.rb +25 -0
  35. data/lib/fnordmetric/version.rb +3 -0
  36. data/lib/fnordmetric/web/app.rb +63 -0
  37. data/lib/fnordmetric/web/app_helpers.rb +42 -0
  38. data/lib/fnordmetric/web/dashboard.rb +40 -0
  39. data/lib/fnordmetric/web/event.rb +99 -0
  40. data/lib/fnordmetric/web/reactor.rb +127 -0
  41. data/lib/fnordmetric/web/web.rb +59 -0
  42. data/lib/fnordmetric/web/websocket.rb +41 -0
  43. data/lib/fnordmetric/widget.rb +82 -0
  44. data/lib/fnordmetric/widgets/bars_widget.rb +44 -0
  45. data/lib/fnordmetric/widgets/html_widget.rb +28 -0
  46. data/lib/fnordmetric/widgets/numbers_widget.rb +80 -0
  47. data/lib/fnordmetric/widgets/pie_widget.rb +23 -0
  48. data/lib/fnordmetric/widgets/timeseries_widget.rb +65 -0
  49. data/lib/fnordmetric/widgets/toplist_widget.rb +68 -0
  50. data/lib/fnordmetric/worker.rb +89 -0
  51. data/lib/fnordmetric/zero_config_gauge.rb +138 -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/haml/app.haml +20 -0
  75. data/web/haml/distribution_gauge.haml +118 -0
  76. data/web/haml/timeseries_gauge.haml +80 -0
  77. data/web/haml/toplist_gauge.haml +194 -0
  78. data/web/img/head.png +0 -0
  79. data/web/img/list.png +0 -0
  80. data/web/img/list_active.png +0 -0
  81. data/web/img/list_hover.png +0 -0
  82. data/web/img/loader.gif +0 -0
  83. data/web/img/loader_white.gif +0 -0
  84. data/web/img/navbar.png +0 -0
  85. data/web/img/navbar_btn.png +0 -0
  86. data/web/img/picto_gauge.png +0 -0
  87. data/web/js/fnordmetric.bars_widget.js +178 -0
  88. data/web/js/fnordmetric.dashboard_view.js +99 -0
  89. data/web/js/fnordmetric.gauge_explorer.js +173 -0
  90. data/web/js/fnordmetric.gauge_view.js +260 -0
  91. data/web/js/fnordmetric.html_widget.js +21 -0
  92. data/web/js/fnordmetric.js +315 -0
  93. data/web/js/fnordmetric.numbers_widget.js +122 -0
  94. data/web/js/fnordmetric.overview_view.js +35 -0
  95. data/web/js/fnordmetric.pie_widget.js +118 -0
  96. data/web/js/fnordmetric.realtime_timeline_widget.js +175 -0
  97. data/web/js/fnordmetric.session_view.js +342 -0
  98. data/web/js/fnordmetric.timeline_widget.js +333 -0
  99. data/web/js/fnordmetric.timeseries_widget.js +405 -0
  100. data/web/js/fnordmetric.toplist_widget.js +119 -0
  101. data/web/js/fnordmetric.ui.js +91 -0
  102. data/web/js/fnordmetric.util.js +248 -0
  103. data/web/vendor/font-awesome/css/font-awesome-ie7.min.css +22 -0
  104. data/web/vendor/font-awesome/css/font-awesome.css +540 -0
  105. data/web/vendor/font-awesome/css/font-awesome.min.css +33 -0
  106. data/web/vendor/font-awesome/font/FontAwesome.otf +0 -0
  107. data/web/vendor/font-awesome/font/fontawesome-webfont.eot +0 -0
  108. data/web/vendor/font-awesome/font/fontawesome-webfont.svg +284 -0
  109. data/web/vendor/font-awesome/font/fontawesome-webfont.ttf +0 -0
  110. data/web/vendor/font-awesome/font/fontawesome-webfont.woff +0 -0
  111. data/web/vendor/jquery-1.6.2.min.js +18 -0
  112. data/web/vendor/jquery-ui.min.js +6 -0
  113. data/web/vendor/jquery.combobox.js +129 -0
  114. data/web/vendor/jquery.maskedinput.js +252 -0
  115. metadata +438 -0
@@ -0,0 +1,127 @@
1
+ class FnordMetric::Reactor
2
+
3
+ def initialize
4
+ @redis = FnordMetric.mk_redis
5
+ @namespaces = FnordMetric.namespaces.dup
6
+ end
7
+
8
+ def ready!
9
+ @namespaces.each do |key, ns|
10
+ ns.ready!(@redis)
11
+ end
12
+ end
13
+
14
+ def execute(*args)
15
+ execute_unsafe(*args)
16
+ rescue Exception => e
17
+ FnordMetric.error("reactor crashed: " + e.to_s)
18
+ puts (e.backtrace * "\n") if ENV["FNORDMETRIC_ENV"] == "dev"
19
+ []
20
+ end
21
+
22
+ private
23
+
24
+ def execute_unsafe(socket, event, messages = [])
25
+ return [] unless event["namespace"]
26
+
27
+ unless ns = @namespaces[event["namespace"].to_sym]
28
+ return([{ "error" => "invalid namespace: #{event["namespace"]}" }])
29
+ end
30
+
31
+ messages << discover(ns) if event["type"] == "discover_request"
32
+ messages << widget(ns, event) if event["type"] == "widget_request"
33
+ messages << gauge(ns, event) if event["type"] == "render_request"
34
+ messages << active_users(ns, event) if event["type"] == "active_users_request"
35
+ messages << gauge_list(ns, event) if event["type"] == "gauge_list_request"
36
+
37
+ messages.flatten.compact.map do |m|
38
+ m["namespace"] = event["namespace"]; m
39
+ end
40
+ end
41
+
42
+ def widget(namespace, event)
43
+ klass = if event["klass"] == "generic" && event["cmd"] == "values_for"
44
+ FnordMetric::NumbersWidget
45
+ elsif event["klass"] == "generic" && event["cmd"] == "values_at"
46
+ FnordMetric::TimeseriesWidget
47
+ else
48
+ "FnordMetric::#{event["klass"]}".constantize
49
+ end
50
+
51
+ klass.execute(namespace, event)
52
+ end
53
+
54
+ def gauge(namespace, event)
55
+ return false unless gauge = namespace.gauges[event["gauge"].to_sym]
56
+
57
+ {
58
+ :type => "render_response",
59
+ :gauge => gauge.name,
60
+ :payload => gauge.render_to_event(namespace, event)
61
+ }
62
+ end
63
+
64
+ def discover(namespace)
65
+ namespace.ready!(@redis)
66
+
67
+ [namespace.dashboards.map do |dash_key, dash|
68
+ { "type" => "discover_response", "gauge_key" => dash_key, "view" => "dashboard",
69
+ "group" => dash.group }
70
+ end,
71
+ namespace.gauges.map do |gauge_key, gauge|
72
+ next unless gauge.renderable?
73
+ { "type" => "discover_response", "gauge_key" => gauge_key, "view" => "gauge",
74
+ "title" => gauge.title, "group" => gauge.group, "tick" => gauge.tick }
75
+ end.compact]
76
+ end
77
+
78
+ def active_users(namespace, event)
79
+ namespace.ready!(@redis)
80
+
81
+ events = if event["filter_by_type"]
82
+ namespace.events(:by_type, :type => event["type"])
83
+ elsif event["filter_by_session_key"]
84
+ namespace.events(:by_session_key, :session_key => params["session_key"])
85
+ else
86
+ find_opts = { :limit => 100 }
87
+ find_opts.merge!(:since => event["since"].to_i+1) if event["since"]
88
+ namespace.events(:all, find_opts)
89
+ end
90
+
91
+ sessions = namespace.sessions(:all, :limit => 100).map do |session|
92
+ session.fetch_data!
93
+ session.to_json
94
+ end
95
+
96
+ types_key = namespace.key_prefix("type-")
97
+ types = if event["first_poll"]
98
+ @redis.keys("#{types_key}*").map{ |k| k.gsub(types_key,'') }
99
+ else
100
+ []
101
+ end
102
+
103
+ {
104
+ :type => "active_users_response",
105
+ :sessions => sessions,
106
+ :events => events.map(&:to_json),
107
+ :types => types
108
+ }
109
+ end
110
+
111
+ def gauge_list(namespace, event)
112
+ namespace.ready!(@redis)
113
+
114
+ gauges = namespace.gauges.map do |name, gauge|
115
+ {
116
+ "key" => gauge.name,
117
+ "title" => gauge.title
118
+ }
119
+ end
120
+
121
+ {
122
+ :type => "gauge_list_response",
123
+ :gauges => gauges
124
+ }
125
+ end
126
+
127
+ end
@@ -0,0 +1,59 @@
1
+ class FnordMetric::Web
2
+
3
+ def initialize(opts)
4
+ @opts = opts
5
+
6
+ @opts[:server] ||= "thin"
7
+ @opts[:host] ||= "0.0.0.0"
8
+ @opts[:port] ||= "4242"
9
+
10
+ FnordMetric.register(self)
11
+ end
12
+
13
+ def initialized
14
+ server = @opts[:server].downcase
15
+
16
+ middleware_stack = @opts[:use] || []
17
+
18
+ websocket = FnordMetric::WebSocket.new
19
+
20
+ webapp = if FnordMetric.options[:http_websocket_only]
21
+ lambda { |env| [204, {}, [""]] }
22
+ else
23
+ FnordMetric::App.new(@opts)
24
+ end
25
+
26
+ dispatch = Rack::Builder.app do
27
+ use Rack::CommonLogger
28
+ use Rack::ShowExceptions
29
+
30
+ map "/stream" do
31
+ run websocket
32
+ end
33
+
34
+ map "/" do
35
+ middleware_stack.each do |middleware|
36
+ use(*middleware[0..1], &middleware[2])
37
+ end
38
+
39
+ run webapp
40
+ end
41
+
42
+ end
43
+
44
+ unless ["thin", "hatetepe"].include? server
45
+ raise "Need an EventMachine webserver, but #{server} isn't"
46
+ end
47
+
48
+ host = @opts[:host]
49
+ port = @opts[:port]
50
+
51
+ Rack::Server.start(
52
+ :app => dispatch,
53
+ :server => server,
54
+ :Host => host,
55
+ :Port => port
56
+ ) && FnordMetric.log("listening on http://#{host}:#{port}")
57
+ end
58
+
59
+ end
@@ -0,0 +1,41 @@
1
+ require "eventmachine"
2
+ require 'rack/websocket'
3
+ require "em-hiredis"
4
+ require "json"
5
+
6
+ class FnordMetric::WebSocket < Rack::WebSocket::Application
7
+
8
+ def initialize
9
+ super
10
+
11
+ @reactor = FnordMetric::Reactor.new
12
+ @uuid = "websocket-#{get_uuid}"
13
+ end
14
+
15
+ def on_open(env)
16
+ @reactor.ready!
17
+ end
18
+
19
+ def on_message(env, message)
20
+ begin
21
+ message = JSON.parse(message)
22
+ rescue
23
+ puts "websocket: invalid json"
24
+ else
25
+ message["_eid"] ||= get_uuid
26
+ message["_sender"] = @uuid
27
+
28
+ @reactor.execute(self, message).each do |m|
29
+ send_data m.to_json
30
+ end
31
+ end
32
+ rescue Exception => e
33
+ FnordMetric.error("[WebSocket] #{e.to_s}")
34
+ puts e.backtrace.join("\n")
35
+ end
36
+
37
+ def get_uuid
38
+ rand(8**64).to_s(36)
39
+ end
40
+
41
+ end
@@ -0,0 +1,82 @@
1
+ class FnordMetric::Widget
2
+
3
+ attr_accessor :gauges, :tick
4
+
5
+ def initialize(opts={})
6
+ @opts = opts
7
+
8
+ unless opts.has_key?(:title)
9
+ error! "widget can't be initialized without a title"
10
+ end
11
+
12
+ add_gauges(opts.delete(:gauges))
13
+ end
14
+
15
+ def title
16
+ @opts[:title]
17
+ end
18
+
19
+ def token
20
+ token = title.to_s.gsub(/[\W]/, '').downcase
21
+ token = Digest::SHA1.hexdigest(title.to_s) if token.empty?
22
+ token
23
+ end
24
+
25
+ def add_gauges(gauges)
26
+ if gauges.blank? && has_tick?
27
+ error! "initializing a widget without gauges is void"
28
+ else
29
+ @gauges = gauges
30
+ end
31
+
32
+ if (ticks = gauges.map{ |g| g.tick }).uniq.length == 1
33
+ @tick = ticks.first
34
+ elsif !!self.try(:has_tick?)
35
+ error! "you can't add gauges with different ticks to the same widget"
36
+ end
37
+ end
38
+
39
+ def error!(msg)
40
+ FnordMetric.error!(msg)
41
+ end
42
+
43
+ def range
44
+ ensure_has_tick!
45
+ #@opts[:range] || default_range # FIXME: allow custom ranges, but assure that the range-start is 'on a tick'
46
+ default_range
47
+ end
48
+
49
+ def ticks
50
+ ensure_has_tick!
51
+ range.step(@tick)
52
+ end
53
+
54
+ def default_range(now=Time.now)
55
+ ensure_has_tick!
56
+ te = gauges.first.tick_at(now.to_i)
57
+ te -= @tick unless include_current?
58
+ rs = (@opts[:ticks] || (@tick == 1.hour.to_i ? 24 : 30)).to_i
59
+ (te-(@tick*rs)..te)
60
+ end
61
+
62
+ def include_current?
63
+ !(@opts[:include_current] == false)
64
+ end
65
+
66
+ def data
67
+ {
68
+ :title => @opts[:title],
69
+ :width => @opts[:width] || 100,
70
+ :klass => self.class.name.split("::").last
71
+ }
72
+ end
73
+
74
+ def render
75
+ data
76
+ end
77
+
78
+ def ensure_has_tick!
79
+ error! "widget does not have_tick" unless has_tick?
80
+ end
81
+
82
+ end
@@ -0,0 +1,44 @@
1
+ class FnordMetric::BarsWidget < FnordMetric::Widget
2
+
3
+ def self.execute(namespace, event)
4
+ resp = if event["cmd"] == "values_for"
5
+ {
6
+ :cmd => :values_for,
7
+ :values => execute_values_for(namespace.gauges[event["gauge"].to_sym], event["until"])
8
+ }
9
+ end
10
+
11
+ return false unless resp
12
+
13
+ resp.merge(
14
+ :type => "widget_response",
15
+ :widget_key => event["widget_key"]
16
+ )
17
+ end
18
+
19
+ def self.execute_values_for(gauge, time)
20
+ gauge.field_values_at(time).sort do |a,b|
21
+ a.first.to_i <=> b.first.to_i
22
+ end.map do |a|
23
+ [a.first, a.second.to_i]
24
+ end
25
+ end
26
+
27
+ def data
28
+ super.merge(
29
+ :gauge => gauges.first.name,
30
+ :title => gauges.first.title,
31
+ :autoupdate => (@opts[:autoupdate] || 60),
32
+ :order_by => (@opts[:order_by] || 'value'),
33
+ :plot_style => (@opts[:plot_style] || 'vertical'),
34
+ :async_chart => true,
35
+ :color => FnordMetric::COLORS.last,
36
+ :tick => tick
37
+ )
38
+ end
39
+
40
+ def has_tick?
41
+ false
42
+ end
43
+
44
+ end
@@ -0,0 +1,28 @@
1
+ class FnordMetric::HtmlWidget < FnordMetric::Widget
2
+ def data
3
+ super.merge(
4
+ :html => @opts[:html]
5
+ )
6
+ end
7
+
8
+ def add_gauges(gauges)
9
+ @gauges = []
10
+ @tick = 0
11
+
12
+ if !gauges.blank?
13
+ error! "initializing a html widget with gauges is void"
14
+ end
15
+ end
16
+
17
+ def data_gauges
18
+ {}
19
+ end
20
+
21
+ def default_range(now=Time.now)
22
+ 0..0
23
+ end
24
+
25
+ def has_tick?
26
+ false
27
+ end
28
+ end
@@ -0,0 +1,80 @@
1
+ class FnordMetric::NumbersWidget < FnordMetric::Widget
2
+
3
+ def self.execute(namespace, event)
4
+ resp = if event["cmd"] == "values_for"
5
+ execute_values_for(namespace.gauges[event["gauge"].to_sym], event)
6
+ end
7
+
8
+ return false unless resp
9
+
10
+ resp.merge(
11
+ :type => "widget_response",
12
+ :widget_key => event["widget_key"]
13
+ )
14
+ end
15
+
16
+ def self.execute_values_for(gauge, event)
17
+ unless gauge
18
+ return { "error" => "gauge not found..." }
19
+ end
20
+
21
+ _t = Time.now.to_i
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
+
41
+ values = {}.tap do |out|
42
+ event["offsets"].each do |off|
43
+ if off.to_s.starts_with?("s")
44
+ offset = 0
45
+ span = (gauge.tick * off.to_s[1..-1].to_i)
46
+ values = gauge.values_in((_t-span).._t+gauge.tick)
47
+ value = values.values.compact.map(&:to_i).sum
48
+ else
49
+ offset = off.to_i * gauge.tick
50
+ span = gauge.tick
51
+ value = gauge.value_at(_t-offset)
52
+ end
53
+
54
+ out["#{gauge.name}-#{offset}-#{span}"] = {
55
+ :value => value,
56
+ :desc => "$formatOffset(#{offset}, #{span})"
57
+ }
58
+ end
59
+ end
60
+
61
+ { "cmd" => "values_for",
62
+ "gauge" => gauge.name,
63
+ "values" => values }
64
+ end
65
+
66
+ def data
67
+ super.merge(
68
+ :series => gauges.map(&:name),
69
+ :series_titles => gauges.map(&:title),
70
+ :series_units => Hash[gauges.map{ |g| [g.name, g.unit] }],
71
+ :offsets => (@opts[:offsets] || [0, 1, "s30"]),
72
+ :autoupdate => (@opts[:autoupdate] || 60)
73
+ )
74
+ end
75
+
76
+ def has_tick?
77
+ false
78
+ end
79
+
80
+ end