time_bandits 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
|