johnf-fnordmetric 1.2.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. data/Gemfile +6 -0
  2. data/Rakefile +9 -0
  3. data/fnordmetric.gemspec +41 -0
  4. data/lib/fnordmetric/acceptors/acceptor.rb +42 -0
  5. data/lib/fnordmetric/acceptors/amqp_acceptor.rb +56 -0
  6. data/lib/fnordmetric/acceptors/fyrehose_acceptor.rb +43 -0
  7. data/lib/fnordmetric/acceptors/stomp_acceptor.rb +71 -0
  8. data/lib/fnordmetric/acceptors/tcp_acceptor.rb +58 -0
  9. data/lib/fnordmetric/acceptors/udp_acceptor.rb +37 -0
  10. data/lib/fnordmetric/api.rb +46 -0
  11. data/lib/fnordmetric/cache.rb +20 -0
  12. data/lib/fnordmetric/context.rb +96 -0
  13. data/lib/fnordmetric/defaults.rb +22 -0
  14. data/lib/fnordmetric/enterprise/compatibility_handler.rb +42 -0
  15. data/lib/fnordmetric/ext.rb +75 -0
  16. data/lib/fnordmetric/gauge.rb +98 -0
  17. data/lib/fnordmetric/gauge_calculations.rb +106 -0
  18. data/lib/fnordmetric/gauge_modifiers.rb +144 -0
  19. data/lib/fnordmetric/gauge_rendering.rb +40 -0
  20. data/lib/fnordmetric/gauge_validations.rb +15 -0
  21. data/lib/fnordmetric/gauges/distribution_gauge.rb +87 -0
  22. data/lib/fnordmetric/gauges/server_health_gauge.rb +13 -0
  23. data/lib/fnordmetric/gauges/timeseries_gauge.rb +138 -0
  24. data/lib/fnordmetric/gauges/toplist_gauge.rb +44 -0
  25. data/lib/fnordmetric/histogram.rb +64 -0
  26. data/lib/fnordmetric/logger.rb +63 -0
  27. data/lib/fnordmetric/namespace.rb +208 -0
  28. data/lib/fnordmetric/session.rb +139 -0
  29. data/lib/fnordmetric/standalone.rb +20 -0
  30. data/lib/fnordmetric/timeseries.rb +79 -0
  31. data/lib/fnordmetric/toplist.rb +61 -0
  32. data/lib/fnordmetric/udp_client.rb +22 -0
  33. data/lib/fnordmetric/util.rb +25 -0
  34. data/lib/fnordmetric/version.rb +3 -0
  35. data/lib/fnordmetric/web/app.rb +63 -0
  36. data/lib/fnordmetric/web/app_helpers.rb +42 -0
  37. data/lib/fnordmetric/web/dashboard.rb +40 -0
  38. data/lib/fnordmetric/web/event.rb +99 -0
  39. data/lib/fnordmetric/web/reactor.rb +127 -0
  40. data/lib/fnordmetric/web/web.rb +59 -0
  41. data/lib/fnordmetric/web/websocket.rb +41 -0
  42. data/lib/fnordmetric/widget.rb +82 -0
  43. data/lib/fnordmetric/widgets/bars_widget.rb +44 -0
  44. data/lib/fnordmetric/widgets/html_widget.rb +28 -0
  45. data/lib/fnordmetric/widgets/numbers_widget.rb +80 -0
  46. data/lib/fnordmetric/widgets/pie_widget.rb +23 -0
  47. data/lib/fnordmetric/widgets/timeseries_widget.rb +65 -0
  48. data/lib/fnordmetric/widgets/toplist_widget.rb +68 -0
  49. data/lib/fnordmetric/worker.rb +89 -0
  50. data/lib/fnordmetric/zero_config_gauge.rb +138 -0
  51. data/lib/fnordmetric.rb +149 -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/fnordmetric-core.css +1409 -0
  75. data/web/fnordmetric-core.js +3420 -0
  76. data/web/fnordmetric-ui.css +282 -0
  77. data/web/fnordmetric-ui.js +12032 -0
  78. data/web/haml/app.haml +20 -0
  79. data/web/haml/distribution_gauge.haml +118 -0
  80. data/web/haml/timeseries_gauge.haml +80 -0
  81. data/web/haml/toplist_gauge.haml +194 -0
  82. data/web/img/head.png +0 -0
  83. data/web/img/list.png +0 -0
  84. data/web/img/list_active.png +0 -0
  85. data/web/img/list_hover.png +0 -0
  86. data/web/img/loader.gif +0 -0
  87. data/web/img/loader_white.gif +0 -0
  88. data/web/img/navbar.png +0 -0
  89. data/web/img/navbar_btn.png +0 -0
  90. data/web/img/picto_gauge.png +0 -0
  91. data/web/js/fnordmetric.bars_widget.js +178 -0
  92. data/web/js/fnordmetric.dashboard_view.js +99 -0
  93. data/web/js/fnordmetric.gauge_explorer.js +173 -0
  94. data/web/js/fnordmetric.gauge_view.js +260 -0
  95. data/web/js/fnordmetric.html_widget.js +21 -0
  96. data/web/js/fnordmetric.js +315 -0
  97. data/web/js/fnordmetric.numbers_widget.js +122 -0
  98. data/web/js/fnordmetric.overview_view.js +35 -0
  99. data/web/js/fnordmetric.pie_widget.js +118 -0
  100. data/web/js/fnordmetric.realtime_timeline_widget.js +175 -0
  101. data/web/js/fnordmetric.session_view.js +342 -0
  102. data/web/js/fnordmetric.timeline_widget.js +333 -0
  103. data/web/js/fnordmetric.timeseries_widget.js +405 -0
  104. data/web/js/fnordmetric.toplist_widget.js +119 -0
  105. data/web/js/fnordmetric.ui.js +91 -0
  106. data/web/js/fnordmetric.util.js +248 -0
  107. data/web/vendor/font-awesome/css/font-awesome-ie7.min.css +22 -0
  108. data/web/vendor/font-awesome/css/font-awesome.css +540 -0
  109. data/web/vendor/font-awesome/css/font-awesome.min.css +33 -0
  110. data/web/vendor/font-awesome/font/FontAwesome.otf +0 -0
  111. data/web/vendor/font-awesome/font/fontawesome-webfont.eot +0 -0
  112. data/web/vendor/font-awesome/font/fontawesome-webfont.svg +284 -0
  113. data/web/vendor/font-awesome/font/fontawesome-webfont.ttf +0 -0
  114. data/web/vendor/font-awesome/font/fontawesome-webfont.woff +0 -0
  115. data/web/vendor/jquery-1.6.2.min.js +18 -0
  116. data/web/vendor/jquery-ui.min.js +6 -0
  117. data/web/vendor/jquery.combobox.js +129 -0
  118. data/web/vendor/jquery.maskedinput.js +252 -0
  119. metadata +444 -0
@@ -0,0 +1,64 @@
1
+ class FnordMetric::Histogram < Hash
2
+
3
+ def initialize
4
+ super{ |h,k| h[k]=0 }
5
+ end
6
+
7
+ def set_opts(opts = {})
8
+ @opts = opts
9
+ end
10
+
11
+ def [](key)
12
+ super(key.to_f)
13
+ end
14
+
15
+ def []=(key, val)
16
+ super(key.to_f, val)
17
+ end
18
+
19
+ def min
20
+ keys.sort.first.to_i
21
+ end
22
+
23
+ def max
24
+ keys.sort.last.to_i
25
+ end
26
+
27
+ def histogram(windows)
28
+ windows = histogram_windows(windows) unless windows.is_a?(Array)
29
+ Hash[windows.map{ |w| [w,0] }].tap do |histo|
30
+ self.each do |k,v|
31
+ histo.detect do |win, wval|
32
+ histo[win] += v if win.include?(k)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def json_histogram(windows)
39
+ histogram(windows).to_a.sort do |a, b|
40
+ a[0].first <=> b[0].first
41
+ end.map do |r, v|
42
+ [r.size == 1.0 ? r.last.to_s : json_value(r), v.to_i]
43
+ end.to_json
44
+ end
45
+
46
+ def json_value(r)
47
+ "#{r.first.round(@opts[:precision]).to_s}-#{r.last.round(@opts[:precision]).to_s}"
48
+ end
49
+
50
+ private
51
+
52
+ def histogram_windows(windows)
53
+ _min = min
54
+ _max = max
55
+
56
+ return [(0..1)] if (_max-_min == 0)
57
+
58
+ windows.times
59
+ .inject((_min.._max)
60
+ .step(((_max-_min)/windows.to_f)).to_a << _max){ |a,n|
61
+ a[n]=(a[n]..a[n+1]); a }.take(windows)
62
+ end
63
+
64
+ end
@@ -0,0 +1,63 @@
1
+ class FnordMetric::Logger
2
+
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
34
+ dump_file = File.open(logfile_path, 'a+')
35
+
36
+ fetcher = Thread.new do
37
+ loop do
38
+ event = events.pop
39
+
40
+ dump_file.write(event.to_json+"\n")
41
+ dump_file.flush
42
+ end
43
+ end
44
+
45
+ listener = Thread.new do
46
+ backend = FnordMetric.backend
47
+ backend.subscribe do |event|
48
+ events << event if log_channel?(event["_channel"])
49
+ end
50
+ end
51
+
52
+ FnordMetric.log "logging to #{logfile_path}"
53
+ end
54
+
55
+
56
+ private
57
+
58
+ def log_channel?(channel)
59
+ return !!@opts[:channels] if !channel
60
+ @opts[:channels].include?(channel.to_s)
61
+ end
62
+
63
+ end
@@ -0,0 +1,208 @@
1
+ class FnordMetric::Namespace
2
+
3
+ attr_reader :handlers, :gauges, :opts, :key, :dashboards, :flags
4
+
5
+ @@opts = [:event, :gauge, :widget, :set_title, :hide_active_users, :hide_overview,
6
+ :hide_gauge_explorer, :dashboard]
7
+
8
+ @@multi_gauges = [:timeseries_gauge, :toplist_gauge, :distribution_gauge]
9
+
10
+ def initialize(key, opts)
11
+ @gauges = Hash.new
12
+ @dashboards = Hash.new
13
+ @handlers = Hash.new.with_indifferent_access
14
+ @title = key
15
+ @opts = opts
16
+ @key = key
17
+
18
+ @flags = {
19
+ :hide_active_users => (FnordMetric.options[:enable_active_users] == false),
20
+ :hide_gauge_explorer => (FnordMetric.options[:enable_gauge_explorer] == false)
21
+ }
22
+ end
23
+
24
+ def ready!(redis, sync_redis = nil)
25
+ @redis = redis
26
+ @sync_redis = sync_redis
27
+ load_gauges
28
+ self
29
+ end
30
+
31
+ def announce(event)
32
+ if !@flags[:hide_active_users]
33
+ announce_to_timeline(event)
34
+ announce_to_typelist(event)
35
+ end
36
+
37
+ if event[:_session]
38
+ event[:_session_key] = announce_to_session(event).session_key
39
+ end
40
+
41
+ if event[:_type].to_sym == :_enterprise
42
+ ctx = FnordMetric::Context.new(opts, FnordMetric::Enterprise::CompatibilityHandler)
43
+ ctx.call(event, @redis, self)
44
+ return self
45
+ end
46
+
47
+ if FnordMetric::ZeroConfigGauge::TYPES.include?(event[:_type].to_sym)
48
+ ctx = FnordMetric::Context.new(opts, FnordMetric::ZeroConfigGauge::Handler)
49
+ ctx.call(event, @redis, self)
50
+ return self
51
+ end
52
+
53
+ res = [
54
+ @handlers[event[:_type].to_s],
55
+ @handlers["*"]
56
+ ].flatten.compact.each do |context|
57
+ context.call(event, @redis, self)
58
+ end.size
59
+
60
+ if res == 0
61
+ FnordMetric.error("no handler for event-type: #{event[:_type]}")
62
+ end
63
+
64
+ self
65
+ end
66
+
67
+ def announce_to_session(event)
68
+ FnordMetric::Session.create(@opts.clone.merge(
69
+ :namespace_key => @key,
70
+ :namespace_prefix => key_prefix,
71
+ :redis => @redis,
72
+ :event => event
73
+ ))
74
+ end
75
+
76
+ def announce_to_timeline(event)
77
+ timeline_key = key_prefix(:timeline)
78
+ @redis.zadd(timeline_key, event[:_time], event[:_eid])
79
+ end
80
+
81
+ def announce_to_typelist(event)
82
+ typelist_key = key_prefix("type-#{event[:_type]}")
83
+ @redis.lpush(typelist_key, event[:_eid])
84
+ end
85
+
86
+
87
+ def key_prefix(append=nil)
88
+ [@opts[:redis_prefix], @key, append].compact.join("-")
89
+ end
90
+
91
+ def token
92
+ @key
93
+ end
94
+
95
+ def title
96
+ @title
97
+ end
98
+
99
+ def dashboards(name=nil, opts = {})
100
+ return @dashboards unless name
101
+ dash = FnordMetric::Dashboard.new(opts.merge(:title => name))
102
+ @dashboards[dash.token.to_s] ||= dash
103
+ end
104
+
105
+ def sessions(_ids, opts={})
106
+ return FnordMetric::Session.all(extend_opts(opts)) if _ids == :all
107
+ end
108
+
109
+ def events(_ids, opts={})
110
+ return FnordMetric::Event.all(extend_opts(opts)) if _ids == :all
111
+ return FnordMetric::Event.by_type(opts.delete(:type), extend_opts(opts)) if _ids == :by_type
112
+ return FnordMetric::Event.by_session_key(opts.delete(:session_key), extend_opts(opts)) if _ids == :by_session_key
113
+ end
114
+
115
+ def method_missing(m, *args, &block)
116
+ return send(:opt_multigauge, *args.unshift(m), &block) if @@multi_gauges.include?(m)
117
+ raise "unknown option: #{m}" unless @@opts.include?(m)
118
+ send(:"opt_#{m}", *args, &block)
119
+ end
120
+
121
+ def opt_hide_active_users
122
+ @flags[:hide_active_users] = true
123
+ end
124
+
125
+ def opt_hide_gauge_explorer
126
+ @flags[:hide_gauge_explorer] = true
127
+ end
128
+
129
+ def opt_hide_overview
130
+ @flags[:hide_overview] = true
131
+ end
132
+
133
+ def opt_set_title(title)
134
+ @title = title
135
+ end
136
+
137
+ def opt_event(event_type, opts={}, &block)
138
+ FnordMetric::Context.new(opts, block).tap do |context|
139
+ @handlers[event_type.to_s] ||= []
140
+ @handlers[event_type.to_s] << context
141
+ end
142
+ end
143
+
144
+ def opt_gauge(gauge_key, opts={})
145
+ opts.merge!(:key => gauge_key)
146
+ store_gauge(gauge_key, opts) if opts[:zero_config]
147
+ opts.merge!(:key_prefix => key_prefix)
148
+ klass = "FnordMetric::#{(opts[:type] || "").to_s.camelize}Gauge".constantize
149
+ @gauges[gauge_key] ||= klass.new(opts)
150
+ end
151
+
152
+ def opt_multigauge(gauge_type, gauge_key, opts={})
153
+ opts.merge!(:key => gauge_key, :key_prefix => key_prefix)
154
+ klass = "FnordMetric::#{gauge_type.to_s.camelize}"
155
+ @gauges[gauge_key] ||= klass.constantize.new(opts)
156
+ end
157
+
158
+ def opt_widget(dashboard, widget)
159
+ widget = build_widget(widget) if widget.is_a?(Hash)
160
+ dashboards(dashboard).add_widget(widget)
161
+ end
162
+
163
+ def opt_dashboard(dashboard, opts)
164
+ dashboards(dashboard, opts)
165
+ end
166
+
167
+ def build_widget(opts)
168
+ _gauges = [opts[:gauges]].flatten.map do |g|
169
+ @gauges[g] || FnordMetric::ZeroConfigGauge.new(g, self)
170
+ end
171
+ widget_klass = "FnordMetric::#{opts.fetch(:type).to_s.capitalize}Widget"
172
+ widget_klass.constantize.new(opts.merge(:gauges => _gauges))
173
+ end
174
+
175
+ def store_gauge(gauge_key, opts)
176
+ gaugelist_key = key_prefix("zero-config-gauges")
177
+ sync_redis.hset(gaugelist_key, gauge_key, opts.to_json)
178
+ end
179
+
180
+ def load_gauges
181
+ gaugelist_key = key_prefix("zero-config-gauges")
182
+ sync_redis.hgetall(gaugelist_key).each do |gauge_key, gauge_opts|
183
+ gopts = JSON.parse(gauge_opts).symbolize_keys
184
+ gopts.delete(:zero_config)
185
+ opt_gauge(gauge_key.to_sym, gopts)
186
+ end
187
+ end
188
+
189
+ def sync_redis
190
+ @sync_redis || @redis
191
+ end
192
+
193
+ def extend_opts(opts)
194
+ opts.merge(
195
+ :namespace_prefix => key_prefix,
196
+ :redis_prefix => @opts[:redis_prefix],
197
+ :redis => @redis
198
+ )
199
+ end
200
+
201
+ def to_json
202
+ flags.merge(
203
+ :token => token,
204
+ :title => title
205
+ ).to_json
206
+ end
207
+
208
+ end
@@ -0,0 +1,139 @@
1
+ class FnordMetric::Session
2
+
3
+ attr_accessor :updated_at, :name, :picture
4
+
5
+ @@meta_attributes = %w(name picture)
6
+
7
+ def self.create(opts)
8
+ redis = opts.fetch(:redis)
9
+ event = opts[:event]
10
+
11
+ hash = Digest::MD5.hexdigest(event[:_session])
12
+ set_key = "#{opts[:namespace_prefix]}-session"
13
+
14
+ self.new(hash).tap do |session|
15
+ session.add_redis(redis, set_key)
16
+ session.add_event(event)
17
+ session.expire(opts[:session_data_ttl])
18
+ end
19
+ end
20
+
21
+ def self.find(session_key, opts)
22
+ set_key = "#{opts[:namespace_prefix]}-session"
23
+ self.new(session_key, [opts[:redis], set_key])
24
+ end
25
+
26
+ def self.all(opts)
27
+ set_key = "#{opts[:namespace_prefix]}-session"
28
+ limit = (opts[:limit].try(:to_i)||0)-1
29
+ session_ids = opts[:redis].zrevrange(set_key, 0, limit, :withscores => true)
30
+
31
+ unless session_ids.first.is_a?(Array)
32
+ session_ids = session_ids.in_groups_of(2).map do |session_id, ts|
33
+ [session_id, Float(ts)]
34
+ end
35
+ end
36
+
37
+ session_ids.map do |session_key, ts|
38
+ find(session_key, opts).tap{ |s| s.updated_at = "%.f" % ts }
39
+ end
40
+ end
41
+
42
+ def initialize(session_key, redis_opts=nil)
43
+ @session_key = session_key
44
+ add_redis(*redis_opts) if redis_opts
45
+ end
46
+
47
+ def session_key
48
+ @session_key
49
+ end
50
+
51
+ def picture
52
+ @picture
53
+ end
54
+
55
+ def name
56
+ @name
57
+ end
58
+
59
+ def data(key=nil)
60
+ key ? @data[key] : @data
61
+ end
62
+
63
+ def event_ids
64
+ @event_ids || []
65
+ end
66
+
67
+ def events
68
+ []
69
+ end
70
+
71
+ def to_json
72
+ { :session_key => session_key }.tap do |hash|
73
+ hash.merge!(:_picture => @picture) if @picture
74
+ hash.merge!(:_name => @name) if @name
75
+ hash.merge!(:_updated_at => @updated_at) if @updated_at
76
+ end
77
+ end
78
+
79
+ def redis_key(append=nil)
80
+ [@redis_prefix, @session_key, append].compact.join("-")
81
+ end
82
+
83
+ def add_redis(redis, prefix)
84
+ @redis_prefix = prefix
85
+ @redis = redis
86
+ end
87
+
88
+ def touch(time=Time.now.to_i)
89
+ @redis.zadd(@redis_prefix, time, @session_key)
90
+ end
91
+
92
+ def expire(time)
93
+ @redis.expire(redis_key(:events), time)
94
+ @redis.expire(redis_key(:data), time)
95
+ end
96
+
97
+ def add_event(event)
98
+ @redis.zadd(redis_key(:events), event[:_time], event[:_eid])
99
+
100
+ add_data(:_picture, event[:url]) if event[:_type] == "_set_picture"
101
+ add_data(:_name, event[:name]) if event[:_type] == "_set_name"
102
+ add_event_data(event) if event[:_type] == "_set_data"
103
+ touch(event[:_time])
104
+ end
105
+
106
+ def add_event_data(event)
107
+ event.each do |key, value|
108
+ add_data(key, value) unless key.to_s.starts_with?("_")
109
+ end
110
+ end
111
+
112
+ def add_data(key, value)
113
+ @redis.hset(redis_key(:data), key, value)
114
+ end
115
+
116
+ def fetch_data!
117
+ @data = Hash.new
118
+ @redis.hgetall(redis_key(:data)).each do |key, value|
119
+ if key.to_s.starts_with?("_")
120
+ fetch_meta_key(key, value)
121
+ else
122
+ @data[key.intern] = value
123
+ end
124
+ end
125
+ end
126
+
127
+ def fetch_meta_key(key, value)
128
+ meta_key = key[1..-1]
129
+ if @@meta_attributes.include?(meta_key)
130
+ instance_variable_set(:"@#{meta_key}", value)
131
+ end
132
+ end
133
+
134
+ def fetch_event_ids!(since=-1)
135
+ # FIXME: use WITHSCORE to get the timestamps and return event objects
136
+ @event_ids = @redis.zrevrange(redis_key(:events), 0, since)
137
+ end
138
+
139
+ end
@@ -0,0 +1,20 @@
1
+ _opts = FnordMetric.options
2
+
3
+ if _opts[:web_interface]
4
+ FnordMetric::Web.new(
5
+ :host => _opts[:web_interface][0],
6
+ :port => _opts[:web_interface][1]
7
+ )
8
+ end
9
+
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
+ )
16
+ end
17
+
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
@@ -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
@@ -0,0 +1,22 @@
1
+ class FnordMetric::UDPClient
2
+
3
+ def initialize(host, port)
4
+ @sock = UDPSocket.new
5
+ @sock.connect(host, port)
6
+ end
7
+
8
+ def event(event_data)
9
+ begin
10
+ if event_data.is_a?(Hash)
11
+ event_data = event_data.to_json
12
+ else
13
+ JSON.parse(event_data) # void ;)
14
+ end
15
+ rescue JSON::ParserError
16
+ FnordMetric.log("event_lost: can't parse json")
17
+ else
18
+ @sock.send(event_data, 0)
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,25 @@
1
+ class FnordMetric::Util
2
+
3
+ def self.parse_time(str)
4
+ str = str.downcase
5
+
6
+ if (str == "now")
7
+ Time.now.to_i
8
+ elsif str =~ /^([0-9]+(?:\.[0-9]+)?)$/
9
+ $1.to_i
10
+ elsif str =~ /^-([0-9]+(?:\.[0-9]+)?)$/
11
+ Time.now.to_i - $1.to_i
12
+ elsif str =~ /^-([0-9]+(?:\.[0-9]+)?)s(ec(ond)?(s?))?$/
13
+ Time.now.to_i - $1.to_f
14
+ elsif str =~ /^-([0-9]+(?:\.[0-9]+)?)m(in(ute)?(s?))?$/
15
+ Time.now.to_i - ($1.to_f * 60)
16
+ elsif str =~ /^-([0-9]+(?:\.[0-9]+)?)h(our(s?))?$/
17
+ Time.now.to_i - ($1.to_f * 3600)
18
+ elsif str =~ /^-([0-9]+(?:\.[0-9]+)?)d(ay(s?))?$/
19
+ Time.now.to_i - ($1.to_i * 86400)
20
+ else
21
+ raise "invalid time specifiation: #{str}"
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,3 @@
1
+ module FnordMetric
2
+ VERSION = "1.2.7"
3
+ end