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 +8 -8
- data/Gemfile +6 -0
- data/README.rdoc +22 -13
- data/Rakefile +14 -0
- data/lib/time_bandits.rb +3 -1
- data/lib/time_bandits/monkey_patches/action_controller.rb +17 -4
- data/lib/time_bandits/monkey_patches/memcache-client.rb +10 -35
- data/lib/time_bandits/monkey_patches/memcached.rb +13 -54
- data/lib/time_bandits/rack/logger.rb +1 -1
- data/lib/time_bandits/rack/logger40.rb +1 -1
- data/lib/time_bandits/railtie.rb +12 -7
- data/lib/time_bandits/time_consumers/base_consumer.rb +57 -0
- data/lib/time_bandits/time_consumers/dalli.rb +51 -0
- data/lib/time_bandits/time_consumers/database.rb +10 -30
- data/lib/time_bandits/time_consumers/garbage_collection.rb +7 -11
- data/lib/time_bandits/time_consumers/mem_cache.rb +12 -15
- data/lib/time_bandits/time_consumers/memcached.rb +19 -14
- data/lib/time_bandits/version.rb +1 -1
- data/test/test_helper.rb +18 -0
- data/test/unit/active_support_notifications_test.rb +64 -0
- data/test/unit/base_test.rb +63 -0
- data/test/unit/memcached_test.rb +60 -0
- data/time_bandits.gemspec +2 -0
- metadata +41 -7
- data/init.rb +0 -12
- data/lib/time_bandits/monkey_patches/action_controller_rails2.rb +0 -162
- data/lib/time_bandits/monkey_patches/active_record_rails2.rb +0 -86
- data/lib/time_bandits/time_consumers/database_rails2.rb +0 -61
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NWE3YzllNzNjMGYzNmZiYWFlNTc4ODNjNzE2NTc4MTJiNDM2ODFlMA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MDRiZmU5NDZjZjkzNWVlMTg4NDVhNjk1MWRlZTAyMjg0NGI4NDNhYg==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MGFjMjNkNWNjOGY4NjA5YTM0MTZmMmQwMzIwZDU2MzEyNjk4Y2QzN2EzM2Qy
|
10
|
+
ZjA3ZmE1NDZkN2Y5ODM0NDViNTMzNzc1M2NmZTM4Nzk2ZmQ2YmRlZTg5MGEy
|
11
|
+
MmJmNGUyZWZhMDAzMWMzNmNlMjIwNjg1ZDNiMTlkMTc3OTk0OGU=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YzgzYjM2ZjMxMTUyMTNlOTJhMDY3MjBjMmZkY2E4OTExMGQ3Nzg0NjUyYWRk
|
14
|
+
MTdhM2UzNzY3MmE1YWMxYTk0MmYxNTc2YWM2NTQ1ZjBhNDQyYTg0MDExYjg5
|
15
|
+
MmE0OWUxZjE2OGNjMWRmZTRkYTg1NDljMTFmMWVhNmQzYjcwZjQ=
|
data/Gemfile
CHANGED
data/README.rdoc
CHANGED
@@ -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
|
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
|
64
|
-
|
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
|
-
|
68
|
-
|
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
|
71
|
-
|
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
|
-
|
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
|
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
|
data/lib/time_bandits.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
68
|
-
|
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" %
|
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" %
|
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
|
data/lib/time_bandits/railtie.rb
CHANGED
@@ -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
|