time_bandits 0.0.4 → 0.0.5
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.
- data/README.rdoc +27 -19
- data/TODO +3 -0
- data/lib/time_bandits/monkey_patches/action_controller.rb +8 -0
- data/lib/time_bandits/monkey_patches/active_record.rb +76 -0
- data/lib/time_bandits/railtie.rb +2 -2
- data/lib/time_bandits/time_consumers/database.rb +21 -26
- data/lib/time_bandits/version.rb +1 -1
- metadata +7 -6
- data/lib/time_bandits/monkey_patches/activerecord_adapter.rb +0 -85
data/README.rdoc
CHANGED
@@ -9,13 +9,13 @@ Time Bandits is a plugin which enhances Rails' controller/view/db benchmark logg
|
|
9
9
|
Without configuration, the standard Rails 'Completed line' will change
|
10
10
|
from its default format
|
11
11
|
|
12
|
-
Completed in 56ms (
|
12
|
+
Completed 200 OK in 56ms (Views: 28.5ms, ActiveRecord: 5.1ms)
|
13
13
|
|
14
14
|
to:
|
15
15
|
|
16
|
-
Completed in 56.278ms (
|
16
|
+
Completed 200 OK in 56.278ms (Views: 28.488ms, ActiveRecord: 5.111ms(2q,0h))
|
17
17
|
|
18
|
-
|
18
|
+
"ActiveRecord: 5.111ms(2q,0h)" means that 2 SQL queries were executed and there were 0 SQL query cache hits.
|
19
19
|
|
20
20
|
However, non-trivial applications also rather often use external services, which consume time that adds
|
21
21
|
to your total response time, and sometimes these external services are not under your control. In these
|
@@ -30,50 +30,58 @@ Example:
|
|
30
30
|
TimeBandits.add TimeBandits::TimeConsumers::Memcached
|
31
31
|
TimeBandits.add TimeBandits::TimeConsumers::GarbageCollection.instance if GC.respond_to? :enable_stats
|
32
32
|
|
33
|
-
Here we've added two additional consumers, which are already provided with the
|
34
|
-
information requires a patched ruby,
|
35
|
-
|
33
|
+
Here we've added two additional consumers, which are already provided with the
|
34
|
+
plugin. (Note that GC information requires a patched ruby, see prerequistes below.)
|
35
|
+
|
36
|
+
Note: if you run a multithreaded program, the numbers reported for garbage collections and
|
37
|
+
heap usage are partially misleading, because the Ruby interpreter collects stats in global
|
38
|
+
variables shared by all threads.
|
36
39
|
|
37
40
|
With these two new time consumers, the log line changes to
|
38
41
|
|
39
|
-
Completed in 680.378ms (
|
42
|
+
Completed 200 OK in 680.378ms (Views: 28.488ms, ActiveRecord: 5.111ms(2q,0h), MC: 5.382(6r,0m), GC: 120.100(1), HP: 0(2000000,546468,18682541,934967))
|
40
43
|
|
41
44
|
"MC: 5.382(6r,0m)" means that 6 memcache reads were performed and all keys were found in the cache (0 misses).
|
42
45
|
|
43
46
|
"GC: 120.100(1)" tells us that 1 garbage collection was triggered during the request, taking 120.100 milliseconds.
|
44
47
|
|
45
|
-
"HP: 0(2000000,546468,18682541,934967)" shows statistics on heap usage. The format is g(s,a,
|
48
|
+
"HP: 0(2000000,546468,18682541,934967)" shows statistics on heap usage. The format is g(s,a,b,l), where
|
46
49
|
|
47
50
|
g: heap growth during the request (#slots)
|
48
51
|
s: size of the heap after request processing was completed (#slots)
|
49
52
|
a: number of object allocations during the request (#slots)
|
50
|
-
|
53
|
+
b: number of bytes allocated by the ruby x_malloc call (#bytes)
|
51
54
|
l: live data set size after last GC (#slots)
|
52
55
|
|
56
|
+
Sidenote for Germans: you can use the word "Gesabbel" (eng: drivel) as a mnemonic here ;-)
|
57
|
+
|
53
58
|
It's pretty easy to write additional time consumers; please refer to the source code.
|
54
59
|
|
55
60
|
|
56
61
|
== Prerequisites
|
57
62
|
|
58
|
-
Rails
|
59
|
-
|
63
|
+
Rails 3.x is required. The plugin will raise an error if you try to use it with a
|
64
|
+
different version.
|
65
|
+
|
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/).
|
60
69
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
(see http://github.com/alpinegizmo/logjam/).
|
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).
|
65
73
|
|
66
74
|
|
67
75
|
== History
|
68
76
|
|
69
|
-
This plugin started from the code of the 'custom_benchmark' plugin
|
70
|
-
|
71
|
-
|
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.
|
72
80
|
|
73
81
|
|
74
82
|
== License
|
75
83
|
|
76
|
-
Copyright (c) 2009 Stefan Kaes <skaes@railsexpress.de>
|
84
|
+
Copyright (c) 2009,2011 Stefan Kaes <skaes@railsexpress.de>
|
77
85
|
|
78
86
|
Some portions Copyright (c) 2008 tylerkovacs
|
79
87
|
|
data/TODO
ADDED
@@ -58,6 +58,14 @@ module ActionController #:nodoc:
|
|
58
58
|
consumed_during_rendering = TimeBandits.consumed - consumed_before_rendering
|
59
59
|
runtime - consumed_during_rendering
|
60
60
|
end
|
61
|
+
|
62
|
+
module ClassMethods
|
63
|
+
def log_process_action(payload) #:nodoc:
|
64
|
+
messages, view_runtime = [], payload[:view_runtime]
|
65
|
+
messages << ("Views: %.3fms" % view_runtime.to_f) if view_runtime
|
66
|
+
messages
|
67
|
+
end
|
68
|
+
end
|
61
69
|
end
|
62
70
|
|
63
71
|
class LogSubscriber
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# this file monkey patches class ActiveRecord::LogSubscriber
|
2
|
+
# to count the number of sql statements being executed
|
3
|
+
# and the number of query cache hits
|
4
|
+
# it needs to be adapted to each new rails version
|
5
|
+
|
6
|
+
raise "AR log subscriber monkey patch for custom benchmarking is not compatible with your rails version" unless
|
7
|
+
(Rails::VERSION::STRING > "3.0" && Rails::VERSION::STRING < "3.1")
|
8
|
+
|
9
|
+
require "active_record/log_subscriber"
|
10
|
+
|
11
|
+
module ActiveRecord
|
12
|
+
class LogSubscriber
|
13
|
+
|
14
|
+
def self.call_count=(value)
|
15
|
+
Thread.current["active_record_sql_call_count"] = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.call_count
|
19
|
+
Thread.current["active_record_sql_call_count"] ||= 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.query_cache_hits=(value)
|
23
|
+
Thread.current["active_record_sql_query_cache_hits"] = value
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.query_cache_hits
|
27
|
+
Thread.current["active_record_sql_query_cache_hits"] ||= 0
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.reset_call_count
|
31
|
+
calls = call_count
|
32
|
+
self.call_count = 0
|
33
|
+
calls
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.reset_query_cache_hits
|
37
|
+
hits = query_cache_hits
|
38
|
+
self.query_cache_hits = 0
|
39
|
+
hits
|
40
|
+
end
|
41
|
+
|
42
|
+
def sql(event)
|
43
|
+
self.class.runtime += event.duration
|
44
|
+
self.class.call_count += 1
|
45
|
+
self.class.query_cache_hits += 1 if event.payload[:name] == "CACHE"
|
46
|
+
|
47
|
+
return unless logger.debug?
|
48
|
+
|
49
|
+
name = '%s (%.1fms)' % [event.payload[:name], event.duration]
|
50
|
+
sql = event.payload[:sql].squeeze(' ')
|
51
|
+
|
52
|
+
if odd?
|
53
|
+
name = color(name, CYAN, true)
|
54
|
+
sql = color(sql, nil, true)
|
55
|
+
else
|
56
|
+
name = color(name, MAGENTA, true)
|
57
|
+
end
|
58
|
+
|
59
|
+
debug " #{name} #{sql}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module Railties
|
64
|
+
module ControllerRuntime
|
65
|
+
def cleanup_view_runtime
|
66
|
+
super
|
67
|
+
end
|
68
|
+
module ClassMethods
|
69
|
+
def log_process_action(payload)
|
70
|
+
super
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
data/lib/time_bandits/railtie.rb
CHANGED
@@ -9,8 +9,8 @@ module TimeBandits
|
|
9
9
|
end
|
10
10
|
|
11
11
|
ActiveSupport.on_load(:active_record) do
|
12
|
-
require 'time_bandits/monkey_patches/
|
13
|
-
TimeBandits.add TimeBandits::TimeConsumers::Database
|
12
|
+
require 'time_bandits/monkey_patches/active_record'
|
13
|
+
TimeBandits.add TimeBandits::TimeConsumers::Database
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
@@ -1,54 +1,49 @@
|
|
1
1
|
# this consumer gets installed automatically by the plugin
|
2
2
|
# if this were not so
|
3
3
|
#
|
4
|
-
#
|
4
|
+
# TimeBandit.add TimeBandits::TimeConsumers::Database
|
5
5
|
#
|
6
6
|
# would do the job
|
7
7
|
|
8
8
|
module TimeBandits
|
9
9
|
module TimeConsumers
|
10
|
-
# provide a time consumer interface to ActiveRecord
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
10
|
+
# provide a time consumer interface to ActiveRecord
|
11
|
+
module Database
|
12
|
+
extend self
|
13
|
+
|
14
|
+
def info
|
15
|
+
Thread.current[:time_bandits_database_info] ||= [0.0, 0, 0]
|
16
16
|
end
|
17
|
-
private :initialize
|
18
17
|
|
19
|
-
def
|
20
|
-
|
18
|
+
def info=(info)
|
19
|
+
Thread.current[:time_bandits_database_info] = info
|
21
20
|
end
|
22
21
|
|
23
22
|
def reset
|
24
23
|
reset_stats
|
25
|
-
|
26
|
-
@consumed = 0.0
|
27
|
-
@query_cache_hits = 0
|
24
|
+
self.info = [0.0, 0, 0]
|
28
25
|
end
|
29
26
|
|
30
27
|
def consumed
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
28
|
+
time, calls, hits = reset_stats
|
29
|
+
i = self.info
|
30
|
+
i[2] += hits
|
31
|
+
i[1] += calls
|
32
|
+
i[0] += time
|
35
33
|
end
|
36
34
|
|
37
35
|
def runtime
|
38
|
-
sprintf "
|
36
|
+
sprintf "ActiveRecord: %.3fms(%dq,%dh)", *info
|
39
37
|
end
|
40
38
|
|
41
39
|
private
|
42
|
-
def all_connections
|
43
|
-
ActiveRecord::Base.connection_handler.connection_pools.values.map{|pool| pool.connections}.flatten
|
44
|
-
end
|
45
40
|
|
46
41
|
def reset_stats
|
47
|
-
|
48
|
-
hits =
|
49
|
-
calls =
|
50
|
-
time =
|
51
|
-
[
|
42
|
+
s = ActiveRecord::LogSubscriber
|
43
|
+
hits = s.reset_query_cache_hits
|
44
|
+
calls = s.reset_call_count
|
45
|
+
time = s.reset_runtime
|
46
|
+
[time, calls, hits]
|
52
47
|
end
|
53
48
|
end
|
54
49
|
end
|
data/lib/time_bandits/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: time_bandits
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 5
|
10
|
+
version: 0.0.5
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Stefan Kaes
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-03
|
18
|
+
date: 2011-04-03 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -47,9 +47,10 @@ files:
|
|
47
47
|
- Gemfile
|
48
48
|
- README.rdoc
|
49
49
|
- Rakefile
|
50
|
+
- TODO
|
50
51
|
- lib/time_bandits.rb
|
51
52
|
- lib/time_bandits/monkey_patches/action_controller.rb
|
52
|
-
- lib/time_bandits/monkey_patches/
|
53
|
+
- lib/time_bandits/monkey_patches/active_record.rb
|
53
54
|
- lib/time_bandits/monkey_patches/memcache-client.rb
|
54
55
|
- lib/time_bandits/monkey_patches/memcached.rb
|
55
56
|
- lib/time_bandits/monkey_patches/rails_rack_logger.rb
|
@@ -91,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
92
|
requirements: []
|
92
93
|
|
93
94
|
rubyforge_project: time_bandits
|
94
|
-
rubygems_version: 1.
|
95
|
+
rubygems_version: 1.5.2
|
95
96
|
signing_key:
|
96
97
|
specification_version: 3
|
97
98
|
summary: Custom performance logging for Rails
|
@@ -1,85 +0,0 @@
|
|
1
|
-
# this file monkey patches class ActiveRecord::ConnectionAdapters::AbstractAdapter
|
2
|
-
# and the module module ActiveRecord::ConnectionAdapters::QueryCache
|
3
|
-
# to count the number of sql statements being executed.
|
4
|
-
# it needs to be adapted to each new rails version
|
5
|
-
|
6
|
-
raise "AR abstract adapter monkey patch for custom benchmarking is not compatible with your rails version" unless %w(2.3.2 2.3.3 2.3.4 2.3.8 2.3.9 2.3.10).include?(Rails::VERSION::STRING)
|
7
|
-
|
8
|
-
module ActiveRecord
|
9
|
-
module ConnectionAdapters
|
10
|
-
class ConnectionPool
|
11
|
-
attr_reader :connections
|
12
|
-
end
|
13
|
-
|
14
|
-
class AbstractAdapter
|
15
|
-
attr_accessor :call_count, :query_cache_hits
|
16
|
-
|
17
|
-
def initialize(connection, logger = nil) #:nodoc:
|
18
|
-
@connection, @logger = connection, logger
|
19
|
-
@runtime = 0
|
20
|
-
@call_count = 0
|
21
|
-
@last_verification = 0
|
22
|
-
@query_cache_enabled = false
|
23
|
-
@query_cache_hits = 0
|
24
|
-
end
|
25
|
-
|
26
|
-
def reset_call_count
|
27
|
-
calls = @call_count
|
28
|
-
@call_count = 0
|
29
|
-
calls
|
30
|
-
end
|
31
|
-
|
32
|
-
def reset_query_cache_hits
|
33
|
-
hits = @query_cache_hits
|
34
|
-
@query_cache_hits = 0
|
35
|
-
hits
|
36
|
-
end
|
37
|
-
|
38
|
-
protected
|
39
|
-
def log(sql, name)
|
40
|
-
if block_given?
|
41
|
-
result = nil
|
42
|
-
seconds = Benchmark.realtime { result = yield }
|
43
|
-
@runtime += seconds
|
44
|
-
@call_count += 1
|
45
|
-
log_info(sql, name, seconds * 1000)
|
46
|
-
result
|
47
|
-
else
|
48
|
-
log_info(sql, name, 0)
|
49
|
-
nil
|
50
|
-
end
|
51
|
-
rescue Exception => e
|
52
|
-
# Log message and raise exception.
|
53
|
-
# Set last_verification to 0, so that connection gets verified
|
54
|
-
# upon reentering the request loop
|
55
|
-
@last_verification = 0
|
56
|
-
message = "#{e.class.name}: #{e.message}: #{sql}"
|
57
|
-
log_info(message, name, 0)
|
58
|
-
raise ActiveRecord::StatementInvalid, message
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
module QueryCache
|
63
|
-
private
|
64
|
-
def cache_sql(sql)
|
65
|
-
result =
|
66
|
-
if @query_cache.has_key?(sql)
|
67
|
-
@query_cache_hits += 1
|
68
|
-
log_info(sql, "CACHE", 0.0)
|
69
|
-
@query_cache[sql]
|
70
|
-
else
|
71
|
-
@query_cache[sql] = yield
|
72
|
-
end
|
73
|
-
|
74
|
-
if Array === result
|
75
|
-
result.collect { |row| row.dup }
|
76
|
-
else
|
77
|
-
result.duplicable? ? result.dup : result
|
78
|
-
end
|
79
|
-
rescue TypeError
|
80
|
-
result
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|