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,61 @@
1
+ class FnordMetric::Toplist
2
+
3
+ attr_accessor :timelines, :total
4
+
5
+ def initialize(timeline = {})
6
+ @total = 0
7
+ @toplist = Hash.new{ |h,k| h[k] = 0 }
8
+
9
+ @timelines = Hash.new do |h,k|
10
+ h[k] = Hash.new{ |h,k| h[k] = 0 }
11
+ end
12
+ end
13
+
14
+ def incr_item(time, item, value)
15
+ @toplist[item] += value.to_f
16
+ @timelines[item][time] += value.to_f
17
+ @total += value.to_f
18
+ end
19
+
20
+ def prepare!
21
+ @toplist_arr = @toplist.to_a.sort do |a,b|
22
+ b.last <=> a.last
23
+ end
24
+ end
25
+
26
+ def toplist(take = 100)
27
+ prepare! unless @toplist_arr
28
+ @toplist_arr[0..(take-1)]
29
+ end
30
+
31
+ def percentage(item)
32
+ (@toplist[item].to_f / total.to_f) * 100.0
33
+ end
34
+
35
+ def value(item)
36
+ @toplist[item].to_f
37
+ end
38
+
39
+ def trend(item)
40
+ times = @timelines[item].keys.sort
41
+
42
+ (@timelines[item][times.last] -
43
+ @timelines[item][times.first]) /
44
+ @timelines[item][times.first]
45
+ end
46
+
47
+ def rank(item)
48
+ prepare! unless @toplist_arr
49
+
50
+ @toplist_arr.index([item, value(item)]) + 1
51
+ end
52
+
53
+ def trending(take = 100)
54
+ @toplist.to_a.map{ |k,v|
55
+ [k, trend(k)]
56
+ }.sort{ |a,b|
57
+ b.last <=> a.last
58
+ }[0..(take-1)]
59
+ end
60
+
61
+ end
@@ -1,3 +1,3 @@
1
1
  module FnordMetric
2
- VERSION = "0.7.5"
2
+ VERSION = "0.9.7"
3
3
  end
@@ -0,0 +1,122 @@
1
+ # encoding: utf-8
2
+
3
+ class FnordMetric::App < Sinatra::Base
4
+
5
+ include FnordMetric::AppHelpers
6
+
7
+ if RUBY_VERSION =~ /1.9.\d/
8
+ Encoding.default_external = Encoding::UTF_8
9
+ end
10
+
11
+ if ENV['RACK_ENV'] == "test"
12
+ set :raise_errors, true
13
+ end
14
+
15
+ enable :session
16
+ set :haml, :format => :html5
17
+ set :views, ::File.expand_path('../../../../web/haml', __FILE__)
18
+ set :public_folder, ::File.expand_path('../../../../web', __FILE__)
19
+
20
+ helpers do
21
+ include Rack::Utils
22
+ include FnordMetric::AppHelpers
23
+ end
24
+
25
+ def initialize(opts)
26
+ @opts = opts
27
+
28
+ @namespaces = FnordMetric.namespaces
29
+ @redis = Redis.connect(:url => opts[:redis_url])
30
+
31
+ super(nil)
32
+ end
33
+
34
+ get '/' do
35
+ redirect "#{path_prefix}/#{@namespaces.keys.first}"
36
+ end
37
+
38
+ get '/:namespace' do
39
+ pass unless current_namespace
40
+ haml :app
41
+ end
42
+
43
+ post '/events' do
44
+ halt 400, 'please specify the event_type (_type)' unless params["_type"]
45
+ track_event((8**32).to_s(36), parse_params(params))
46
+ end
47
+
48
+ # FIXPAUL move to websockets
49
+ get '/:namespace/dashboard/:dashboard' do
50
+ dashboard = current_namespace.dashboards.fetch(params[:dashboard])
51
+
52
+ dashboard.to_json
53
+ end
54
+
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
+ end
122
+
@@ -0,0 +1,42 @@
1
+ module FnordMetric::AppHelpers
2
+
3
+ def h(*args)
4
+ escape_html(*args)
5
+ end
6
+
7
+ def path_prefix
8
+ request.env["SCRIPT_NAME"]
9
+ end
10
+
11
+ def namespaces
12
+ @namespaces
13
+ end
14
+
15
+ def current_namespace
16
+ @namespaces[@namespaces.keys.detect{ |k|
17
+ k.to_s == params[:namespace]
18
+ }.try(:intern)]
19
+ end
20
+
21
+ def parse_params(hash)
22
+ hash.tap do |h|
23
+ h.keys.each{ |k| h[k] = parse_param(h[k]) }
24
+ end
25
+ end
26
+
27
+ def parse_param(object)
28
+ return object unless object.is_a?(String)
29
+ return object.to_f if object.match(/^[0-9]+[,\.][0-9]+$/)
30
+ return object.to_i if object.match(/^[0-9]+$/)
31
+ object
32
+ end
33
+
34
+ def track_event(event_id, event_data)
35
+ # FIXPAUL: use api
36
+ @redis.hincrby "#{@opts[:redis_prefix]}-stats", "events_received", 1
37
+ @redis.set "#{@opts[:redis_prefix]}-event-#{event_id}", event_data.to_json
38
+ @redis.lpush "#{@opts[:redis_prefix]}-queue", event_id
39
+ @redis.expire "#{@opts[:redis_prefix]}-event-#{event_id}", @opts[:event_queue_ttl]
40
+ end
41
+
42
+ end
@@ -16,6 +16,10 @@ class FnordMetric::Dashboard
16
16
  @options[:title]
17
17
  end
18
18
 
19
+ def group
20
+ @options[:group] || "Dashboards"
21
+ end
22
+
19
23
  def token
20
24
  token = title.to_s.gsub(/[\W]/, '')
21
25
  token = Digest::SHA1.hexdigest(title.to_s) if token.empty?
@@ -6,15 +6,20 @@ module FnordMetric
6
6
  #def self.track!(event_type, event_data)
7
7
  #end
8
8
 
9
- def self.all(opts)
9
+ def self.all(opts)
10
+ opts[:limit] ||= 100
11
+
10
12
  range_opts = { :withscores => true }
11
13
  range_opts.merge!(:limit => [0,opts[:limit]]) if opts[:limit]
14
+
12
15
  events = opts[:redis].zrevrangebyscore(
13
16
  "#{opts[:namespace_prefix]}-timeline",
14
17
  '+inf', opts[:since]||'0',
15
18
  range_opts
16
19
  )
17
20
 
21
+ events = events[0..opts[:limit] - 1]
22
+
18
23
  unless events.first.is_a?(Array)
19
24
  events = events.in_groups_of(2).map do |event_id, ts|
20
25
  [event_id, Float(ts)]
@@ -75,7 +80,7 @@ module FnordMetric
75
80
  def id
76
81
  @event_id
77
82
  end
78
-
83
+
79
84
  def data(key=nil)
80
85
  key ? @data[key.to_s] : @data
81
86
  end
@@ -0,0 +1,87 @@
1
+ class FnordMetric::Reactor
2
+
3
+ def initialize
4
+ @redis = FnordMetric.mk_redis
5
+ @namespaces = FnordMetric.namespaces
6
+ end
7
+
8
+ def execute(*args)
9
+ execute_unsafe(*args)
10
+ rescue Exception => e
11
+ FnordMetric.error("reactor crashed: " + e.to_s)
12
+ puts (e.backtrace * "\n") if ENV["FNORDMETRIC_ENV"] == "dev"
13
+ []
14
+ end
15
+
16
+ private
17
+
18
+ def execute_unsafe(socket, event, messages = [])
19
+ return false unless event["namespace"]
20
+ return false unless ns = @namespaces[event["namespace"].to_sym]
21
+ messages << discover(ns) if event["type"] == "discover_request"
22
+ messages << widget(ns, event) if event["type"] == "widget_request"
23
+ messages << gauge(ns, event) if event["type"] == "render_request"
24
+ messages << active_users(ns, event) if event["type"] == "active_users_request"
25
+ messages.flatten.compact
26
+ end
27
+
28
+ def widget(namespace, event)
29
+ "FnordMetric::#{event["klass"]}".constantize.execute(namespace, event) # FIXPAUL
30
+ end
31
+
32
+ def gauge(namespace, event)
33
+ return false unless gauge = namespace.gauges[event["gauge"].to_sym]
34
+
35
+ {
36
+ :type => "render_response",
37
+ :gauge => gauge.name,
38
+ :payload => gauge.render_to_event(namespace, event)
39
+ }
40
+ end
41
+
42
+ def discover(namespace)
43
+ [namespace.dashboards.map do |dash_key, dash|
44
+ { "type" => "discover_response", "gauge_key" => dash_key, "view" => "dashboard",
45
+ "group" => dash.group }
46
+ end,
47
+ namespace.gauges.map do |gauge_key, gauge|
48
+ next unless gauge.renderable?
49
+ { "type" => "discover_response", "gauge_key" => gauge_key, "view" => "gauge",
50
+ "title" => gauge.title, "group" => gauge.group, "tick" => gauge.tick }
51
+ end.compact]
52
+ end
53
+
54
+ def active_users(namespace, event)
55
+ namespace.ready!(@redis)
56
+
57
+ events = if event["filter_by_type"]
58
+ namespace.events(:by_type, :type => event["type"])
59
+ elsif event["filter_by_session_key"]
60
+ namespace.events(:by_session_key, :session_key => params["session_key"])
61
+ else
62
+ find_opts = { :limit => 100 }
63
+ find_opts.merge!(:since => event["since"].to_i+1) if event["since"]
64
+ namespace.events(:all, find_opts)
65
+ end
66
+
67
+ sessions = namespace.sessions(:all, :limit => 100).map do |session|
68
+ session.fetch_data!
69
+ session.to_json
70
+ end
71
+
72
+ types_key = namespace.key_prefix("type-")
73
+ types = if event["first_poll"]
74
+ @redis.keys("#{types_key}*").map{ |k| k.gsub(types_key,'') }
75
+ else
76
+ []
77
+ end
78
+
79
+ {
80
+ :type => "active_users_response",
81
+ :sessions => sessions,
82
+ :events => events.map(&:to_json),
83
+ :types => types
84
+ }
85
+ end
86
+
87
+ end
@@ -0,0 +1,53 @@
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
+ webapp = FnordMetric::App.new(@opts)
20
+
21
+ dispatch = Rack::Builder.app do
22
+ use Rack::CommonLogger
23
+ use Rack::ShowExceptions
24
+
25
+ map "/stream" do
26
+ run websocket
27
+ end
28
+
29
+ map "/" do
30
+ middleware_stack.each do |middleware|
31
+ use(*middleware[0..1], &middleware[2])
32
+ end
33
+
34
+ run webapp
35
+ end
36
+ end
37
+
38
+ unless ["thin", "hatetepe"].include? server
39
+ raise "Need an EventMachine webserver, but #{server} isn't"
40
+ end
41
+
42
+ host = @opts[:host]
43
+ port = @opts[:port]
44
+
45
+ Rack::Server.start(
46
+ :app => dispatch,
47
+ :server => server,
48
+ :Host => host,
49
+ :Port => port
50
+ ) && FnordMetric.log("listening on http://#{host}:#{port}")
51
+ end
52
+
53
+ end
@@ -0,0 +1,38 @@
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
+ # socket openened :)
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
+ end
33
+
34
+ def get_uuid
35
+ rand(8**64).to_s(36)
36
+ end
37
+
38
+ 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
+ :class => "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,56 @@
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
+ :class => "widget_response",
12
+ :widget_key => event["widget_key"]
13
+ )
14
+ end
15
+
16
+ def self.execute_values_for(gauge, event)
17
+ _t = Time.now.to_i
18
+
19
+ values = {}.tap do |out|
20
+ event["offsets"].each do |off|
21
+ if off.to_s.starts_with?("s")
22
+ offset = 0
23
+ span = (gauge.tick * off.to_s[1..-1].to_i)
24
+ values = gauge.values_in((_t-span).._t+gauge.tick)
25
+ value = values.values.compact.map(&:to_i).sum
26
+ else
27
+ offset = off.to_i * gauge.tick
28
+ span = gauge.tick
29
+ value = gauge.value_at(_t-offset)
30
+ end
31
+
32
+ out["#{gauge.name}-#{offset}-#{span}"] = {
33
+ :value => value,
34
+ :desc => "$formatOffset(#{offset}, #{span})"
35
+ }
36
+ end
37
+ end
38
+
39
+ { "cmd" => "values_for",
40
+ "series" => gauge.name,
41
+ "values" => values }
42
+ end
43
+
44
+ def data
45
+ super.merge(
46
+ :series => gauges.map(&:name),
47
+ :offsets => (@opts[:offsets] || [0, 1, "s30"]),
48
+ :autoupdate => (@opts[:autoupdate] || 60)
49
+ )
50
+ end
51
+
52
+ def has_tick?
53
+ false
54
+ end
55
+
56
+ end
@@ -0,0 +1,55 @@
1
+ class FnordMetric::TimeseriesWidget < FnordMetric::Widget
2
+
3
+ def self.execute(namespace, event)
4
+ resp = if event["cmd"] == "values_at"
5
+ {
6
+ :cmd => :values_at,
7
+ :gauges => event["gauges"].map{ |gkey|
8
+ vals = namespace.gauges[gkey.to_sym].values_in(event["since"]..event["until"])
9
+ { :key => gkey, :vals => vals }
10
+ }
11
+ }
12
+ end
13
+
14
+ return false unless resp
15
+
16
+ resp.merge(
17
+ :class => "widget_response",
18
+ :widget_key => event["widget_key"]
19
+ )
20
+ end
21
+
22
+ def data
23
+ super.merge(
24
+ :series => series,
25
+ :gauges => gauges.map(&:name),
26
+ :start_timestamp => ticks.first,
27
+ :end_timestamp => ticks.last,
28
+ :autoupdate => (@opts[:autoupdate] || 60),
29
+ :include_current => !!@opts[:include_current],
30
+ :default_style => (@opts[:plot_style] || 'line'),
31
+ :async_chart => true,
32
+ :tick => tick
33
+ )
34
+ end
35
+
36
+ def series
37
+ colors = FnordMetric::COLORS.dup
38
+
39
+ gauges.map do |gauge|
40
+ {
41
+ :name => gauge.name,
42
+ :data => [{:x => ticks.first, :y => 0}],
43
+ :color => colors.unshift(colors.pop).first
44
+ }
45
+ end
46
+ end
47
+
48
+ def has_tick?
49
+ true
50
+ end
51
+
52
+ end
53
+
54
+ class FnordMetric::TimelineWidget < FnordMetric::TimeseriesWidget
55
+ end
@@ -0,0 +1,64 @@
1
+ class FnordMetric::ToplistWidget < FnordMetric::Widget
2
+
3
+ def self.execute(namespace, event)
4
+ t = Time.now.to_i
5
+
6
+ return false unless event["gauge"]
7
+
8
+ resp = if event["cmd"] == "values_for"
9
+ {
10
+ :cmd => :values_for,
11
+ :gauge => event["gauge"],
12
+ :values => execute_values_for(namespace.gauges[event["gauge"].to_sym], t),
13
+ :count => namespace.gauges[event["gauge"].to_sym].field_values_total(t)
14
+ }
15
+ end
16
+
17
+ return false unless resp
18
+
19
+ resp.merge(
20
+ :class => "widget_response",
21
+ :widget_key => event["widget_key"]
22
+ )
23
+ end
24
+
25
+ def self.execute_values_for(gauge, time)
26
+ gauge.field_values_at(time).sort do |a,b|
27
+ a.first.to_i <=> b.first.to_i
28
+ end.map do |a|
29
+ [a.first, a.second.to_i]
30
+ end
31
+ end
32
+
33
+ def data
34
+ super.merge(
35
+ :gauges => data_gauges,
36
+ :autoupdate => (@opts[:autoupdate] || 0),
37
+ :render_target => @opts[:render_target],
38
+ :ticks => @opts[:ticks],
39
+ :click_callback => @opts[:click_callback],
40
+ :async_chart => true,
41
+ :tick => tick
42
+ ).tap do |dat|
43
+ dat.merge!(
44
+ :gauges => @opts[:_gauges]
45
+ ) if dat[:ticks]
46
+ end
47
+ end
48
+
49
+ def data_gauges
50
+ Hash.new.tap do |hash|
51
+ gauges.each do |g|
52
+ hash[g.name] = {
53
+ :tick => g.tick,
54
+ :title => g.title
55
+ }
56
+ end
57
+ end
58
+ end
59
+
60
+ def has_tick?
61
+ false
62
+ end
63
+
64
+ end