fnordmetric 1.0.1 → 1.2.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.
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