time_bandits 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MmFhZDNkN2U1OTY3NDExMWE1YTU3M2QxNDEyNWU3NGY3YmI0OGE4ZA==
4
+ NWE3YzllNzNjMGYzNmZiYWFlNTc4ODNjNzE2NTc4MTJiNDM2ODFlMA==
5
5
  data.tar.gz: !binary |-
6
- YjM2MjQ2YTdmMDQwNGJkNzQ0M2VmMDExY2FiN2U4NzI2MjBjNTdmOQ==
6
+ MDRiZmU5NDZjZjkzNWVlMTg4NDVhNjk1MWRlZTAyMjg0NGI4NDNhYg==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- NDE1Mjc2NWZhMjgxZWYzMDc2NTBlNTcxNTBjNTYyNWZiN2ZhM2MyYTVmYjYy
10
- OTU0MzkyMjRlYTY0YTU0YjQzZTMxZjdjNDY2NDAzNDBiOWU0YzgxMDE0OTY5
11
- ZWRjYzY1N2NlMmUwOGY1YzJmMGViYjhkYmY1NGM2ZDE5ZDdlMDg=
9
+ MGFjMjNkNWNjOGY4NjA5YTM0MTZmMmQwMzIwZDU2MzEyNjk4Y2QzN2EzM2Qy
10
+ ZjA3ZmE1NDZkN2Y5ODM0NDViNTMzNzc1M2NmZTM4Nzk2ZmQ2YmRlZTg5MGEy
11
+ MmJmNGUyZWZhMDAzMWMzNmNlMjIwNjg1ZDNiMTlkMTc3OTk0OGU=
12
12
  data.tar.gz: !binary |-
13
- MzlkMjA5MmM0MzJkYTNmOGFiYmJiNDRkMWFkNmRjMTU5NDBjOWQ5MGUzMzU3
14
- ZjU1YjE4YTA2MDhmODYwOWE3Y2FiZmY2YzEzMWQ1MGIwZGU2ODVlYWNkZDc3
15
- N2ExOWNjZWJmZDM2MzExYTRkYzBkYmI1NDcxMWNmNmYwOTA5MjQ=
13
+ YzgzYjM2ZjMxMTUyMTNlOTJhMDY3MjBjMmZkY2E4OTExMGQ3Nzg0NjUyYWRk
14
+ MTdhM2UzNzY3MmE1YWMxYTk0MmYxNTc2YWM2NTQ1ZjBhNDQyYTg0MDExYjg5
15
+ MmE0OWUxZjE2OGNjMWRmZTRkYTg1NDljMTFmMWVhNmQzYjcwZjQ=
data/Gemfile CHANGED
@@ -2,3 +2,9 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in time_bandits.gemspec
4
4
  gemspec
5
+
6
+ # gem "activesupport", "3.2.13"
7
+ gem "activesupport", "4.0.0"
8
+
9
+ gem "debugger"
10
+ gem "memcached"
@@ -55,33 +55,42 @@ With these two new time consumers, the log line changes to
55
55
 
56
56
  Sidenote for Germans: you can use the word "Gesabbel" (eng: drivel) as a mnemonic here ;-)
57
57
 
58
- It's pretty easy to write additional time consumers; please refer to the source code.
58
+ It's realtively straightforward to write additional time consumers; the more difficult part of this is
59
+ monkey patching the code which you want to instrument. Have a look at consumers under
60
+ lib/time_bandits/time_consumers and the corresponding patches under lib/time_bandits/monkey_patches.
59
61
 
60
62
 
61
63
  == Prerequisites
62
64
 
63
- Rails 3.x is required. The plugin will raise an error if you try to use it with a
64
- different version.
65
+ Rails >= 3.x is required. The gem will raise an error if you try to use it with an incompatible
66
+ version.
65
67
 
66
- You'll need a ruby with the railsexpress GC patches applied, if you want to include GC and
67
- heap size information in the completed line. This is very useful, especially if you want
68
- to analyze your rails logs using logjam (see http://github.com/alpinegizmo/logjam/).
68
+ You'll need a ruby with the railsexpress GC patches applied, if you want to include GC and heap size
69
+ information in the completed line. This is very useful, especially if you want to analyze your rails
70
+ logs using logjam (see http://github.com/skaes/logjam/).
69
71
 
70
- Ruby Enterprise Edition contains a subset of the railsexpress patches. To get the full
71
- monty, you can use for example rvm and the railsexpress rvm patchsets (see
72
- https://github.com/skaes/rvm-patchsets).
72
+ Ruby only contains a subset of the railsexpress patches. To get the full monty, you can use for example
73
+ rvm and the railsexpress rvm patchsets (see https://github.com/skaes/rvm-patchsets).
73
74
 
74
75
 
75
76
  == History
76
77
 
77
- This plugin started from the code of the 'custom_benchmark' plugin written by
78
- tylerkovacs. However, we changed so much of the code that is is practically a full
79
- rewrite, hence we changed the name.
78
+ This plugin started from the code of the 'custom_benchmark' plugin written by tylerkovacs. However, we
79
+ changed so much of the code that is is practically a full rewrite, hence we changed the name.
80
80
 
81
+ == Release Notes
82
+
83
+ version 0.5:
84
+ - has dropped rails 2 support
85
+ - relies on ActiveSupport::Notifications
86
+ - is suposedly thread safe
87
+ - all measurements are thread local (except GC statistics)
88
+ - times are all measured in milliseconds internally
89
+ - added class TimeBandits::TimeConsumers::BaseConsumer to simplify writing custom consumers
81
90
 
82
91
  == License
83
92
 
84
- Copyright (c) 2009, 2011 Stefan Kaes <skaes@railsexpress.de>
93
+ Copyright (c) 2009-2012 Stefan Kaes <skaes@railsexpress.de>
85
94
 
86
95
  Permission is hereby granted, free of charge, to any person obtaining
87
96
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -1,2 +1,16 @@
1
1
  require 'bundler'
2
2
  Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+ include Rake::DSL
6
+
7
+ $:.unshift 'lib'
8
+ require 'time_bandits'
9
+
10
+ task :default => :test
11
+
12
+ Rake::TestTask.new do |t|
13
+ t.libs << "test"
14
+ t.test_files = FileList['test/**/*_test.rb']
15
+ t.verbose = true
16
+ end
@@ -13,9 +13,11 @@ module TimeBandits
13
13
  autoload :JMX, 'time_bandits/time_consumers/jmx'
14
14
  autoload :MemCache, 'time_bandits/time_consumers/mem_cache'
15
15
  autoload :Memcached, 'time_bandits/time_consumers/memcached'
16
+ autoload :Dalli, 'time_bandits/time_consumers/dalli'
16
17
  end
17
18
 
18
19
  require 'time_bandits/railtie' if defined?(Rails) && Rails::VERSION::STRING >= "3.0"
20
+ require 'time_bandits/time_consumers/base_consumer'
19
21
 
20
22
  mattr_accessor :time_bandits
21
23
  self.time_bandits = []
@@ -33,7 +35,7 @@ module TimeBandits
33
35
  end
34
36
 
35
37
  def self.runtime
36
- time_bandits.map{|b| b.runtime}.join(", ")
38
+ time_bandits.map{|b| b.runtime}.join("| ")
37
39
  end
38
40
 
39
41
  def self.metrics
@@ -8,7 +8,6 @@ module ActionController #:nodoc:
8
8
  # patch to ensure that the completed line is always written to the log.
9
9
  # this is not necessary anymore with rails 4.
10
10
  def process_action(action, *args)
11
-
12
11
  raw_payload = get_raw_payload
13
12
  ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
14
13
 
@@ -48,8 +47,7 @@ module ActionController #:nodoc:
48
47
  consumed_before_rendering = TimeBandits.consumed
49
48
  runtime = yield
50
49
  consumed_during_rendering = TimeBandits.consumed - consumed_before_rendering
51
- # TODO: time bandits all measure in seconds a.t.m.; this should be changed
52
- runtime - consumed_during_rendering*1000
50
+ runtime - consumed_during_rendering
53
51
  end
54
52
 
55
53
  private
@@ -92,14 +90,29 @@ module ActionController #:nodoc:
92
90
 
93
91
  class LogSubscriber
94
92
  # the original method logs the completed line.
95
- # but we do it in the middleware.
93
+ # but we do it in the middleware, unless we're in test mode. don't ask.
96
94
  def process_action(event)
97
95
  payload = event.payload
98
96
  additions = ActionController::Base.log_process_action(payload)
97
+
99
98
  Thread.current.thread_variable_set(
100
99
  :time_bandits_completed_info,
101
100
  [ event.duration, additions, payload[:view_runtime], "#{payload[:controller]}##{payload[:action]}" ]
102
101
  )
102
+
103
+ # this an ugly hack to encure completed lines show up in the test logs
104
+ # TODO: move this code to some other place
105
+ return unless Rails.env.test? && Rails::VERSION::STRING >= "3.2"
106
+
107
+ status = payload[:status]
108
+ if status.nil? && payload[:exception].present?
109
+ exception_class_name = payload[:exception].first
110
+ status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
111
+ end
112
+ message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.1fms" % event.duration
113
+ message << " (#{additions.join(" | ")})" unless additions.blank?
114
+
115
+ info(message)
103
116
  end
104
117
  end
105
118
 
@@ -6,48 +6,23 @@ require 'memcache'
6
6
  raise "MemCache needs to be loaded before monkey patching it" unless defined?(MemCache)
7
7
 
8
8
  class MemCache
9
- @@cache_latency = 0.0
10
- @@cache_touches = 0
11
- @@cache_misses = 0
12
-
13
- def self.reset_benchmarks
14
- @@cache_latency = 0.0
15
- @@cache_touches = 0
16
- @@cache_misses = 0
17
- end
18
-
19
- def self.get_benchmarks
20
- [@@cache_latency, @@cache_touches, @@cache_misses]
21
- end
22
-
23
- def self.cache_runtime
24
- sprintf "MC: %.3f(%dr,%dm)", @@cache_latency * 1000, @@cache_touches, @@cache_misses
25
- end
26
-
27
- def self.metrics
28
- {
29
- :memcache_time => @@cache_latency * 1000,
30
- :memcache_calls => @@cache_touches,
31
- :memcache_misses => @@cache_misses
32
- }
33
- end
34
9
 
35
10
  def get_with_benchmark(key, raw = false)
36
- val = nil
37
- @@cache_latency += Benchmark.realtime{ val=get_without_benchmark(key, raw) }
38
- @@cache_touches += 1
39
- @@cache_misses += 1 if val.nil?
40
- val
11
+ ActiveSupport::Notifications.instrument("get.memcache") do |payload|
12
+ val = get_without_benchmark(key, raw)
13
+ payload[:misses] = val.nil? ? 1 : 0
14
+ val
15
+ end
41
16
  end
42
17
  alias_method :get_without_benchmark, :get
43
18
  alias_method :get, :get_with_benchmark
44
19
 
45
20
  def get_multi_with_benchmark(*keys)
46
- results = nil
47
- @@cache_latency += Benchmark.realtime{ results=get_multi_without_benchmark(*keys) }
48
- @@cache_touches += 1
49
- @@cache_misses += keys.size - results.size
50
- results
21
+ ActiveSupport::Notifications.instrument("get.memcache") do |payload|
22
+ results = get_multi_without_benchmark(*keys)
23
+ payload[:misses] = keys.size - results.size
24
+ results
25
+ end
51
26
  end
52
27
  alias_method :get_multi_without_benchmark, :get_multi
53
28
  alias_method :get_multi, :get_multi_with_benchmark
@@ -6,79 +6,38 @@ require 'memcached'
6
6
  raise "Memcached needs to be loaded before monkey patching it" unless defined?(Memcached)
7
7
 
8
8
  class Memcached
9
-
10
- def self.reset_benchmarks
11
- @@cache_latency = 0.0
12
- @@cache_calls = 0
13
- @@cache_misses = 0
14
- @@cache_reads = 0
15
- @@cache_writes = 0
16
- end
17
- self.reset_benchmarks
18
-
19
- def self.get_benchmarks
20
- [@@cache_latency, @@cache_calls, @@cache_reads, @@cache_misses, @@cache_writes]
21
- end
22
-
23
- def self.cache_runtime
24
- sprintf "MC: %.3f(%dr,%dm,%dw,%dc)", @@cache_latency * 1000, @@cache_reads, @@cache_misses, @@cache_writes, @@cache_calls
25
- end
26
-
27
- def self.metrics
28
- {
29
- :memcache_time => @@cache_latency * 1000,
30
- :memcache_calls => @@cache_calls,
31
- :memcache_misses => @@cache_misses,
32
- :memcache_reads => @@cache_reads,
33
- :memcache_writes => @@cache_writes
34
- }
35
- end
36
-
37
9
  def get_with_benchmark(key, marshal = true)
38
- @@cache_calls += 1
39
- if key.is_a?(Array)
40
- @@cache_reads += (num_keys = key.size)
41
- results = []
42
- @@cache_latency += Benchmark.realtime do
10
+ ActiveSupport::Notifications.instrument("get.memcached") do |payload|
11
+ if key.is_a?(Array)
12
+ payload[:reads] = (num_keys = key.size)
13
+ results = []
43
14
  begin
44
15
  results = get_without_benchmark(key, marshal)
45
16
  rescue Memcached::NotFound
46
17
  end
47
- end
48
- @@cache_misses += num_keys - results.size
49
- results
50
- else
51
- val = nil
52
- @@cache_reads += 1
53
- @@cache_latency += Benchmark.realtime do
18
+ payload[:misses] = num_keys - results.size
19
+ results
20
+ else
21
+ val = nil
22
+ payload[:reads] = 1
54
23
  begin
55
24
  val = get_without_benchmark(key, marshal)
56
25
  rescue Memcached::NotFound
57
26
  end
27
+ payload[:misses] = val.nil? ? 1 : 0
28
+ val
58
29
  end
59
- @@cache_misses += 1 if val.nil?
60
- val
61
30
  end
62
31
  end
63
32
  alias_method :get_without_benchmark, :get
64
33
  alias_method :get, :get_with_benchmark
65
34
 
66
35
  def set_with_benchmark(*args)
67
- @@cache_calls += 1
68
- @@cache_writes += 1
69
- result = nil
70
- exception = nil
71
- @@cache_latency += Benchmark.realtime do
72
- begin
73
- set_without_benchmark(*args)
74
- rescue Exception => exception
75
- end
36
+ ActiveSupport::Notifications.instrument("set.memcached") do
37
+ set_without_benchmark(*args)
76
38
  end
77
- raise exception if exception
78
- result
79
39
  end
80
40
  alias_method :set_without_benchmark, :set
81
41
  alias_method :set, :set_with_benchmark
82
42
 
83
43
  end
84
-
@@ -55,7 +55,7 @@ module TimeBandits
55
55
  completed_info = Thread.current.thread_variable_get(:time_bandits_completed_info)
56
56
  additions = completed_info[1] if completed_info
57
57
 
58
- message = "Completed #{status} #{::Rack::Utils::HTTP_STATUS_CODES[status]} in %.1fms" % (run_time*1000)
58
+ message = "Completed #{status} #{::Rack::Utils::HTTP_STATUS_CODES[status]} in %.1fms" % run_time
59
59
  message << " (#{additions.join(' | ')})" unless additions.blank?
60
60
  info message
61
61
  ensure
@@ -77,7 +77,7 @@ module TimeBandits
77
77
  status = resp ? resp.first.to_i : 500
78
78
  completed_info = Thread.current.thread_variable_get(:time_bandits_completed_info)
79
79
  additions = completed_info[1] if completed_info
80
- message = "Completed #{status} #{::Rack::Utils::HTTP_STATUS_CODES[status]} in %.1fms" % (run_time*1000)
80
+ message = "Completed #{status} #{::Rack::Utils::HTTP_STATUS_CODES[status]} in %.1fms" % run_time
81
81
  message << " (#{additions.join(' | ')})" unless additions.blank?
82
82
  logger.info message
83
83
  end
@@ -13,16 +13,21 @@ module TimeBandits
13
13
  initializer "time_bandits" do |app|
14
14
  app.config.middleware.swap("Rails::Rack::Logger", "TimeBandits::Rack::Logger")
15
15
 
16
- # rails 4 inserts Rack::Lock in development, but not in production.
17
- # time bandits are not thread safe yet, so we insert the Rack::Lock middleware in production.
18
- # TODO: make time_bandits thread safe
19
- if Rails::VERSION::STRING >= "4.0" && Rails.env.production?
20
- app.config.middleware.insert_before("TimeBandits::Rack::Logger", "Rack::Lock")
21
- end
22
-
23
16
  ActiveSupport.on_load(:action_controller) do
24
17
  require 'time_bandits/monkey_patches/action_controller'
25
18
  include ActionController::TimeBanditry
19
+
20
+ # make sure TimeBandits.reset is called in test environment as middlewares are not executed
21
+ if Rails.env.test?
22
+ require 'action_controller/test_case'
23
+ module ActionController::TestCase::Behavior
24
+ def process_with_time_bandits(*args)
25
+ TimeBandits.reset
26
+ process_without_time_bandits(*args)
27
+ end
28
+ alias_method_chain :process, :time_bandits
29
+ end
30
+ end
26
31
  end
27
32
 
28
33
  ActiveSupport.on_load(:active_record) do
@@ -0,0 +1,57 @@
1
+ module TimeBandits::TimeConsumers
2
+ class BaseConsumer
3
+ class << self
4
+ def instance
5
+ Thread.current.thread_variable_get(name) ||
6
+ Thread.current.thread_variable_set(name, new)
7
+ end
8
+
9
+ def prefix(sym)
10
+ @metrics_prefix = sym
11
+ end
12
+
13
+ # first symbol is used as time measurement
14
+ def fields(*symbols)
15
+ @struct = Struct.new(*(symbols.map{|s| "#{@metrics_prefix}_#{s}".to_sym}))
16
+ symbols.each do |name|
17
+ class_eval(<<-"EVA", __FILE__, __LINE__ + 1)
18
+ def #{name}; @counters.#{@metrics_prefix}_#{name}; end
19
+ def #{name}=(v); @counters.#{@metrics_prefix}_#{name} = v; end
20
+ EVA
21
+ end
22
+ end
23
+
24
+ def format(f, *keys)
25
+ @runtime_format = f
26
+ @runtime_keys = keys.map{|s| "#{@metrics_prefix}_#{s}".to_sym}
27
+ end
28
+
29
+ attr_reader :metrics_prefix, :struct, :timer_name, :runtime_format, :runtime_keys
30
+
31
+ def method_missing(m, *args)
32
+ (i = instance).respond_to?(m) ? i.send(m,*args) : super
33
+ end
34
+ end
35
+
36
+ def initialize
37
+ @counters = self.class.struct.new
38
+ reset
39
+ end
40
+
41
+ def reset
42
+ @counters.length.times{|i| @counters[i] = 0}
43
+ end
44
+
45
+ def metrics
46
+ @counters.members.each_with_object({}){|m,h| h[m] = @counters.send(m)}
47
+ end
48
+
49
+ def consumed
50
+ @counters[0]
51
+ end
52
+
53
+ def runtime
54
+ self.class.runtime_format % metrics.values_at(*self.class.runtime_keys)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,51 @@
1
+ module TimeBandits::TimeConsumers
2
+ class Dalli < BaseConsumer
3
+ prefix :memcache
4
+ fields :time, :calls, :misses, :reads, :writes
5
+ format "DALLI: %.3f(%dr,%dm,%dw,%dc)", :time, :reads, :misses, :writes, :calls
6
+
7
+ class Subscriber < ActiveSupport::LogSubscriber
8
+ # cache events are: read write fetch_hit generate delete read_multi increment decrement clear
9
+ def cache_read(event)
10
+ i = cache(event)
11
+ i.reads += 1
12
+ i.misses += 1 unless event.payload[:hit]
13
+ end
14
+
15
+ def cache_read_multi(event)
16
+ i = cache(event)
17
+ i.reads += event.payload[:key].size
18
+ end
19
+
20
+ def cache_write(event)
21
+ i = cache(event)
22
+ i.writes += 1
23
+ end
24
+
25
+ def cache_increment(event)
26
+ i = cache(event)
27
+ i.writes += 1
28
+ end
29
+
30
+ def cache_decrement(event)
31
+ i = cache(event)
32
+ i.writes += 1
33
+ end
34
+
35
+ def cache_delete(event)
36
+ i = cache(event)
37
+ i.writes += 1
38
+ end
39
+
40
+ private
41
+ def cache(event)
42
+ i = Dalli.instance
43
+ i.time += event.duration
44
+ i.calls += 1
45
+ i
46
+ end
47
+ end
48
+ Subscriber.attach_to :active_support
49
+ end
50
+
51
+ end