fnordmetric 0.7.5 → 0.9.7
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/V1.0-ROADMAP +97 -0
- data/doc/full_example.rb +95 -511
- data/doc/legacy_example.rb +640 -0
- data/doc/minimal_example.rb +26 -0
- data/doc/preview3.png +0 -0
- data/fnordmetric.gemspec +3 -2
- data/lib/fnordmetric/acceptors/acceptor.rb +29 -0
- data/lib/fnordmetric/{inbound_stream.rb → acceptors/tcp_acceptor.rb} +8 -5
- data/lib/fnordmetric/{inbound_datagram.rb → acceptors/udp_acceptor.rb} +9 -8
- data/lib/fnordmetric/api.rb +2 -2
- data/lib/fnordmetric/context.rb +37 -18
- data/lib/fnordmetric/defaults.rb +9 -0
- data/lib/fnordmetric/ext.rb +72 -0
- data/lib/fnordmetric/gauge.rb +37 -10
- data/lib/fnordmetric/gauge_calculations.rb +38 -16
- data/lib/fnordmetric/gauge_modifiers.rb +67 -0
- data/lib/fnordmetric/gauge_rendering.rb +40 -0
- data/lib/fnordmetric/gauge_validations.rb +15 -0
- data/lib/fnordmetric/gauges/distribution_gauge.rb +85 -0
- data/lib/fnordmetric/gauges/timeseries_gauge.rb +143 -0
- data/lib/fnordmetric/gauges/toplist_gauge.rb +44 -0
- data/lib/fnordmetric/histogram.rb +57 -0
- data/lib/fnordmetric/logger.rb +42 -36
- data/lib/fnordmetric/namespace.rb +47 -23
- data/lib/fnordmetric/session.rb +6 -6
- data/lib/fnordmetric/standalone.rb +15 -35
- data/lib/fnordmetric/timeseries.rb +79 -0
- data/lib/fnordmetric/toplist.rb +61 -0
- data/lib/fnordmetric/version.rb +1 -1
- data/lib/fnordmetric/web/app.rb +122 -0
- data/lib/fnordmetric/web/app_helpers.rb +42 -0
- data/lib/fnordmetric/{dashboard.rb → web/dashboard.rb} +4 -0
- data/lib/fnordmetric/{event.rb → web/event.rb} +7 -2
- data/lib/fnordmetric/web/reactor.rb +87 -0
- data/lib/fnordmetric/web/web.rb +53 -0
- data/lib/fnordmetric/web/websocket.rb +38 -0
- data/lib/fnordmetric/widgets/bars_widget.rb +44 -0
- data/lib/fnordmetric/{html_widget.rb → widgets/html_widget.rb} +0 -0
- data/lib/fnordmetric/widgets/numbers_widget.rb +56 -0
- data/lib/fnordmetric/{pie_widget.rb → widgets/pie_widget.rb} +0 -0
- data/lib/fnordmetric/widgets/timeseries_widget.rb +55 -0
- data/lib/fnordmetric/widgets/toplist_widget.rb +64 -0
- data/lib/fnordmetric/worker.rb +26 -25
- data/lib/fnordmetric.rb +85 -115
- data/readme.md +362 -0
- data/spec/gauge_like_shared.rb +54 -0
- data/spec/gauge_spec.rb +2 -36
- data/spec/namespace_spec.rb +25 -11
- data/spec/spec_helper.rb +4 -0
- data/spec/{inbound_stream_spec.rb → tcp_acceptor_spec.rb} +3 -3
- data/spec/timeseries_gauge_spec.rb +54 -0
- data/spec/{inbound_datagram_spec.rb → udp_acceptor_spec.rb} +3 -3
- data/web/fnordmetric.css +786 -0
- data/web/haml/app.haml +38 -0
- data/web/haml/distribution_gauge.haml +118 -0
- data/web/haml/timeseries_gauge.haml +80 -0
- data/web/haml/toplist_gauge.haml +194 -0
- data/web/img/head.png +0 -0
- data/web/img/list.png +0 -0
- data/web/img/list_active.png +0 -0
- data/web/img/list_hover.png +0 -0
- data/web/img/loader_white.gif +0 -0
- data/web/img/navbar.png +0 -0
- data/web/img/navbar_btn.png +0 -0
- data/web/img/picto_gauge.png +0 -0
- data/web/js/fnordmetric.bars_widget.js +178 -0
- data/web/js/fnordmetric.dashboard_view.js +99 -0
- data/web/js/fnordmetric.gauge_view.js +260 -0
- data/web/js/fnordmetric.html_widget.js +21 -0
- data/web/js/fnordmetric.js +255 -0
- data/web/js/fnordmetric.numbers_widget.js +121 -0
- data/web/js/fnordmetric.overview_view.js +35 -0
- data/web/js/fnordmetric.pie_widget.js +118 -0
- data/web/js/fnordmetric.realtime_timeline_widget.js +175 -0
- data/web/js/fnordmetric.session_view.js +343 -0
- data/web/js/fnordmetric.timeline_widget.js +333 -0
- data/web/js/fnordmetric.timeseries_widget.js +388 -0
- data/web/js/fnordmetric.toplist_widget.js +112 -0
- data/web/js/fnordmetric.ui.js +91 -0
- data/web/js/fnordmetric.util.js +244 -0
- data/{pub → web}/loader.gif +0 -0
- data/web/vendor/d3.v2.js +9382 -0
- data/web/vendor/font-awesome/css/font-awesome.css +239 -0
- data/web/vendor/font-awesome/font/fontawesome-webfont.eot +0 -0
- data/web/vendor/font-awesome/font/fontawesome-webfont.svg +175 -0
- data/web/vendor/font-awesome/font/fontawesome-webfont.svgz +0 -0
- data/web/vendor/font-awesome/font/fontawesome-webfont.ttf +0 -0
- data/web/vendor/font-awesome/font/fontawesome-webfont.woff +0 -0
- data/web/vendor/jquery-1.6.2.min.js +18 -0
- data/web/vendor/jquery-ui.min.js +413 -0
- data/web/vendor/jquery.maskedinput.js +252 -0
- data/web/vendor/rickshaw.css +286 -0
- data/web/vendor/rickshaw.fnordmetric.js +2676 -0
- metadata +129 -79
- data/Gemfile +0 -6
- data/README.md +0 -404
- data/Rakefile +0 -6
- data/doc/version +0 -1
- data/haml/app.haml +0 -79
- data/haml/widget.haml +0 -9
- data/lib/fnordmetric/app.rb +0 -163
- data/lib/fnordmetric/average_metric.rb +0 -7
- data/lib/fnordmetric/bars_widget.rb +0 -26
- data/lib/fnordmetric/combine_metric.rb +0 -7
- data/lib/fnordmetric/count_metric.rb +0 -13
- data/lib/fnordmetric/funnel_widget.rb +0 -2
- data/lib/fnordmetric/metric.rb +0 -80
- data/lib/fnordmetric/metric_api.rb +0 -37
- data/lib/fnordmetric/numbers_widget.rb +0 -26
- data/lib/fnordmetric/report.rb +0 -29
- data/lib/fnordmetric/sum_metric.rb +0 -13
- data/lib/fnordmetric/timeline_widget.rb +0 -30
- data/lib/fnordmetric/toplist_widget.rb +0 -25
- data/pub/fnordmetric.css +0 -145
- data/pub/fnordmetric.js +0 -1179
- data/pub/vendor/highcharts.js +0 -170
- 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
|
data/lib/fnordmetric/version.rb
CHANGED
@@ -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
|
@@ -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
|
File without changes
|
@@ -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
|
File without changes
|
@@ -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
|