time_bandits 0.4.1 → 0.5.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.
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