fnordmetric 1.0.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/Rakefile +1 -1
  2. data/fnordmetric.gemspec +1 -0
  3. data/lib/fnordmetric.rb +6 -13
  4. data/lib/fnordmetric/acceptors/acceptor.rb +20 -7
  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 +1 -0
  9. data/lib/fnordmetric/acceptors/udp_acceptor.rb +2 -1
  10. data/lib/fnordmetric/context.rb +21 -9
  11. data/lib/fnordmetric/defaults.rb +16 -3
  12. data/lib/fnordmetric/gauge.rb +8 -3
  13. data/lib/fnordmetric/gauge_calculations.rb +8 -2
  14. data/lib/fnordmetric/gauge_modifiers.rb +5 -4
  15. data/lib/fnordmetric/gauges/server_health_gauge.rb +13 -0
  16. data/lib/fnordmetric/namespace.rb +53 -17
  17. data/lib/fnordmetric/util.rb +25 -0
  18. data/lib/fnordmetric/version.rb +1 -1
  19. data/lib/fnordmetric/web/app.rb +7 -66
  20. data/lib/fnordmetric/web/reactor.rb +32 -2
  21. data/lib/fnordmetric/web/websocket.rb +1 -1
  22. data/lib/fnordmetric/widgets/bars_widget.rb +1 -1
  23. data/lib/fnordmetric/widgets/numbers_widget.rb +28 -4
  24. data/lib/fnordmetric/widgets/timeseries_widget.rb +19 -9
  25. data/lib/fnordmetric/widgets/toplist_widget.rb +8 -4
  26. data/lib/fnordmetric/worker.rb +5 -1
  27. data/lib/fnordmetric/zero_config_gauge.rb +138 -0
  28. data/spec/context_spec.rb +4 -4
  29. data/spec/event_spec.rb +11 -11
  30. data/spec/gauge_modifiers_spec.rb +135 -29
  31. data/spec/gauge_spec.rb +7 -2
  32. data/spec/namespace_spec.rb +8 -19
  33. data/spec/util_spec.rb +46 -0
  34. data/web/.gitignore +4 -0
  35. data/web/build.sh +34 -0
  36. data/web/{fnordmetric.css → css/fnordmetric.core.css} +121 -58
  37. data/web/haml/app.haml +4 -22
  38. data/web/{loader.gif → img/loader.gif} +0 -0
  39. data/web/js/fnordmetric.bars_widget.js +3 -3
  40. data/web/js/fnordmetric.dashboard_view.js +1 -1
  41. data/web/js/fnordmetric.gauge_explorer.js +173 -0
  42. data/web/js/fnordmetric.js +93 -33
  43. data/web/js/fnordmetric.numbers_widget.js +15 -14
  44. data/web/js/fnordmetric.session_view.js +0 -1
  45. data/web/js/fnordmetric.timeline_widget.js +3 -3
  46. data/web/js/fnordmetric.timeseries_widget.js +46 -29
  47. data/web/js/fnordmetric.toplist_widget.js +23 -16
  48. data/web/js/fnordmetric.util.js +12 -8
  49. data/web/vendor/font-awesome/css/font-awesome-ie7.min.css +22 -0
  50. data/web/vendor/font-awesome/css/font-awesome.css +522 -221
  51. data/web/vendor/font-awesome/css/font-awesome.min.css +33 -0
  52. data/web/vendor/font-awesome/font/FontAwesome.otf +0 -0
  53. data/web/vendor/font-awesome/font/fontawesome-webfont.eot +0 -0
  54. data/web/vendor/font-awesome/font/fontawesome-webfont.svg +278 -169
  55. data/web/vendor/font-awesome/font/fontawesome-webfont.ttf +0 -0
  56. data/web/vendor/font-awesome/font/fontawesome-webfont.woff +0 -0
  57. data/web/vendor/jquery-ui.min.js +6 -413
  58. data/web/vendor/jquery.combobox.js +129 -0
  59. metadata +55 -48
  60. data/doc/V1.0-ROADMAP +0 -97
  61. data/doc/full_example.rb +0 -224
  62. data/doc/legacy_example.rb +0 -640
  63. data/doc/minimal_example.rb +0 -26
  64. data/doc/preview1.png +0 -0
  65. data/doc/preview2.png +0 -0
  66. data/doc/preview3.png +0 -0
  67. data/readme.md +0 -365
  68. data/web/vendor/d3.v2.js +0 -9382
  69. data/web/vendor/font-awesome/font/fontawesome-webfont.svgz +0 -0
  70. data/web/vendor/rickshaw.css +0 -286
  71. data/web/vendor/rickshaw.fnordmetric.js +0 -2700
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'rspec'
2
2
  require 'rspec/core/rake_task'
3
3
  desc "Run all examples"
4
- task RSpec::Core::RakeTask.new('spec')
4
+ task RSpec::Core::RakeTask.new('fnordmetric-core/spec')
5
5
 
6
6
  task :default => "spec"
data/fnordmetric.gemspec CHANGED
@@ -33,6 +33,7 @@ Gem::Specification.new do |s|
33
33
  s.add_development_dependency "shoulda"
34
34
 
35
35
  s.files = `git ls-files`.split("\n") - [".gitignore", ".rspec", ".travis.yml"]
36
+ s.files += ["web/fnordmetric-ui.js", "web/fnordmetric-ui.css", "web/fnordmetric-core.js", "web/fnordmetric-core.css"]
36
37
  s.test_files = `git ls-files -- spec/*`.split("\n")
37
38
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
38
39
  s.require_paths = ["lib"]
data/lib/fnordmetric.rb CHANGED
@@ -51,19 +51,7 @@ module FnordMetric
51
51
  end
52
52
 
53
53
  def self.default_options(opts = {})
54
- {
55
- :redis_url => "redis://localhost:6379",
56
- :redis_prefix => "fnordmetric",
57
- :inbound_stream => ["0.0.0.0", "1337"],
58
- :inbound_protocol => :tcp,
59
- :web_interface => ["0.0.0.0", "4242"],
60
- :web_interface_server => "thin",
61
- :start_worker => true,
62
- :print_stats => 3,
63
- :event_queue_ttl => 120,
64
- :event_data_ttl => 3600*24*30,
65
- :session_data_ttl => 3600*24*30
66
- }.merge(opts)
54
+ FnordMetric::DEFAULT_OPTIONS.merge(opts)
67
55
  end
68
56
 
69
57
  def self.log(msg)
@@ -123,6 +111,7 @@ require "fnordmetric/gauge_modifiers"
123
111
  require "fnordmetric/gauge_validations"
124
112
  require "fnordmetric/gauge_rendering"
125
113
  require "fnordmetric/gauge"
114
+ require "fnordmetric/zero_config_gauge"
126
115
  require "fnordmetric/gauges/timeseries_gauge"
127
116
  require "fnordmetric/gauges/toplist_gauge"
128
117
  require "fnordmetric/gauges/distribution_gauge"
@@ -136,6 +125,7 @@ require "fnordmetric/api"
136
125
  require "fnordmetric/worker"
137
126
  require "fnordmetric/logger"
138
127
  require "fnordmetric/defaults"
128
+ require "fnordmetric/util"
139
129
  require "fnordmetric/web/web"
140
130
  require "fnordmetric/web/app_helpers"
141
131
  require "fnordmetric/web/app"
@@ -146,6 +136,9 @@ require "fnordmetric/web/dashboard"
146
136
  require "fnordmetric/acceptors/acceptor"
147
137
  require "fnordmetric/acceptors/tcp_acceptor"
148
138
  require "fnordmetric/acceptors/udp_acceptor"
139
+ require "fnordmetric/acceptors/fyrehose_acceptor"
140
+ require "fnordmetric/acceptors/amqp_acceptor"
141
+ require "fnordmetric/acceptors/stomp_acceptor"
149
142
  require "fnordmetric/widget"
150
143
  require "fnordmetric/widgets/timeseries_widget"
151
144
  require "fnordmetric/widgets/numbers_widget"
@@ -6,11 +6,19 @@ class FnordMetric::Acceptor
6
6
  FnordMetric.register(self)
7
7
  end
8
8
 
9
- def initialized
10
- inbound_class = if @opts[:protocol] == :udp
9
+ def initialized
10
+ inbound_class = if @opts[:protocol] == :udp
11
11
  FnordMetric::UDPAcceptor
12
- else
12
+ elsif @opts[:protocol] == :tcp
13
13
  FnordMetric::TCPAcceptor
14
+ elsif @opts[:protocol] == :fyrehose
15
+ FnordMetric::FyrehoseAcceptor
16
+ elsif @opts[:protocol] == :amqp
17
+ FnordMetric::AMQPAcceptor
18
+ elsif @opts[:protocol] == :stomp
19
+ FnordMetric::STOMPAcceptor
20
+ else
21
+ raise "unknown protocol: #{@opts[:protocol]}"
14
22
  end
15
23
 
16
24
  @opts[:listen] = [
@@ -20,10 +28,15 @@ class FnordMetric::Acceptor
20
28
 
21
29
  begin
22
30
  inbound_stream = inbound_class.start(@opts)
23
- FnordMetric.log "listening on #{@opts[:protocol]}://#{@opts[:listen][0..1].join(":")}"
24
- #rescue
25
- # FnordMetric.log "cant start #{inbound_class.name}. port in use?"
31
+ if inbound_class.respond_to?(:outbound?) && inbound_class.outbound?
32
+ FnordMetric.log "connected to #{@opts[:protocol]}://#{@opts[:listen][0..1].join(":")}"
33
+ else
34
+ FnordMetric.log "listening on #{@opts[:protocol]}://#{@opts[:listen][0..1].join(":")}"
35
+ end
36
+ rescue Exception => e
37
+ raise e if ENV["FNORDMETRIC_ENV"] == "dev"
38
+ FnordMetric.log "cant start #{inbound_class.name} on #{@opts[:protocol]}://#{@opts[:listen][0..1].join(":")}. port in use?"
26
39
  end
27
40
  end
28
41
 
29
- end
42
+ end
@@ -0,0 +1,56 @@
1
+ class FnordMetric::AMQPAcceptor
2
+
3
+ def self.start(opts)
4
+ begin
5
+ require "amqp"
6
+ rescue LoadError
7
+ FnordMetric.error("require 'amqp' failed, you need the amqp gem")
8
+ exit 1
9
+ end
10
+
11
+ new(opts)
12
+ end
13
+
14
+ def initialize(opts)
15
+ amqp = AMQP.connect(:host => 'firehose')
16
+ amqp_channel = AMQP::Channel.new(amqp)
17
+
18
+ msg_handler = lambda do |channel, data|
19
+ event = begin
20
+ JSON.parse(data)
21
+ rescue
22
+ FnordMetric.log("[AMQP] received invalid JSON: #{data[0..60]}")
23
+ end
24
+
25
+ if event
26
+ event["_type"] ||= channel
27
+ events << event
28
+ push_next_event
29
+ end
30
+ end
31
+
32
+ opts[:channels].each do |channel|
33
+ queue = amqp_channel.queue(channel, :auto_delete => true)
34
+ queue.subscribe{ |data| msg_handler[channel, data] }
35
+ end
36
+ end
37
+
38
+ def push_next_event
39
+ return true if events.empty?
40
+ api.event(@events.pop)
41
+ EM.next_tick(&method(:push_next_event))
42
+ end
43
+
44
+ def events
45
+ @events ||= []
46
+ end
47
+
48
+ def api
49
+ @api ||= FnordMetric::API.new(FnordMetric.options)
50
+ end
51
+
52
+ def self.outbound?
53
+ true
54
+ end
55
+
56
+ end
@@ -0,0 +1,43 @@
1
+ class FnordMetric::FyrehoseAcceptor
2
+
3
+ def self.start(opts)
4
+ require "fyrehose"
5
+ require "fyrehose/reactor"
6
+
7
+ new(opts)
8
+ end
9
+
10
+ def initialize(opts)
11
+ reactor = EM.connect(opts[:host], opts[:port], Fyrehose::Reactor)
12
+
13
+ reactor.on_message do |channel, data|
14
+ event = JSON.parse(data)
15
+ event["_type"] ||= channel
16
+ events << event
17
+ push_next_event
18
+ end
19
+
20
+ opts[:channels].each do |channel|
21
+ reactor.subscribe(channel)
22
+ end
23
+ end
24
+
25
+ def push_next_event
26
+ return true if events.empty?
27
+ api.event(@events.pop)
28
+ EM.next_tick(&method(:push_next_event))
29
+ end
30
+
31
+ def events
32
+ @events ||= []
33
+ end
34
+
35
+ def api
36
+ @api ||= FnordMetric::API.new(FnordMetric.options)
37
+ end
38
+
39
+ def self.outboud?
40
+ true
41
+ end
42
+
43
+ end
@@ -0,0 +1,71 @@
1
+ class FnordMetric::STOMPAcceptor
2
+
3
+ def self.start(opts)
4
+ begin
5
+ require "stomp"
6
+ rescue LoadError
7
+ FnordMetric.error("require 'stomp' failed, you need the stomp gem")
8
+ exit 1
9
+ end
10
+
11
+ new(opts)
12
+ end
13
+
14
+ def initialize(opts)
15
+ @mutex = Mutex.new
16
+
17
+ client = Stomp::Client.new(:hosts => [{
18
+ :host => opts[:host],
19
+ :port => opts[:port],
20
+ :passcode => opts[:password],
21
+ :login => opts[:username]}])
22
+
23
+ msg_handler = lambda do |topic, msg|
24
+ data = msg.body
25
+
26
+ event = begin
27
+ JSON.parse(data)
28
+ rescue
29
+ FnordMetric.log("[STOMP] received invalid JSON: #{data[0..60]}")
30
+ end
31
+
32
+ if event
33
+ event["_type"] ||= topic.gsub(/^\/topic\//, '')
34
+ @mutex.synchronize{ events << event }
35
+ end
36
+ end
37
+
38
+ opts[:topics].each do |topic|
39
+ client.subscribe(topic){ |data| msg_handler[topic, data] }
40
+ end
41
+
42
+ Thread.new do
43
+ client.join
44
+ end
45
+
46
+ EM.next_tick(&method(:push_next_event))
47
+ end
48
+
49
+ def push_next_event
50
+ nxt = @mutex.synchronize{ events.pop }
51
+ unless nxt
52
+ EM::Timer.new(0.01, &method(:push_next_event))
53
+ return true
54
+ end
55
+ api.event(nxt)
56
+ EM.next_tick(&method(:push_next_event))
57
+ end
58
+
59
+ def events
60
+ @events ||= []
61
+ end
62
+
63
+ def api
64
+ @api ||= FnordMetric::API.new(FnordMetric.options)
65
+ end
66
+
67
+ def self.outbound?
68
+ true
69
+ end
70
+
71
+ end
@@ -54,4 +54,5 @@ class FnordMetric::TCPAcceptor < EventMachine::Connection
54
54
  def api
55
55
  @api ||= FnordMetric::API.new(FnordMetric.options)
56
56
  end
57
+
57
58
  end
@@ -33,4 +33,5 @@ class FnordMetric::UDPAcceptor < EventMachine::Connection
33
33
  def api
34
34
  @api ||= FnordMetric::API.new(FnordMetric.options)
35
35
  end
36
- end
36
+
37
+ end
@@ -16,12 +16,13 @@ class FnordMetric::Context
16
16
 
17
17
  def initialize(opts, block)
18
18
  @block = block
19
- @opts = opts
19
+ @opts = opts
20
20
  end
21
21
 
22
- def call(event, redis)
22
+ def call(event, redis, namespace)
23
23
  @redis = redis
24
- @event = event
24
+ @event = event
25
+ @namespace = namespace
25
26
  proxy.instance_eval(&@block)
26
27
  rescue Exception => e
27
28
  raise e if ENV['FNORDMETRIC_ENV'] == 'test'
@@ -33,9 +34,13 @@ class FnordMetric::Context
33
34
  @proxy ||= Proxy.new(self)
34
35
  end
35
36
 
37
+ def namespace
38
+ @namespace
39
+ end
40
+
36
41
  def dispatch(method, *args, &block)
37
- if args.size > 0 && @opts[:gauges][args[0]].try(:renderable?)
38
- @opts[:gauges][args.delete_at(0)].execute(method, *args.unshift(self), &block)
42
+ if args.size > 0 && gauges[args[0]].try(:renderable?)
43
+ gauges[args.delete_at(0)].execute(method, *args.unshift(self), &block)
39
44
  else
40
45
  send(method, *args, &block)
41
46
  end
@@ -56,7 +61,7 @@ private
56
61
  end
57
62
 
58
63
  def data
59
- @event
64
+ @event
60
65
  end
61
66
 
62
67
  def key(gauge)
@@ -67,10 +72,18 @@ private
67
72
  @event[:_time].to_i
68
73
  end
69
74
 
75
+ def type
76
+ @event[:_type].to_sym
77
+ end
78
+
79
+ def gauges
80
+ @namespace.gauges
81
+ end
82
+
70
83
  protected
71
-
84
+
72
85
  def fetch_gauge(_gauge)
73
- _gauge.is_a?(FnordMetric::Gauge) ? _gauge : @opts[:gauges].fetch(_gauge)
86
+ _gauge.is_a?(FnordMetric::Gauge) ? _gauge : gauges.fetch(_gauge)
74
87
  rescue
75
88
  error! "error: gauge '#{_gauge}' is undefined"
76
89
  end
@@ -81,4 +94,3 @@ protected
81
94
 
82
95
 
83
96
  end
84
-
@@ -1,9 +1,22 @@
1
1
  FnordMetric::COLORS = ["#4572a7", "#aa4643", "#89a54e", "#80699b", "#3d96ae", "#db843d"].reverse
2
2
 
3
+ FnordMetric::DEFAULT_PROC = lambda{ |arg| }
4
+
3
5
  FnordMetric::TICKS = lambda{ |tick, span| [tick, 60, 300, 1200, 3600, 86400]
4
6
  .select{ |t| (t >= tick) && ((span/t) > 5) }
5
7
  .uniq }
6
8
 
7
-
8
- FnordMetric::DEFAULT_PROC = lambda{ |arg| }
9
-
9
+ FnordMetric::DEFAULT_OPTIONS = {
10
+ :redis_url => "redis://localhost:6379",
11
+ :redis_prefix => "fnordmetric",
12
+ :inbound_stream => nil,
13
+ :inbound_protocol => nil,
14
+ :web_interface => ["0.0.0.0", "4242"],
15
+ :web_interface_server => "thin",
16
+ :start_worker => true,
17
+ :print_stats => 3,
18
+ :event_queue_ttl => 120,
19
+ :event_data_ttl => 3600*24*30,
20
+ :session_data_ttl => 3600*24*30,
21
+ :default_flush_interval => 10
22
+ }
@@ -11,7 +11,8 @@ class FnordMetric::Gauge
11
11
  end
12
12
 
13
13
  def tick
14
- (@opts[:tick] || @opts[:resolution] || 3600).to_i
14
+ (@opts[:tick] || @opts[:resolution] || @opts[:flush_interval] ||
15
+ FnordMetric.options[:default_flush_interval]).to_i
15
16
  end
16
17
 
17
18
  def retention
@@ -34,10 +35,14 @@ class FnordMetric::Gauge
34
35
  @opts[:group] || "Gauges"
35
36
  end
36
37
 
38
+ def unit
39
+ @opts[:unit]
40
+ end
41
+
37
42
  def key_nouns
38
43
  @opts[:key_nouns] || ["Key", "Keys"]
39
44
  end
40
-
45
+
41
46
  def key(_append=nil)
42
47
  [@opts[:key_prefix], "gauge", name, tick, _append].flatten.compact.join("-")
43
48
  end
@@ -89,5 +94,5 @@ class FnordMetric::Gauge
89
94
  def error!(msg)
90
95
  FnordMetric.error(msg)
91
96
  end
92
-
97
+
93
98
  end
@@ -52,12 +52,18 @@ module FnordMetric::GaugeCalculations
52
52
  block = @@avg_per_count_proc if average?
53
53
  #block = @@count_per_session_proc if unique?
54
54
  block = @@avg_per_session_proc if unique? && average?
55
-
56
- if block
55
+
56
+ calc = if block
57
57
  instance_exec(_v, _t, &block)
58
58
  else
59
59
  _v
60
60
  end
61
+
62
+ if calc && @opts[:scale_by]
63
+ calc = calc.to_f * @opts[:scale_by].to_f
64
+ end
65
+
66
+ calc
61
67
  end
62
68
 
63
69
  def field_values_at(time, opts={}, &block)
@@ -1,6 +1,7 @@
1
1
  module FnordMetric::GaugeModifiers
2
2
 
3
3
  def incr(gauge_name, value=1)
4
+ value = value.to_i
4
5
  gauge = fetch_gauge(gauge_name)
5
6
  assure_two_dimensional!(gauge)
6
7
  if gauge.unique?
@@ -13,7 +14,7 @@ module FnordMetric::GaugeModifiers
13
14
  end
14
15
 
15
16
  def incr_tick(gauge, value)
16
- if gauge.progressive?
17
+ if gauge.progressive?
17
18
  @redis.incrby(gauge.key(:head), value).callback do |head|
18
19
  @redis.hsetnx(gauge.key, gauge.tick_at(time), head).callback do |_new|
19
20
  @redis.hincrby(gauge.key, gauge.tick_at(time), value) unless _new
@@ -24,7 +25,7 @@ module FnordMetric::GaugeModifiers
24
25
  @redis.hincrby(gauge.key, gauge.tick_at(time), value)
25
26
  end
26
27
  end
27
- end
28
+ end
28
29
 
29
30
  def incr_uniq(gauge, value, field_name=nil)
30
31
  return false if session_key.blank?
@@ -58,7 +59,7 @@ module FnordMetric::GaugeModifiers
58
59
  @redis.zincrby(gauge.tick_key(time), value, field_name).callback do
59
60
  @redis.incrby(gauge.tick_key(time, :count), 1)
60
61
  end
61
- end
62
+ end
62
63
 
63
64
  def set_value(gauge_name, value)
64
65
  gauge = fetch_gauge(gauge_name)
@@ -140,4 +141,4 @@ module FnordMetric::GaugeModifiers
140
141
  end
141
142
  end
142
143
 
143
- end
144
+ end