fnordmetric 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +6 -0
- data/Gemfile.lock +21 -0
- data/Procfile +1 -2
- data/VERSION +1 -1
- data/_spec/app_spec.rb +178 -0
- data/{spec → _spec}/cache_spec.rb +0 -0
- data/{spec → _spec}/combine_metric_spec.rb +0 -0
- data/{spec → _spec}/core_spec.rb +0 -0
- data/{spec → _spec}/count_metric_spec.rb +0 -0
- data/_spec/dashboard_spec.rb +67 -0
- data/_spec/event_spec.rb +46 -0
- data/{spec → _spec}/metric_spec.rb +0 -0
- data/{spec → _spec}/report_spec.rb +0 -0
- data/{spec → _spec}/sum_metric_spec.rb +0 -0
- data/_spec/widget_spec.rb +107 -0
- data/doc/import_dump.rb +26 -0
- data/em_runner.rb +33 -0
- data/fnordmetric.gemspec +59 -20
- data/haml/app.haml +26 -12
- data/lib/fnordmetric.rb +150 -15
- data/lib/fnordmetric/app.rb +70 -11
- data/lib/fnordmetric/cache.rb +4 -4
- data/lib/fnordmetric/context.rb +65 -0
- data/lib/fnordmetric/dashboard.rb +16 -12
- data/lib/fnordmetric/event.rb +65 -15
- data/lib/fnordmetric/gauge.rb +46 -0
- data/lib/fnordmetric/gauge_calculations.rb +43 -0
- data/lib/fnordmetric/gauge_modifiers.rb +43 -0
- data/lib/fnordmetric/inbound_stream.rb +66 -0
- data/lib/fnordmetric/logger.rb +38 -0
- data/lib/fnordmetric/namespace.rb +120 -0
- data/lib/fnordmetric/numbers_widget.rb +29 -11
- data/lib/fnordmetric/session.rb +131 -0
- data/lib/fnordmetric/standalone.rb +31 -0
- data/lib/fnordmetric/timeline_widget.rb +29 -9
- data/lib/fnordmetric/widget.rb +50 -45
- data/lib/fnordmetric/worker.rb +80 -0
- data/pub/fnordmetric/fnordmetric.css +76 -9
- data/pub/fnordmetric/fnordmetric.js +541 -42
- data/pub/raphael-min.js +8 -0
- data/pub/raphael-utils.js +221 -0
- data/readme.rdoc +172 -27
- data/server.rb +22 -0
- data/spec/app_spec.rb +359 -117
- data/spec/context_spec.rb +42 -0
- data/spec/dashboard_spec.rb +7 -47
- data/spec/event_spec.rb +114 -33
- data/spec/gauge_modifiers_spec.rb +276 -0
- data/spec/gauge_spec.rb +128 -0
- data/spec/namespace_spec.rb +104 -0
- data/spec/session_spec.rb +231 -0
- data/spec/spec_helper.rb +27 -4
- data/spec/widget_spec.rb +81 -75
- data/spec/worker_spec.rb +37 -0
- data/test_stream.sh +187 -0
- data/ulm_stats.rb +198 -0
- metadata +114 -35
- data/lib/fnordmetric/core.rb +0 -66
- data/lib/fnordmetric/engine.rb +0 -3
data/lib/fnordmetric/app.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
class FnordMetric::App < Sinatra::Base
|
2
4
|
|
3
5
|
@@sessions = Hash.new
|
4
6
|
|
7
|
+
Encoding.default_external = Encoding::UTF_8
|
8
|
+
|
5
9
|
#use Rack::Reloader, 0
|
6
10
|
|
7
11
|
enable :session
|
@@ -9,6 +13,17 @@ class FnordMetric::App < Sinatra::Base
|
|
9
13
|
set :haml, :format => :html5
|
10
14
|
set :views, ::File.expand_path('../../../haml', __FILE__)
|
11
15
|
set :public, ::File.expand_path('../../../pub', __FILE__)
|
16
|
+
|
17
|
+
def initialize(namespaces, opts)
|
18
|
+
@namespaces = {}
|
19
|
+
@redis = Redis.new
|
20
|
+
namespaces.each do |key, block|
|
21
|
+
@namespaces[key] = FnordMetric::Namespace.new(key, opts.clone)
|
22
|
+
@namespaces[key].instance_eval(&block)
|
23
|
+
@namespaces[key].ready!(@redis.clone)
|
24
|
+
end
|
25
|
+
super(nil)
|
26
|
+
end
|
12
27
|
|
13
28
|
helpers do
|
14
29
|
include Rack::Utils
|
@@ -18,6 +33,12 @@ class FnordMetric::App < Sinatra::Base
|
|
18
33
|
request.env["SCRIPT_NAME"]
|
19
34
|
end
|
20
35
|
|
36
|
+
def current_namespace
|
37
|
+
@namespaces[@namespaces.keys.detect{ |k|
|
38
|
+
k.to_s == params[:namespace]
|
39
|
+
}.try(:intern)]
|
40
|
+
end
|
41
|
+
|
21
42
|
end
|
22
43
|
|
23
44
|
if ENV['RACK_ENV'] == "test"
|
@@ -25,24 +46,62 @@ class FnordMetric::App < Sinatra::Base
|
|
25
46
|
end
|
26
47
|
|
27
48
|
get '/' do
|
28
|
-
redirect "#{
|
49
|
+
redirect "#{path_prefix}/#{@namespaces.keys.first}"
|
29
50
|
end
|
30
51
|
|
31
|
-
get '
|
32
|
-
@dashboard = FnordMetric.dashboards.detect{|d| d.token == params[:name] }
|
33
|
-
@dashboard ||= FnordMetric.dashboards.first
|
52
|
+
get '/:namespace' do
|
34
53
|
haml :app
|
35
54
|
end
|
36
55
|
|
37
|
-
get '/
|
38
|
-
|
39
|
-
|
56
|
+
get '/favicon.ico' do
|
57
|
+
""
|
58
|
+
end
|
59
|
+
|
60
|
+
#get '/metric/:name' do
|
61
|
+
# content_type 'application/json'
|
62
|
+
# FnordMetric::MetricAPI.new(params).render
|
63
|
+
#end
|
64
|
+
|
65
|
+
#get '/widget/:name' do
|
66
|
+
# @dashboard = FnordMetric.dashboards.first
|
67
|
+
# @widget = @dashboard.widgets.first
|
68
|
+
# haml :widget
|
69
|
+
#end
|
70
|
+
|
71
|
+
get '/:namespace/sessions' do
|
72
|
+
|
73
|
+
sessions = current_namespace.sessions(:all, :limit => 100).map do |session|
|
74
|
+
session.fetch_data!
|
75
|
+
session.to_json
|
76
|
+
end
|
77
|
+
|
78
|
+
{ :sessions => sessions }.to_json
|
79
|
+
end
|
80
|
+
|
81
|
+
get '/:namespace/events' do
|
82
|
+
|
83
|
+
events = if params[:type]
|
84
|
+
current_namespace.events(:by_type, :type => params[:type])
|
85
|
+
else
|
86
|
+
find_opts = { :limit => 100 }
|
87
|
+
find_opts.merge!(:since => params[:since].to_i+1) if params[:since]
|
88
|
+
current_namespace.events(:all, find_opts)
|
89
|
+
end
|
90
|
+
|
91
|
+
{ :events => events.map(&:to_json) }.to_json
|
40
92
|
end
|
41
93
|
|
42
|
-
get '/
|
43
|
-
|
44
|
-
|
45
|
-
|
94
|
+
get '/:namespace/event_types' do
|
95
|
+
types_key = current_namespace.key_prefix("type-")
|
96
|
+
keys = @redis.keys("#{types_key}*").map{ |k| k.gsub(types_key,'') }
|
97
|
+
|
98
|
+
{ :types => keys }.to_json
|
99
|
+
end
|
100
|
+
|
101
|
+
get '/:namespace/dashboard/:dashboard' do
|
102
|
+
dashboard = current_namespace.dashboards.fetch(params[:dashboard])
|
103
|
+
|
104
|
+
dashboard.to_json
|
46
105
|
end
|
47
106
|
|
48
107
|
post '/events' do
|
data/lib/fnordmetric/cache.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
class FnordMetric::Cache
|
2
|
-
include Mongoid::Document
|
2
|
+
# include Mongoid::Document
|
3
3
|
|
4
|
-
self.collection_name = 'fnordmetric_cache'
|
4
|
+
# self.collection_name = 'fnordmetric_cache'
|
5
5
|
|
6
|
-
field :cache_key, :type => String
|
7
|
-
field :data, :type => Hash
|
6
|
+
# field :cache_key, :type => String
|
7
|
+
# field :data, :type => Hash
|
8
8
|
|
9
9
|
def self.store!(cache_key, data)
|
10
10
|
data = { :value => data } unless data.is_a?(Hash)
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class FnordMetric::Context
|
2
|
+
|
3
|
+
include FnordMetric::GaugeModifiers
|
4
|
+
|
5
|
+
def initialize(opts, block)
|
6
|
+
@block = block
|
7
|
+
@opts = opts
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(event, redis)
|
11
|
+
@redis = redis
|
12
|
+
@event = event
|
13
|
+
self.instance_eval(&@block)
|
14
|
+
rescue Exception => e
|
15
|
+
raise e if ENV['FNORDMETRIC_ENV'] == 'test'
|
16
|
+
puts "error: #{e.message}"
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def session_key
|
22
|
+
@event[:_session_key]
|
23
|
+
end
|
24
|
+
|
25
|
+
def data
|
26
|
+
@event
|
27
|
+
end
|
28
|
+
|
29
|
+
def key(gauge)
|
30
|
+
fetch_gauge(gauge).key
|
31
|
+
end
|
32
|
+
|
33
|
+
def time
|
34
|
+
@event[:_time].to_i
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def fetch_gauge(_gauge)
|
40
|
+
_gauge.is_a?(FnordMetric::Gauge) ? _gauge : @opts[:gauges].fetch(_gauge)
|
41
|
+
rescue
|
42
|
+
error! "error: gauge '#{_gauge}' is undefined"
|
43
|
+
end
|
44
|
+
|
45
|
+
def error!(msg)
|
46
|
+
FnordMetric.error!(msg)
|
47
|
+
end
|
48
|
+
|
49
|
+
def assure_two_dimensional!(gauge)
|
50
|
+
return true if gauge.two_dimensional?
|
51
|
+
error! "error: #{caller[0].split(" ")[-1]} can only be used with 2-dimensional gauges"
|
52
|
+
end
|
53
|
+
|
54
|
+
def assure_three_dimensional!(gauge)
|
55
|
+
return true unless gauge.two_dimensional?
|
56
|
+
error! "error: #{caller[0].split(" ")[-1]} can only be used with 3-dimensional gauges"
|
57
|
+
end
|
58
|
+
|
59
|
+
def assure_non_progressive!(gauge)
|
60
|
+
return true unless gauge.progressive?
|
61
|
+
error! "error: #{caller[0].split(" ")[-1]} can only be used with non-progressive gauges"
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
@@ -1,17 +1,15 @@
|
|
1
1
|
class FnordMetric::Dashboard
|
2
2
|
|
3
|
-
attr_accessor :widgets
|
3
|
+
attr_accessor :widgets
|
4
4
|
|
5
|
-
def initialize(options
|
6
|
-
|
5
|
+
def initialize(options={})
|
6
|
+
raise "please provide a :title" unless options[:title]
|
7
7
|
@widgets = Array.new
|
8
|
-
|
9
|
-
(_block||block).call(self)
|
10
|
-
add_report(@options[:report]) if @options[:report]
|
8
|
+
@options = options
|
11
9
|
end
|
12
10
|
|
13
11
|
def add_widget(w)
|
14
|
-
@widgets <<
|
12
|
+
@widgets << w
|
15
13
|
end
|
16
14
|
|
17
15
|
def title
|
@@ -21,10 +19,16 @@ class FnordMetric::Dashboard
|
|
21
19
|
def token
|
22
20
|
title.to_s.gsub(/[\W]/, '')
|
23
21
|
end
|
24
|
-
|
25
|
-
def add_report(report)
|
26
|
-
@report = report
|
27
|
-
@widgets.each{ |w| w.add_report(report) }
|
28
|
-
end
|
29
22
|
|
23
|
+
def to_json
|
24
|
+
{
|
25
|
+
:title => title,
|
26
|
+
:widgets => {}.tap { |wids|
|
27
|
+
@widgets.each do |w|
|
28
|
+
wids[w.token] = w.render
|
29
|
+
end
|
30
|
+
}
|
31
|
+
}.to_json
|
32
|
+
end
|
33
|
+
|
30
34
|
end
|
data/lib/fnordmetric/event.rb
CHANGED
@@ -1,28 +1,78 @@
|
|
1
1
|
class FnordMetric::Event
|
2
|
-
include Mongoid::Document
|
3
2
|
|
4
|
-
|
3
|
+
attr_accessor :time, :type, :event_id
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
field :data, :type => Hash
|
5
|
+
#def self.track!(event_type, event_data)
|
6
|
+
#end
|
9
7
|
|
10
|
-
def self.
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
def self.all(opts)
|
9
|
+
range_opts = { :withscores => true }
|
10
|
+
range_opts.merge!(:limit => [0,opts[:limit]]) if opts[:limit]
|
11
|
+
opts[:redis].zrevrangebyscore(
|
12
|
+
"#{opts[:namespace_prefix]}-timeline",
|
13
|
+
'+inf', opts[:since]||'0',
|
14
|
+
range_opts
|
15
|
+
).in_groups_of(2).map do |event_id, ts|
|
16
|
+
next if event_id.blank?
|
17
|
+
find(event_id, opts).tap{ |e| e.time = ts }
|
18
|
+
end
|
14
19
|
end
|
15
20
|
|
16
|
-
def
|
17
|
-
|
21
|
+
def self.by_type(_type, opts)
|
22
|
+
opts[:redis].lrange(
|
23
|
+
"#{opts[:namespace_prefix]}-type-#{_type}",
|
24
|
+
0, 200).map do |event_id|
|
25
|
+
next if event_id.blank?
|
26
|
+
find(event_id, opts).tap{ |e| }
|
27
|
+
end
|
18
28
|
end
|
19
29
|
|
20
|
-
def
|
21
|
-
|
30
|
+
def self.find(event_id, opts)
|
31
|
+
self.new(event_id, opts).tap do |event|
|
32
|
+
event.fetch!
|
33
|
+
end
|
22
34
|
end
|
23
35
|
|
24
|
-
def
|
25
|
-
|
36
|
+
def initialize(event_id, opts)
|
37
|
+
@opts = opts
|
38
|
+
@event_id = event_id
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetch!
|
42
|
+
@data = JSON.parse(fetch_json).tap do |event|
|
43
|
+
@type = event.delete("_type")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetch_json
|
48
|
+
@opts[:redis].get(redis_key) || "{}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def redis_key
|
52
|
+
[@opts[:redis_prefix], :event, @event_id].join("-")
|
53
|
+
end
|
54
|
+
|
55
|
+
def session_key
|
56
|
+
@data["_session"] ? Digest::MD5.hexdigest(@data["_session"]) : nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def id
|
60
|
+
@event_id
|
61
|
+
end
|
62
|
+
|
63
|
+
def data(key=nil)
|
64
|
+
key ? @data[key.to_s] : @data
|
65
|
+
end
|
66
|
+
|
67
|
+
alias :[] :data
|
68
|
+
|
69
|
+
def to_json
|
70
|
+
@data.merge!(
|
71
|
+
:_type => @type,
|
72
|
+
:_session_key => session_key,
|
73
|
+
:_eid => @event_id,
|
74
|
+
:_time => @time
|
75
|
+
)
|
26
76
|
end
|
27
77
|
|
28
78
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class FnordMetric::Gauge
|
2
|
+
|
3
|
+
include FnordMetric::GaugeCalculations
|
4
|
+
|
5
|
+
def initialize(opts)
|
6
|
+
opts.fetch(:key) && opts.fetch(:key_prefix)
|
7
|
+
@opts = opts
|
8
|
+
end
|
9
|
+
|
10
|
+
def tick
|
11
|
+
(@opts[:tick] || 3600).to_i
|
12
|
+
end
|
13
|
+
|
14
|
+
def tick_at(time)
|
15
|
+
(time/tick.to_f).floor*tick
|
16
|
+
end
|
17
|
+
|
18
|
+
def name
|
19
|
+
@opts[:key]
|
20
|
+
end
|
21
|
+
|
22
|
+
def key(_append=nil)
|
23
|
+
[@opts[:key_prefix], "gauge", name, tick, _append].flatten.compact.join("-")
|
24
|
+
end
|
25
|
+
|
26
|
+
def tick_key(_time, _append=nil)
|
27
|
+
key([(progressive? ? :progressive : tick_at(_time).to_s), _append])
|
28
|
+
end
|
29
|
+
|
30
|
+
def two_dimensional?
|
31
|
+
!@opts[:three_dimensional]
|
32
|
+
end
|
33
|
+
|
34
|
+
def progressive?
|
35
|
+
!!@opts[:progressive]
|
36
|
+
end
|
37
|
+
|
38
|
+
def unique?
|
39
|
+
!!@opts[:unique]
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_redis(_redis)
|
43
|
+
@opts[:redis] = _redis
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module FnordMetric::GaugeCalculations
|
2
|
+
|
3
|
+
@@avg_per_session_proc = proc{ |_v, _t|
|
4
|
+
#raise redis.get(tick_key(_t, :"sessions-count")).inspect
|
5
|
+
(_v.to_f / (redis.get(tick_key(_t, :"sessions-count"))||0).to_i)
|
6
|
+
}
|
7
|
+
|
8
|
+
def value_at(time, opts={}, &block)
|
9
|
+
_t = tick_at(time)
|
10
|
+
_v = redis.hget(key, _t)
|
11
|
+
|
12
|
+
calculate_value(_v, _t, opts, block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def values_at(times, opts={}, &block)
|
16
|
+
times = times.map{ |_t| tick_at(_t) }
|
17
|
+
Hash.new.tap do |ret|
|
18
|
+
redis.hmget(key, *times).each_with_index do |_v, _n|
|
19
|
+
_t = times[_n]
|
20
|
+
ret[_t] = calculate_value(_v, _t, opts, block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def values_in(range, opts={}, &block)
|
26
|
+
values_at((tick_at(range.first)..range.last).step(tick))
|
27
|
+
end
|
28
|
+
|
29
|
+
def calculate_value(_v, _t, opts, block)
|
30
|
+
block = @@avg_per_session_proc if opts[:avg_per_session]
|
31
|
+
|
32
|
+
if block
|
33
|
+
instance_exec(_v, _t, &block)
|
34
|
+
else
|
35
|
+
_v
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def redis
|
40
|
+
@opts[:redis]
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module FnordMetric::GaugeModifiers
|
2
|
+
|
3
|
+
def incr(gauge_name, value=1)
|
4
|
+
gauge = fetch_gauge(gauge_name)
|
5
|
+
assure_two_dimensional!(gauge)
|
6
|
+
if gauge.unique?
|
7
|
+
incr_uniq(gauge, value)
|
8
|
+
else
|
9
|
+
incr_tick(gauge, value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def incr_tick(gauge, value)
|
14
|
+
if gauge.progressive?
|
15
|
+
@redis.incrby(gauge.key(:head), value).callback do |head|
|
16
|
+
@redis.hsetnx(gauge.key, gauge.tick_at(time), head).callback do |_new|
|
17
|
+
@redis.hincrby(gauge.key, gauge.tick_at(time), value) unless _new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
else
|
21
|
+
@redis.hincrby(gauge.key, gauge.tick_at(time), value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def incr_uniq(gauge, value)
|
26
|
+
return false unless session_key
|
27
|
+
@redis.sadd(gauge.tick_key(time, :sessions), session_key).callback do |_new|
|
28
|
+
@redis.expire(gauge.tick_key(time, :sessions), gauge.tick)
|
29
|
+
if _new
|
30
|
+
@redis.incr(gauge.tick_key(time, :"sessions-count")).callback do |sc|
|
31
|
+
incr_tick(gauge, value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def incr_field(gauge_name, field_name, value=1)
|
38
|
+
gauge = fetch_gauge(gauge_name)
|
39
|
+
assure_three_dimensional!(gauge)
|
40
|
+
# here be dragons
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|