librato-rails 0.9.0 → 0.10.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/CHANGELOG.md +14 -0
  2. data/FAQ.md +25 -0
  3. data/README.md +11 -2
  4. data/lib/librato/rails/configuration.rb +26 -29
  5. data/lib/librato/rails/railtie.rb +30 -5
  6. data/lib/librato/rails/subscribers/cache.rb +22 -0
  7. data/lib/librato/rails/subscribers/controller.rb +56 -0
  8. data/lib/librato/rails/subscribers/mail.rb +18 -0
  9. data/lib/librato/rails/subscribers/render.rb +28 -0
  10. data/lib/librato/rails/subscribers/sql.rb +24 -0
  11. data/lib/librato/rails/subscribers.rb +14 -69
  12. data/lib/librato/rails/tracker.rb +13 -0
  13. data/lib/librato/rails/version.rb +1 -1
  14. data/lib/librato/rails.rb +9 -214
  15. data/test/dummy/app/assets/javascripts/application.js +0 -3
  16. data/test/dummy/app/controllers/cache_controller.rb +44 -0
  17. data/test/dummy/app/controllers/render_controller.rb +4 -0
  18. data/test/dummy/app/models/user.rb +7 -5
  19. data/test/dummy/app/views/render/_first.html.erb +1 -0
  20. data/test/dummy/app/views/render/_second.html.erb +1 -0
  21. data/test/dummy/app/views/render/partial.html.erb +2 -0
  22. data/test/dummy/app/views/render/template.html.erb +1 -0
  23. data/test/dummy/config/application.rb +8 -6
  24. data/test/dummy/config/environments/test.rb +1 -1
  25. data/test/dummy/config/routes.rb +12 -3
  26. data/test/integration/cache_test.rb +40 -0
  27. data/test/integration/mail_test.rb +2 -4
  28. data/test/integration/render_test.rb +27 -0
  29. data/test/integration/request_test.rb +15 -11
  30. data/test/integration/sql_test.rb +6 -6
  31. data/test/support/integration_case.rb +11 -7
  32. data/test/unit/configuration_test.rb +63 -73
  33. data/test/unit/tracker_test.rb +15 -0
  34. metadata +36 -53
  35. data/lib/librato/rack/middleware.rb +0 -47
  36. data/lib/librato/rack.rb +0 -4
  37. data/lib/librato/rails/aggregator.rb +0 -95
  38. data/lib/librato/rails/collector.rb +0 -45
  39. data/lib/librato/rails/counter_cache.rb +0 -122
  40. data/lib/librato/rails/group.rb +0 -27
  41. data/lib/librato/rails/logging.rb +0 -77
  42. data/lib/librato/rails/validating_queue.rb +0 -31
  43. data/lib/librato/rails/worker.rb +0 -54
  44. data/lib/tasks/metrics-rails_tasks.rake +0 -4
  45. data/test/librato-rails_test.rb +0 -44
  46. data/test/remote/rails_remote_test.rb +0 -193
  47. data/test/unit/aggregator_test.rb +0 -53
  48. data/test/unit/counter_cache_test.rb +0 -90
  49. data/test/unit/group_test.rb +0 -54
  50. data/test/unit/middleware_test.rb +0 -82
  51. data/test/unit/worker_test.rb +0 -31
data/lib/librato/rails.rb CHANGED
@@ -1,215 +1,10 @@
1
- require 'socket'
2
- require 'thread'
3
-
4
- require 'active_support/core_ext/module/attribute_accessors'
5
- require 'active_support/notifications'
6
- require 'librato/metrics'
7
-
8
1
  require 'librato/rack'
9
- require 'librato/rails/aggregator'
10
- require 'librato/rails/collector'
11
- require 'librato/rails/configuration'
12
- require 'librato/rails/counter_cache'
13
- require 'librato/rails/group'
14
- require 'librato/rails/logging'
15
- require 'librato/rails/validating_queue'
16
- require 'librato/rails/version'
17
- require 'librato/rails/worker'
18
-
19
- module Librato
20
- extend SingleForwardable
21
- def_delegators Librato::Rails, :increment, :measure, :timing, :group
22
-
23
- module Rails
24
- extend SingleForwardable
25
- extend Librato::Rails::Configuration
26
- extend Librato::Rails::Logging
27
-
28
- FORKING_SERVERS = [:unicorn, :passenger]
29
- SOURCE_REGEX = /\A[-:A-Za-z0-9_.]{1,255}\z/
30
-
31
- # config options
32
- mattr_accessor :user
33
- mattr_accessor :token
34
- mattr_accessor :flush_interval
35
- mattr_accessor :source_pids
36
-
37
- # config defaults
38
- self.flush_interval = 60 # seconds
39
- self.source_pids = false # append process id to the source?
40
- # log_level (default :info)
41
- # source (default: your machine's hostname)
42
-
43
- # handy introspection
44
- mattr_accessor :explicit_source
45
-
46
- # a collector instance handles all measurement addition/storage
47
- def_delegators :collector, :aggregate, :counters, :delete_all, :group, :increment,
48
- :measure, :prefix, :prefix=, :timing
49
-
50
- class << self
51
-
52
- # check to see if we've forked into a process where a worker
53
- # isn't running yet, if so start it up!
54
- def check_worker
55
- if @pid != $$
56
- start_worker
57
- # aggregate.clear
58
- # counters.clear
59
- end
60
- end
61
-
62
- # access to client instance
63
- def client
64
- @client ||= prepare_client
65
- end
66
-
67
- # collector instance which is tracking all measurement additions
68
- def collector
69
- @collector ||= Collector.new
70
- end
71
-
72
- # send all current data to Metrics
73
- def flush
74
- log :debug, "flushing pid #{@pid} (#{Time.now}).."
75
- start = Time.now
76
- queue = flush_queue
77
- # thread safety is handled internally for both stores
78
- counters.flush_to(queue)
79
- aggregate.flush_to(queue)
80
- trace_queued(queue.queued) if should_log?(:trace)
81
- queue.submit unless queue.empty?
82
- log :trace, "flushed pid #{@pid} in #{(Time.now - start)*1000.to_f}ms"
83
- rescue Exception => error
84
- log :error, "submission failed permanently: #{error}"
85
- end
86
-
87
- # source including process pid
88
- def qualified_source
89
- self.source_pids ? "#{source}.#{$$}" : source
90
- end
91
-
92
- # run once during Rails startup sequence
93
- def setup(app)
94
- check_config
95
- trace_settings if should_log?(:debug)
96
- return unless should_start?
97
- if app_server == :other
98
- log :info, "starting up..."
99
- else
100
- log :info, "starting up with #{app_server}..."
101
- end
102
- @pid = $$
103
- app.middleware.use Librato::Rack::Middleware
104
- start_worker unless forking_server?
105
- end
106
-
107
- def source
108
- return @source if @source
109
- self.explicit_source = false
110
- @source = Socket.gethostname
111
- end
112
-
113
- # set a custom source
114
- def source=(src)
115
- self.explicit_source = true
116
- @source = src
117
- end
118
-
119
- # start the worker thread, one is needed per process.
120
- # if this process has been forked from an one with an active
121
- # worker thread we don't need to worry about cleanup as only
122
- # the forking thread is copied.
123
- def start_worker
124
- return if @worker # already running
125
- @pid = $$
126
- log :debug, ">> starting up worker for pid #{@pid}..."
127
- @worker = Thread.new do
128
- worker = Worker.new
129
- worker.run_periodically(self.flush_interval) do
130
- flush
131
- end
132
- end
133
- end
134
-
135
- private
136
-
137
- def app_server
138
- if defined?(::Unicorn) && defined?(::Unicorn::HttpServer) && !::Unicorn.listener_names.empty?
139
- :unicorn
140
- elsif defined?(::IN_PHUSION_PASSENGER) || defined?(::PhusionPassenger)
141
- :passenger
142
- elsif defined?(::Thin) && defined?(::Thin::Server)
143
- :thin
144
- else
145
- :other
146
- end
147
- end
148
-
149
- def flush_queue
150
- ValidatingQueue.new(
151
- :client => client,
152
- :source => qualified_source,
153
- :prefix => self.prefix,
154
- :skip_measurement_times => true )
155
- end
156
-
157
- def forking_server?
158
- FORKING_SERVERS.include?(app_server)
159
- end
160
-
161
- def on_heroku
162
- # would be nice to have something more specific here,
163
- # but nothing characteristic in ENV, etc.
164
- @on_heroku ||= source_is_uuid?(Socket.gethostname)
165
- end
166
-
167
- def prepare_client
168
- check_config
169
- client = Librato::Metrics::Client.new
170
- client.authenticate user, token
171
- client.api_endpoint = @api_endpoint if @api_endpoint
172
- client.custom_user_agent = user_agent
173
- client
174
- end
175
-
176
- def ruby_engine
177
- return RUBY_ENGINE if Object.constants.include?(:RUBY_ENGINE)
178
- RUBY_DESCRIPTION.split[0]
179
- end
180
-
181
- def should_start?
182
- if !self.user || !self.token
183
- # don't show this unless we're debugging, expected behavior
184
- log :debug, 'halting: credentials not present.'
185
- false
186
- elsif qualified_source !~ SOURCE_REGEX
187
- log :warn, "halting: '#{qualified_source}' is an invalid source name."
188
- false
189
- elsif !explicit_source && on_heroku
190
- log :warn, 'halting: source must be provided in configuration.'
191
- false
192
- else
193
- true
194
- end
195
- end
196
-
197
- def source_is_uuid?(source)
198
- source =~ /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i
199
- end
200
-
201
- def user_agent
202
- ua_chunks = []
203
- ua_chunks << "librato-rails/#{Librato::Rails::VERSION}"
204
- ua_chunks << "(#{ruby_engine}; #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}; #{RUBY_PLATFORM}; #{app_server})"
205
- ua_chunks.join(' ')
206
- end
207
-
208
- end # end class << self
209
-
210
- end
211
- end
212
-
213
- # must load after all module setup
214
- require 'librato/rails/railtie' if defined?(Rails)
215
- require 'librato/rails/subscribers'
2
+ require_relative 'rails/configuration'
3
+ require_relative 'rails/tracker'
4
+ require_relative 'rails/version'
5
+
6
+ # must load after all module setup and in this order
7
+ if defined?(Rails)
8
+ require_relative 'rails/railtie'
9
+ require_relative 'rails/subscribers'
10
+ end
@@ -10,6 +10,3 @@
10
10
  // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11
11
  // GO AFTER THE REQUIRES BELOW.
12
12
  //
13
- //= require jquery
14
- //= require jquery_ujs
15
- //= require_tree .
@@ -0,0 +1,44 @@
1
+ class CacheController < ApplicationController
2
+ before_filter :instrument_caching
3
+ after_filter :clear_cache
4
+
5
+ def read
6
+ Rails.cache.write('myfoo', 'bar', expires_in: 60.seconds)
7
+ Rails.cache.read('myfoo')
8
+ render :nothing => true
9
+ end
10
+
11
+ def write
12
+ Rails.cache.write('myfoo', 'bar', expires_in: 60.seconds)
13
+ render :nothing => true
14
+ end
15
+
16
+ def fetch_hit
17
+ Rails.cache.write('myfetch', 'bar', expires_in: 60.seconds)
18
+ Rails.cache.fetch('myfetch', expires_in: 60.seconds) { "populate" }
19
+ render :nothing => true
20
+ end
21
+
22
+ def generate
23
+ Rails.cache.fetch('newdata', expires_in: 60.seconds) { "populate" }
24
+ render :nothing => true
25
+ end
26
+
27
+ def delete
28
+ Rails.cache.write('something', 'bar', expires_in: 60.seconds)
29
+ Rails.cache.delete('something')
30
+ render :nothing => true
31
+ end
32
+
33
+ private
34
+
35
+ def clear_cache
36
+ Rails.cache.clear
37
+ end
38
+
39
+ def instrument_caching
40
+ # ensure caching instrumentation is turned on
41
+ Rails.cache.class.instrument = true
42
+ end
43
+
44
+ end
@@ -0,0 +1,4 @@
1
+ class RenderController < ApplicationController
2
+ def render_partial; end
3
+ def render_template; end
4
+ end
@@ -1,17 +1,19 @@
1
1
  class User < ActiveRecord::Base
2
- attr_accessible :email, :password
3
-
2
+ if Rails.version[0] == '3'
3
+ attr_accessible :email, :password
4
+ end
5
+
4
6
  def self.do_custom_events
5
7
  Librato.group 'custom.model' do |g|
6
8
  g.increment 'lookups', 3
7
-
9
+
8
10
  g.timing 'search', 12.3
9
11
  g.timing 'search', 6.7
10
-
12
+
11
13
  g.measure 'total', 12
12
14
  end
13
15
  end
14
-
16
+
15
17
  def touch
16
18
  Librato.increment 'custom.model.touch'
17
19
  end
@@ -0,0 +1 @@
1
+ first
@@ -0,0 +1 @@
1
+ second
@@ -0,0 +1,2 @@
1
+ <%= render :partial => 'first' %>
2
+ <%= render :partial => 'second' %>
@@ -0,0 +1 @@
1
+ Render that template
@@ -43,18 +43,20 @@ module Dummy
43
43
  # like if you have constraints or database-specific column types
44
44
  # config.active_record.schema_format = :sql
45
45
 
46
- # Enforce whitelist mode for mass assignment.
47
- # This will create an empty whitelist of attributes available for mass-assignment for all models
48
- # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
49
- # parameters by using an attr_accessible or attr_protected declaration.
50
- config.active_record.whitelist_attributes = true
46
+ if Rails.version[0] == '3'
47
+ # Enforce whitelist mode for mass assignment.
48
+ # This will create an empty whitelist of attributes available for mass-assignment for all models
49
+ # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
50
+ # parameters by using an attr_accessible or attr_protected declaration.
51
+ config.active_record.whitelist_attributes = true
52
+ end
51
53
 
52
54
  # Enable the asset pipeline
53
55
  config.assets.enabled = true
54
56
 
55
57
  # Version of your assets, change this if you want to expire all your assets
56
58
  config.assets.version = '1.0'
57
-
59
+
58
60
  # set librato_rails prefix
59
61
  # config.librato_rails.prefix = 'dummy'
60
62
  config.librato_rails.flush_interval = 5
@@ -30,7 +30,7 @@ Dummy::Application.configure do
30
30
  config.action_mailer.delivery_method = :test
31
31
 
32
32
  # Raise exception on mass assignment protection for Active Record models
33
- config.active_record.mass_assignment_sanitizer = :strict
33
+ # config.active_record.mass_assignment_sanitizer = :strict
34
34
 
35
35
  # Print deprecation notices to the stderr
36
36
  config.active_support.deprecation = :stderr
@@ -1,13 +1,22 @@
1
1
  Dummy::Application.routes.draw do
2
2
 
3
3
  get "status/:code" => 'status#index'
4
-
4
+
5
5
  get 'exception' => 'home#boom', :as => :exception
6
6
  get 'slow' => 'home#slow', :as => :slow
7
7
  get 'custom' => 'home#custom', :as => :custom
8
-
8
+
9
9
  get 'user/manipulation' => 'user#manipulation', :as => :user_manipulation
10
10
 
11
+ get 'cache/read' => 'cache#read', :as => :cache_read
12
+ get 'cache/write' => 'cache#write', :as => :cache_write
13
+ get 'cache/fetch_hit' => 'cache#fetch_hit', :as => :cache_fetch_hit
14
+ get 'cache/generate' => 'cache#generate', :as => :cache_generate
15
+ get 'cache/delete' => 'cache#delete', :as => :cache_delete
16
+
17
+ get 'render/partial' => 'render#partial', :as => :render_partial
18
+ get 'render/template' => 'render#template', :as => :render_template
19
+
11
20
  # The priority is based upon order of creation:
12
21
  # first created -> highest priority.
13
22
 
@@ -58,5 +67,5 @@ Dummy::Application.routes.draw do
58
67
  # You can have the root of your site routed with "root"
59
68
  # just remember to delete public/index.html.
60
69
  root :to => 'home#index'
61
-
70
+
62
71
  end
@@ -0,0 +1,40 @@
1
+ require 'test_helper'
2
+
3
+ class CacheTest < ActiveSupport::IntegrationCase
4
+
5
+ test 'cache read' do
6
+ visit cache_read_path
7
+
8
+ assert_equal 1, counters["rails.cache.read"]
9
+ assert_equal 1, aggregate["rails.cache.read.time"][:count]
10
+ end
11
+
12
+ test 'cache write' do
13
+ visit cache_write_path
14
+
15
+ assert_equal 1, counters["rails.cache.write"]
16
+ assert_equal 1, aggregate["rails.cache.write.time"][:count]
17
+ end
18
+
19
+ test 'cache fetch_hit' do
20
+ visit cache_fetch_hit_path
21
+
22
+ assert_equal 1, counters["rails.cache.fetch_hit"]
23
+ assert_equal 1, aggregate["rails.cache.fetch_hit.time"][:count]
24
+ end
25
+
26
+ test 'cache generate' do
27
+ visit cache_generate_path
28
+
29
+ assert_equal 1, counters["rails.cache.generate"]
30
+ assert_equal 1, aggregate["rails.cache.generate.time"][:count]
31
+ end
32
+
33
+ test 'cache delete' do
34
+ visit cache_delete_path
35
+
36
+ assert_equal 1, counters["rails.cache.delete"]
37
+ assert_equal 1, aggregate["rails.cache.delete.time"][:count]
38
+ end
39
+
40
+ end
@@ -1,13 +1,11 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class MailTest < ActiveSupport::IntegrationCase
4
-
5
- # Query tests - the numbers specified assume running against SQLite
6
-
4
+
7
5
  test 'mail sent' do
8
6
  user = User.create!(:email => 'foo@foo.com', :password => 'wow')
9
7
  UserMailer.welcome_email(user).deliver
10
8
  assert_equal 1, counters["rails.mail.sent"]
11
9
  end
12
-
10
+
13
11
  end
@@ -0,0 +1,27 @@
1
+ require 'test_helper'
2
+
3
+ class RenderTest < ActiveSupport::IntegrationCase
4
+
5
+ test 'render partial' do
6
+ visit render_partial_path
7
+
8
+ assert_equal 1, counters.fetch("rails.view.render_partial",
9
+ source: 'render:first.html.erb')
10
+ assert_equal 1, counters.fetch("rails.view.render_partial",
11
+ source: 'render:second.html.erb')
12
+ assert_equal 1, aggregate.fetch("rails.view.render_partial.time",
13
+ source: 'render:first.html.erb')[:count]
14
+ assert_equal 1, aggregate.fetch("rails.view.render_partial.time",
15
+ source: 'render:second.html.erb')[:count]
16
+ end
17
+
18
+ test 'render template' do
19
+ visit render_template_path
20
+
21
+ assert_equal 1, counters.fetch("rails.view.render_template",
22
+ source: 'render:template.html.erb')
23
+ assert_equal 1, aggregate.fetch("rails.view.render_template.time",
24
+ source: 'render:template.html.erb')[:count]
25
+ end
26
+
27
+ end
@@ -1,37 +1,41 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class RequestTest < ActiveSupport::IntegrationCase
4
-
4
+
5
5
  # Each request
6
-
6
+
7
7
  test 'increment total and status' do
8
8
  visit root_path
9
-
9
+
10
10
  assert_equal 1, counters["rails.request.total"]
11
11
  assert_equal 1, counters["rails.request.status.200"]
12
12
  assert_equal 1, counters["rails.request.status.2xx"]
13
-
13
+ assert_equal 1, counters["rails.request.method.get"]
14
+
14
15
  visit '/status/204'
15
-
16
+
16
17
  assert_equal 2, counters["rails.request.total"]
17
18
  assert_equal 1, counters["rails.request.status.200"]
18
19
  assert_equal 1, counters["rails.request.status.204"]
19
20
  assert_equal 2, counters["rails.request.status.2xx"]
20
21
  end
21
-
22
+
22
23
  test 'request times' do
23
24
  visit root_path
24
-
25
+
25
26
  # common for all paths
26
27
  assert_equal 1, aggregate["rails.request.time"][:count], 'should record total time'
27
28
  assert_equal 1, aggregate["rails.request.time.db"][:count], 'should record db time'
28
29
  assert_equal 1, aggregate["rails.request.time.view"][:count], 'should record view time'
29
-
30
+
30
31
  # status specific
31
32
  assert_equal 1, aggregate["rails.request.status.200.time"][:count]
32
33
  assert_equal 1, aggregate["rails.request.status.2xx.time"][:count]
34
+
35
+ # http method specific
36
+ assert_equal 1, aggregate["rails.request.method.get.time"][:count]
33
37
  end
34
-
38
+
35
39
  test 'track exceptions' do
36
40
  begin
37
41
  visit exception_path #rescue nil
@@ -40,10 +44,10 @@ class RequestTest < ActiveSupport::IntegrationCase
40
44
  end
41
45
  assert_equal 1, counters["rails.request.exceptions"]
42
46
  end
43
-
47
+
44
48
  test 'track slow requests' do
45
49
  visit slow_path
46
50
  assert_equal 1, counters["rails.request.slow"]
47
51
  end
48
-
52
+
49
53
  end
@@ -1,28 +1,28 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class SQLTest < ActiveSupport::IntegrationCase
4
-
4
+
5
5
  # Query tests - the numbers specified assume running against SQLite
6
-
6
+
7
7
  test 'total queries and query types' do
8
8
  # note that modifying queries are wrapped in a transaction which
9
9
  # adds 2 to total queries per operation.
10
10
  user = User.create!(:email => 'foo@foo.com', :password => 'wow')
11
11
  assert_equal 3, counters["rails.sql.queries"]
12
12
  assert_equal 1, counters["rails.sql.inserts"]
13
-
13
+
14
14
  foo = User.find_by_email('foo@foo.com')
15
15
  assert_equal 4, counters["rails.sql.queries"]
16
16
  assert_equal 1, counters["rails.sql.selects"]
17
-
17
+
18
18
  foo.password = 'new password'
19
19
  foo.save
20
20
  assert_equal 7, counters["rails.sql.queries"]
21
21
  assert_equal 1, counters["rails.sql.updates"]
22
-
22
+
23
23
  foo.destroy
24
24
  assert_equal 10, counters["rails.sql.queries"]
25
25
  assert_equal 1, counters["rails.sql.deletes"]
26
26
  end
27
-
27
+
28
28
  end
@@ -1,19 +1,23 @@
1
1
  class ActiveSupport::IntegrationCase < ActiveSupport::TestCase
2
2
  include Capybara::DSL
3
3
  include Rails.application.routes.url_helpers
4
-
4
+
5
5
  setup do
6
6
  # remove any accumulated metrics
7
- Librato::Rails.delete_all
7
+ collector.delete_all
8
8
  end
9
-
9
+
10
10
  private
11
-
11
+
12
12
  def aggregate
13
- Librato::Rails.aggregate
13
+ collector.aggregate
14
14
  end
15
-
15
+
16
+ def collector
17
+ Librato.tracker.collector
18
+ end
19
+
16
20
  def counters
17
- Librato::Rails.counters
21
+ collector.counters
18
22
  end
19
23
  end