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,143 @@
|
|
1
|
+
class FnordMetric::TimeseriesGauge < FnordMetric::Gauge
|
2
|
+
|
3
|
+
def initialize(opts)
|
4
|
+
super(opts)
|
5
|
+
|
6
|
+
@opts[:series] = @opts[:series].map(&:to_sym)
|
7
|
+
|
8
|
+
if @opts[:calculate]
|
9
|
+
unless [:sum, :average, :progressive_sum].include?(@opts[:calculate].to_sym)
|
10
|
+
raise "unknown calculate option: #{@opts[:calculate]}"
|
11
|
+
end
|
12
|
+
@calculate = @opts[:calculate].to_sym
|
13
|
+
end
|
14
|
+
|
15
|
+
@calculate ||= :sum
|
16
|
+
|
17
|
+
if @calculate == :average
|
18
|
+
@calculate_proc = lambda{ |c,d| d > 0 ? (c/d.to_f).round(2) : 0 }
|
19
|
+
else
|
20
|
+
@calculate_proc = lambda{ |c,d| c }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def render(namespace, event)
|
25
|
+
@interval = parse_interval(event["interval"])
|
26
|
+
colors = FnordMetric::COLORS.dup
|
27
|
+
|
28
|
+
@series = Hash.new
|
29
|
+
@zooms = FnordMetric::TICKS[tick, @interval.size]
|
30
|
+
|
31
|
+
@total = 0
|
32
|
+
|
33
|
+
@opts[:series].each do |series|
|
34
|
+
ts = FnordMetric::Timeseries.new
|
35
|
+
|
36
|
+
fraction_values_in(@interval, series).each do |time, frac|
|
37
|
+
@total += frac.first # FIXPAUL
|
38
|
+
ts.incr_fraction(time, *frac)
|
39
|
+
end
|
40
|
+
|
41
|
+
@series[series] = {
|
42
|
+
:color => colors.unshift(colors.pop).first,
|
43
|
+
:data => Hash[@zooms.map{ |int| [int, ts.timeseries(@interval, int) ] }],
|
44
|
+
:timeseries => ts
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
render_page(:timeseries_gauge)
|
49
|
+
end
|
50
|
+
|
51
|
+
def execute(cmd, context, *args)
|
52
|
+
return incr(context, *args) if cmd == :incr
|
53
|
+
return incr_numerator(context, *args) if cmd == :incr_numerator
|
54
|
+
return incr_denominator(context, *args) if cmd == :incr_denominator
|
55
|
+
|
56
|
+
FnordMetric.error("gauge '#{name}': unknown command: #{cmd}")
|
57
|
+
end
|
58
|
+
|
59
|
+
def renderable?
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
def has_series?
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def incr(ctx, series_name = :default, value = 1)
|
70
|
+
if @calculate == :average
|
71
|
+
incr_numerator(ctx, series_name, value)
|
72
|
+
incr_denominator(ctx, series_name, 1)
|
73
|
+
elsif @calculate == :sum
|
74
|
+
incr_numerator(ctx, series_name, value)
|
75
|
+
elsif @calculate == :progressive_sum
|
76
|
+
incr_numerator(ctx, series_name, value, true)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
# class FnordMetric::NumericGauge < FnordMetric::MultiGauge
|
83
|
+
|
84
|
+
# def initialize(opts)
|
85
|
+
# super(opts)
|
86
|
+
|
87
|
+
# validate_series!
|
88
|
+
# validate_ticks!
|
89
|
+
|
90
|
+
# timeline_widget(
|
91
|
+
# :tab => "Overview",
|
92
|
+
# :title => "Total #{key_nouns.last}",
|
93
|
+
# :ticks => @opts[:ticks],
|
94
|
+
# :series => @opts[:series],
|
95
|
+
# :series_titles => Hash[@opts[:series].map{|s| [s, s]}],
|
96
|
+
# :autoupdate => 10,
|
97
|
+
# :height => 350
|
98
|
+
# ).on(:values_at) do |_series, _ticks, _tick|
|
99
|
+
# series_count_metrics[_series][_tick].values_at(_ticks)
|
100
|
+
# end
|
101
|
+
|
102
|
+
# numbers_widget(
|
103
|
+
# :tab => "Overview",
|
104
|
+
# :title => "Total #{key_nouns.last}",
|
105
|
+
# :series => @opts[:series],
|
106
|
+
# :series_titles => Hash[@opts[:series].map{|s| [s, s]}],
|
107
|
+
# :autoupdate => 2
|
108
|
+
# ).on(:values_for) do |_series|
|
109
|
+
# render_series_numbers(_series.to_sym)
|
110
|
+
# end
|
111
|
+
|
112
|
+
# end
|
113
|
+
|
114
|
+
# def react(event)
|
115
|
+
# if event["_class"] == "incrby" || event["_class"] == "incr"
|
116
|
+
# series = event["series"]
|
117
|
+
# series ||= @opts[:series][0] if @opts[:series].size == 1
|
118
|
+
# incr_series(series.to_sym, event["_time"], event["value"])
|
119
|
+
# end
|
120
|
+
# end
|
121
|
+
|
122
|
+
|
123
|
+
|
124
|
+
|
125
|
+
|
126
|
+
# def render_series_numbers(series)
|
127
|
+
# _t = Time.now.to_i
|
128
|
+
|
129
|
+
# {}.tap do |out|
|
130
|
+
# @opts[:ticks].each do |tick|
|
131
|
+
# out["#{tick}-now"] = {
|
132
|
+
# :value => series_count_metrics[series][tick].value_at(_t),
|
133
|
+
# :desc => "$formatTimeRangePre(#{tick}, 0)"
|
134
|
+
# }
|
135
|
+
# out["#{tick}-last"] = {
|
136
|
+
# :value => series_count_metrics[series][tick].value_at(_t-tick),
|
137
|
+
# :desc => "$formatTimeRangePre(#{tick}, -1)"
|
138
|
+
# }
|
139
|
+
# end
|
140
|
+
# end
|
141
|
+
# end
|
142
|
+
|
143
|
+
# end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class FnordMetric::ToplistGauge < FnordMetric::Gauge
|
2
|
+
|
3
|
+
def render(namespace, event)
|
4
|
+
@interval = parse_interval(event["interval"])
|
5
|
+
|
6
|
+
@toplist = FnordMetric::Toplist.new
|
7
|
+
@all_ticks = ticks_in(@interval, tick, 1)
|
8
|
+
|
9
|
+
@all_ticks.each do |_tick|
|
10
|
+
field_values_at(_tick, :limit => top_k, :append => :toplist).each do |*args|
|
11
|
+
item, count = args.flatten[0..1] # what the fnord... ~paul
|
12
|
+
@toplist.incr_item(_tick, item, count)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
@toplist.total = @all_ticks.inject(0){ |s,t| s + sync_redis.get(tick_key(t, :total)).to_i }
|
17
|
+
|
18
|
+
render_page(:toplist_gauge)
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute(cmd, context, *args)
|
22
|
+
return observe(context, args.first) if cmd == :observe
|
23
|
+
FnordMetric.error("gauge '#{name}': unknown command: #{cmd}")
|
24
|
+
end
|
25
|
+
|
26
|
+
def renderable?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def observe(ctx, item)
|
33
|
+
at = ctx.send(:time)
|
34
|
+
ctx.redis_exec :zincrby, tick_key(at, :toplist), 1, item
|
35
|
+
ctx.redis_exec :incrby, tick_key(at, :total), 1
|
36
|
+
ctx.redis_exec :zremrangebyrank, tick_key(at, :toplist), 0, -top_k
|
37
|
+
ctx.redis_exec :expire, tick_key(at, :toplist), retention
|
38
|
+
end
|
39
|
+
|
40
|
+
def top_k
|
41
|
+
(@opts[:top_k] || 1000).to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class FnordMetric::Histogram < Hash
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
super{ |h,k| h[k]=0 }
|
5
|
+
end
|
6
|
+
|
7
|
+
def [](key)
|
8
|
+
super(key.to_f)
|
9
|
+
end
|
10
|
+
|
11
|
+
def []=(key, val)
|
12
|
+
super(key.to_f, val)
|
13
|
+
end
|
14
|
+
|
15
|
+
def min
|
16
|
+
keys.sort.first.to_i
|
17
|
+
end
|
18
|
+
|
19
|
+
def max
|
20
|
+
keys.sort.last.to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
def histogram(windows)
|
24
|
+
windows = histogram_windows(windows) unless windows.is_a?(Array)
|
25
|
+
Hash[windows.map{ |w| [w,0] }].tap do |histo|
|
26
|
+
self.each do |k,v|
|
27
|
+
histo.detect do |win, wval|
|
28
|
+
histo[win] += v if win.include?(k)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def json_histogram(windows)
|
35
|
+
histogram(windows).to_a.sort do |a, b|
|
36
|
+
a[0].first <=> b[0].first
|
37
|
+
end.map do |r, v|
|
38
|
+
[r.size == 1.0 ? r.last.to_s :
|
39
|
+
"#{r.first.round(1).to_s}-#{r.last.round(1).to_s}", v.to_i]
|
40
|
+
end.to_json
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def histogram_windows(windows)
|
46
|
+
_min = min
|
47
|
+
_max = max
|
48
|
+
|
49
|
+
return [(0..1)] if (_max-_min == 0)
|
50
|
+
|
51
|
+
windows.times
|
52
|
+
.inject((_min.._max)
|
53
|
+
.step(((_max-_min)/windows.to_f)).to_a << _max){ |a,n|
|
54
|
+
a[n]=(a[n]..a[n+1]); a }.take(windows)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
data/lib/fnordmetric/logger.rb
CHANGED
@@ -1,57 +1,63 @@
|
|
1
1
|
class FnordMetric::Logger
|
2
2
|
|
3
|
-
def self.
|
4
|
-
|
5
|
-
|
3
|
+
def self.import(logfile_path)
|
4
|
+
expire = FnordMetric.options[:event_queue_ttl]
|
5
|
+
redis = Redis.new
|
6
|
+
|
7
|
+
@opts[:channels] ||= []
|
8
|
+
@opts[:channels] = @opts[:channels].map(&:to_s)
|
9
|
+
|
10
|
+
dump_file = File.open(logfile_path, 'r')
|
11
|
+
num_lines = %x{wc -l #{logfile_path}}.to_i
|
12
|
+
puts "importing #{num_lines} events..."
|
13
|
+
|
14
|
+
dump_file.each_with_log(num_lines) do |line, ind|
|
15
|
+
(8**64).to_s(36).tap do |uuid|
|
16
|
+
redis.set "fnordmetric-event-#{uuid}", line
|
17
|
+
redis.lpush "fnordmetric-queue" , uuid
|
18
|
+
redis.expire "fnordmetric-event-#{uuid}", expire
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(opts)
|
24
|
+
@opts = opts
|
25
|
+
opts.fetch(:file)
|
26
|
+
|
27
|
+
FnordMetric.register(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialized
|
31
|
+
logfile_path = @opts[:file]
|
32
|
+
|
33
|
+
events = Queue.new
|
6
34
|
dump_file = File.open(logfile_path, 'a+')
|
7
35
|
|
8
36
|
fetcher = Thread.new do
|
9
|
-
redis = Redis.new
|
10
37
|
loop do
|
11
|
-
|
12
|
-
event_data = redis.get("fnordmetric-event-#{event_id}")
|
13
|
-
event_hash = JSON.parse(event_data) rescue next
|
38
|
+
event = events.pop
|
14
39
|
|
15
|
-
|
16
|
-
|
17
|
-
dump_file.write(event_hash.to_json+"\n")
|
40
|
+
dump_file.write(event.to_json+"\n")
|
18
41
|
dump_file.flush
|
19
|
-
|
20
|
-
print "\033[1;34m"
|
21
|
-
print event_hash.inspect
|
22
|
-
print "\033[0m\n"
|
23
42
|
end
|
24
43
|
end
|
25
44
|
|
26
45
|
listener = Thread.new do
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
event_ids << event_id
|
31
|
-
end
|
46
|
+
backend = FnordMetric.backend
|
47
|
+
backend.subscribe do |event|
|
48
|
+
events << event if log_channel?(event["_channel"])
|
32
49
|
end
|
33
50
|
end
|
34
51
|
|
35
|
-
|
52
|
+
FnordMetric.log "logging to #{logfile_path}"
|
36
53
|
end
|
37
54
|
|
38
|
-
def self.import(logfile_path)
|
39
|
-
redis = Redis.new
|
40
|
-
dump_file = File.open(logfile_path, 'r')
|
41
55
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
log_every = (dump_lines.length / 150)
|
48
|
-
dump_lines.each_with_index do |line,n|
|
49
|
-
puts "#{n}/#{dump_lines.length} (#{((n/dump_lines.length.to_f)*100).to_i}%)" if n%log_every==0
|
50
|
-
my_uuid = "#{pre_uuid}-#{n}"
|
51
|
-
redis.set("fnordmetric-event-#{my_uuid}", line)
|
52
|
-
redis.lpush("fnordmetric-queue", my_uuid)
|
53
|
-
redis.expire("fnordmetric-event-#{my_uuid}", 3600*12)
|
54
|
-
end
|
56
|
+
private
|
57
|
+
|
58
|
+
def log_channel?(channel)
|
59
|
+
return !!@opts[:channels] if !channel
|
60
|
+
@opts[:channels].include?(channel.to_s)
|
55
61
|
end
|
56
62
|
|
57
63
|
end
|
@@ -1,38 +1,43 @@
|
|
1
1
|
class FnordMetric::Namespace
|
2
2
|
|
3
|
-
attr_reader :handlers, :gauges, :opts, :key, :dashboards
|
3
|
+
attr_reader :handlers, :gauges, :opts, :key, :dashboards, :flags
|
4
4
|
|
5
|
-
@@opts = [:event, :gauge, :widget, :set_title, :
|
5
|
+
@@opts = [:event, :gauge, :widget, :set_title, :hide_active_users, :hide_overview]
|
6
|
+
@@multi_gauges = [:timeseries_gauge, :toplist_gauge, :distribution_gauge]
|
6
7
|
|
7
|
-
def initialize(key, opts)
|
8
|
+
def initialize(key, opts)
|
8
9
|
@gauges = Hash.new
|
9
10
|
@dashboards = Hash.new
|
10
11
|
@handlers = Hash.new
|
12
|
+
@flags = Hash.new
|
11
13
|
@title = key
|
12
14
|
@active_users_available = true
|
13
15
|
@opts = opts
|
14
|
-
@key = key
|
16
|
+
@key = key
|
15
17
|
end
|
16
18
|
|
17
19
|
def ready!(redis)
|
18
20
|
@redis = redis
|
19
|
-
@gauges.map{ |k,g| g.add_redis(@redis) }
|
20
21
|
self
|
21
22
|
end
|
22
23
|
|
23
|
-
def announce(event)
|
24
|
+
def announce(event)
|
24
25
|
announce_to_timeline(event)
|
25
26
|
announce_to_typelist(event)
|
26
|
-
|
27
|
+
|
27
28
|
if event[:_session]
|
28
29
|
event[:_session_key] = announce_to_session(event).session_key
|
29
30
|
end
|
30
31
|
|
31
|
-
[
|
32
|
+
res = [
|
32
33
|
@handlers[event[:_type].to_s],
|
33
34
|
@handlers["*"]
|
34
|
-
].flatten.compact.each do |context|
|
35
|
-
context.call(event, @redis)
|
35
|
+
].flatten.compact.each do |context|
|
36
|
+
context.call(event, @redis)
|
37
|
+
end.size
|
38
|
+
|
39
|
+
if res == 0
|
40
|
+
FnordMetric.error("no handler for event-type: #{event[:_type]}")
|
36
41
|
end
|
37
42
|
|
38
43
|
self
|
@@ -40,11 +45,11 @@ class FnordMetric::Namespace
|
|
40
45
|
|
41
46
|
def announce_to_session(event)
|
42
47
|
FnordMetric::Session.create(@opts.clone.merge(
|
43
|
-
:namespace_key => @key,
|
48
|
+
:namespace_key => @key,
|
44
49
|
:namespace_prefix => key_prefix,
|
45
50
|
:redis => @redis,
|
46
51
|
:event => event
|
47
|
-
))
|
52
|
+
))
|
48
53
|
end
|
49
54
|
|
50
55
|
def announce_to_timeline(event)
|
@@ -65,15 +70,15 @@ class FnordMetric::Namespace
|
|
65
70
|
def token
|
66
71
|
@key
|
67
72
|
end
|
68
|
-
|
73
|
+
|
69
74
|
def title
|
70
75
|
@title
|
71
76
|
end
|
72
|
-
|
77
|
+
|
73
78
|
def active_users_available
|
74
79
|
@active_users_available
|
75
80
|
end
|
76
|
-
|
81
|
+
|
77
82
|
def dashboards(name=nil)
|
78
83
|
return @dashboards unless name
|
79
84
|
dash = FnordMetric::Dashboard.new(:title => name)
|
@@ -91,29 +96,41 @@ class FnordMetric::Namespace
|
|
91
96
|
end
|
92
97
|
|
93
98
|
def method_missing(m, *args, &block)
|
99
|
+
return send(:opt_multigauge, *args.unshift(m), &block) if @@multi_gauges.include?(m)
|
94
100
|
raise "unknown option: #{m}" unless @@opts.include?(m)
|
95
101
|
send(:"opt_#{m}", *args, &block)
|
96
102
|
end
|
97
103
|
|
98
|
-
def
|
99
|
-
@
|
104
|
+
def opt_hide_active_users
|
105
|
+
@flags[:hide_active_users] = true
|
100
106
|
end
|
101
|
-
|
102
|
-
def
|
103
|
-
@
|
107
|
+
|
108
|
+
def opt_hide_overview
|
109
|
+
@flags[:hide_overview] = true
|
104
110
|
end
|
105
|
-
|
111
|
+
|
112
|
+
def opt_set_title(title)
|
113
|
+
@title = title
|
114
|
+
end
|
115
|
+
|
106
116
|
def opt_event(event_type, opts={}, &block)
|
107
117
|
opts.merge!(:redis => @redis, :gauges => @gauges)
|
108
118
|
FnordMetric::Context.new(opts, block).tap do |context|
|
109
119
|
@handlers[event_type.to_s] ||= []
|
110
120
|
@handlers[event_type.to_s] << context
|
111
|
-
end
|
121
|
+
end
|
112
122
|
end
|
113
123
|
|
114
124
|
def opt_gauge(gauge_key, opts={})
|
115
125
|
opts.merge!(:key => gauge_key, :key_prefix => key_prefix)
|
116
|
-
|
126
|
+
klass = "FnordMetric::#{(opts[:type] || "").to_s.camelize}Gauge".constantize
|
127
|
+
@gauges[gauge_key] ||= klass.new(opts)
|
128
|
+
end
|
129
|
+
|
130
|
+
def opt_multigauge(gauge_type, gauge_key, opts={})
|
131
|
+
opts.merge!(:key => gauge_key, :key_prefix => key_prefix)
|
132
|
+
klass = "FnordMetric::#{gauge_type.to_s.camelize}"
|
133
|
+
@gauges[gauge_key] ||= klass.constantize.new(opts)
|
117
134
|
end
|
118
135
|
|
119
136
|
def opt_widget(dashboard, widget)
|
@@ -135,4 +152,11 @@ class FnordMetric::Namespace
|
|
135
152
|
)
|
136
153
|
end
|
137
154
|
|
155
|
+
def to_json
|
156
|
+
flags.merge(
|
157
|
+
:token => token,
|
158
|
+
:title => title
|
159
|
+
).to_json
|
160
|
+
end
|
161
|
+
|
138
162
|
end
|
data/lib/fnordmetric/session.rb
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
class FnordMetric::Session
|
2
|
-
|
2
|
+
|
3
3
|
attr_accessor :updated_at, :name, :picture
|
4
4
|
|
5
5
|
@@meta_attributes = %w(name picture)
|
6
6
|
|
7
|
-
def self.create(opts)
|
7
|
+
def self.create(opts)
|
8
8
|
redis = opts.fetch(:redis)
|
9
|
-
event = opts[:event]
|
9
|
+
event = opts[:event]
|
10
10
|
|
11
11
|
hash = Digest::MD5.hexdigest(event[:_session])
|
12
12
|
set_key = "#{opts[:namespace_prefix]}-session"
|
13
13
|
|
14
14
|
self.new(hash).tap do |session|
|
15
|
-
session.add_redis(redis, set_key)
|
16
|
-
session.add_event(event)
|
15
|
+
session.add_redis(redis, set_key)
|
16
|
+
session.add_event(event)
|
17
17
|
session.expire(opts[:session_data_ttl])
|
18
|
-
end
|
18
|
+
end
|
19
19
|
end
|
20
20
|
|
21
21
|
def self.find(session_key, opts)
|
@@ -1,40 +1,20 @@
|
|
1
|
-
|
2
|
-
require 'redis'
|
1
|
+
_opts = FnordMetric.options
|
3
2
|
|
4
|
-
|
5
|
-
FnordMetric.
|
3
|
+
if _opts[:web_interface]
|
4
|
+
FnordMetric::Web.new(
|
5
|
+
:host => _opts[:web_interface][0],
|
6
|
+
:port => _opts[:web_interface][1]
|
7
|
+
)
|
6
8
|
end
|
7
9
|
|
8
|
-
|
9
|
-
FnordMetric.
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
13
|
-
|
14
|
-
FnordMetric.run
|
10
|
+
if _opts[:inbound_stream]
|
11
|
+
FnordMetric::Acceptor.new(
|
12
|
+
:protocol => _opts[:inbound_protocol],
|
13
|
+
:host => _opts[:inbound_stream][0],
|
14
|
+
:port => _opts[:inbound_stream][1]
|
15
|
+
)
|
15
16
|
end
|
16
17
|
|
17
|
-
|
18
|
-
FnordMetric::
|
19
|
-
end
|
20
|
-
|
21
|
-
task :import do
|
22
|
-
FnordMetric::Logger.import(dump_file_path)
|
23
|
-
end
|
24
|
-
|
25
|
-
task :help do
|
26
|
-
puts "usage: #{$0} {run|worker|log|import} [DUMP_FILE=fm_dump.json]"
|
27
|
-
end
|
28
|
-
|
29
|
-
task :default => :help
|
30
|
-
|
31
|
-
def dump_file_path
|
32
|
-
if ENV["DUMP_FILE"].blank?
|
33
|
-
Rake::Task[:help].execute; exit!
|
34
|
-
else
|
35
|
-
::File.expand_path(ENV["DUMP_FILE"], ::File.dirname($0))
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
Rake.application.init('fnordmetric')
|
40
|
-
Rake.application.top_level
|
18
|
+
if _opts[:start_worker]
|
19
|
+
FnordMetric::Worker.new()
|
20
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class FnordMetric::Timeseries
|
2
|
+
|
3
|
+
def initialize(timeline = {})
|
4
|
+
@timeline = Hash.new{ |h,k| h[k] = [0,nil] }
|
5
|
+
@timeline.merge!(timeline)
|
6
|
+
end
|
7
|
+
|
8
|
+
def incr_fraction(time, numerator, denominator)
|
9
|
+
incr_numerator(time, numerator) if numerator
|
10
|
+
incr_denominator(time, denominator) if denominator
|
11
|
+
end
|
12
|
+
|
13
|
+
def incr_numerator(time, value)
|
14
|
+
@timeline[time.to_i][0] += value
|
15
|
+
end
|
16
|
+
|
17
|
+
def incr_denominator(time, value)
|
18
|
+
@timeline[time.to_i][-1] ||= 0
|
19
|
+
@timeline[time.to_i][-1] += value
|
20
|
+
end
|
21
|
+
|
22
|
+
def timeseries(range, window, &block)
|
23
|
+
res = Hash.new{ |h,k| h[k] = [0,0] }
|
24
|
+
|
25
|
+
(((range.size)/window.to_f).ceil+1).times.map do |n|
|
26
|
+
res[((range.first+window*(n-1))/window.to_f).floor*window] = [0,0]
|
27
|
+
end
|
28
|
+
|
29
|
+
@timeline.each do |time, vals|
|
30
|
+
next unless range.include?(time)
|
31
|
+
wtime = (time/window.to_f).floor * window
|
32
|
+
if block
|
33
|
+
res[wtime] = block.call(*vals)
|
34
|
+
else
|
35
|
+
res[wtime][0] += vals[0]
|
36
|
+
res[wtime][1] += vals[1]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
FnordMetric::Timeseries.new(res)
|
41
|
+
end
|
42
|
+
|
43
|
+
def sum(range = (ticks.first..ticks.last))
|
44
|
+
@timeline
|
45
|
+
.inject(0){ |s,(t,v)| s + (range.include?(t) ? value_at(t) : 0) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def trend(range = (ticks.first..ticks.last))
|
49
|
+
range ||= (ticks.first..ticks.last)
|
50
|
+
|
51
|
+
rvals = @timeline.to_a
|
52
|
+
.select{ |t,v| range.include?(t) }
|
53
|
+
.sort{ |a,b| a.first <=> b.first }
|
54
|
+
.map{ |t,v| value_at(t) }
|
55
|
+
|
56
|
+
return 0 if rvals.size == 0
|
57
|
+
(rvals.last - rvals.first).to_f / rvals.first
|
58
|
+
end
|
59
|
+
|
60
|
+
def ticks
|
61
|
+
@timeline.keys.sort
|
62
|
+
end
|
63
|
+
|
64
|
+
def value_at(time)
|
65
|
+
if @timeline[time][1].to_i > 0
|
66
|
+
@timeline[time][0] / @timeline[time][1].to_f
|
67
|
+
else
|
68
|
+
@timeline[time][0]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_json(&block)
|
73
|
+
@timeline.to_a
|
74
|
+
.sort{ |a,b| a[0] <=> b[0] }
|
75
|
+
.map { |t,v| { :x => t, :y => block.call(*v), :v0 => v[0], :v1 => v[1] } }
|
76
|
+
.to_json
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|