miniprofiler 0.1.7.1

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.
Files changed (42) hide show
  1. data/.gitignore +4 -0
  2. data/CHANGELOG +32 -0
  3. data/Gemfile +15 -0
  4. data/Gemfile.lock +64 -0
  5. data/README.md +110 -0
  6. data/Rakefile +40 -0
  7. data/autotest/discover.rb +2 -0
  8. data/lib/mini_profiler/body_add_proxy.rb +45 -0
  9. data/lib/mini_profiler/client_timer_struct.rb +76 -0
  10. data/lib/mini_profiler/config.rb +52 -0
  11. data/lib/mini_profiler/context.rb +10 -0
  12. data/lib/mini_profiler/page_timer_struct.rb +53 -0
  13. data/lib/mini_profiler/profiler.rb +405 -0
  14. data/lib/mini_profiler/profiling_methods.rb +73 -0
  15. data/lib/mini_profiler/request_timer_struct.rb +96 -0
  16. data/lib/mini_profiler/sql_timer_struct.rb +48 -0
  17. data/lib/mini_profiler/storage/abstract_store.rb +27 -0
  18. data/lib/mini_profiler/storage/file_store.rb +108 -0
  19. data/lib/mini_profiler/storage/memory_store.rb +68 -0
  20. data/lib/mini_profiler/storage/redis_store.rb +44 -0
  21. data/lib/mini_profiler/timer_struct.rb +31 -0
  22. data/lib/mini_profiler_rails/railtie.rb +84 -0
  23. data/lib/patches/sql_patches.rb +185 -0
  24. data/lib/rack-mini-profiler.rb +6 -0
  25. data/rack-mini-profiler.gemspec +23 -0
  26. data/spec/components/body_add_proxy_spec.rb +90 -0
  27. data/spec/components/client_timer_struct_spec.rb +165 -0
  28. data/spec/components/file_store_spec.rb +47 -0
  29. data/spec/components/memory_store_spec.rb +40 -0
  30. data/spec/components/page_timer_struct_spec.rb +33 -0
  31. data/spec/components/profiler_spec.rb +126 -0
  32. data/spec/components/redis_store_spec.rb +44 -0
  33. data/spec/components/request_timer_struct_spec.rb +148 -0
  34. data/spec/components/sql_timer_struct_spec.rb +63 -0
  35. data/spec/components/timer_struct_spec.rb +47 -0
  36. data/spec/fixtures/simple_client_request.yml +17 -0
  37. data/spec/fixtures/weird_client_request.yml +13 -0
  38. data/spec/integration/mini_profiler_spec.rb +142 -0
  39. data/spec/spec_helper.rb +31 -0
  40. data/spec/support/expand_each_to_matcher.rb +8 -0
  41. data/test_old/config.ru +41 -0
  42. metadata +155 -0
@@ -0,0 +1,108 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ class FileStore < AbstractStore
4
+
5
+ class FileCache
6
+ def initialize(path, prefix)
7
+ @path = path
8
+ @prefix = prefix
9
+ end
10
+
11
+ def [](key)
12
+ begin
13
+ data = ::File.open(path(key),"rb") {|f| f.read}
14
+ return Marshal.load data
15
+ rescue => e
16
+ return nil
17
+ end
18
+ end
19
+
20
+ def []=(key,val)
21
+ ::File.open(path(key), "wb+") {|f| f.write Marshal.dump(val)}
22
+ end
23
+
24
+ private
25
+ def path(key)
26
+ @path + "/" + @prefix + "_" + key
27
+ end
28
+ end
29
+
30
+ EXPIRE_TIMER_CACHE = 3600 * 24
31
+
32
+ def initialize(args)
33
+ @path = args[:path]
34
+ raise ArgumentError.new :path unless @path
35
+ @timer_struct_cache = FileCache.new(@path, "mp_timers")
36
+ @timer_struct_lock = Mutex.new
37
+ @user_view_cache = FileCache.new(@path, "mp_views")
38
+ @user_view_lock = Mutex.new
39
+
40
+ me = self
41
+ Thread.new do
42
+ while true do
43
+ me.cleanup_cache if MiniProfiler.instance
44
+ sleep(3600)
45
+ end
46
+ end
47
+ end
48
+
49
+ def save(page_struct)
50
+ @timer_struct_lock.synchronize {
51
+ @timer_struct_cache[page_struct['Id']] = page_struct
52
+ }
53
+ end
54
+
55
+ def load(id)
56
+ @timer_struct_lock.synchronize {
57
+ @timer_struct_cache[id]
58
+ }
59
+ end
60
+
61
+ def set_unviewed(user, id)
62
+ @user_view_lock.synchronize {
63
+ current = @user_view_cache[user]
64
+ current = [] unless Array === current
65
+ current << id
66
+ @user_view_cache[user] = current.uniq
67
+ }
68
+ end
69
+
70
+ def set_viewed(user, id)
71
+ @user_view_lock.synchronize {
72
+ @user_view_cache[user] ||= []
73
+ current = @user_view_cache[user]
74
+ current = [] unless Array === current
75
+ current.delete(id)
76
+ @user_view_cache[user] = current.uniq
77
+ }
78
+ end
79
+
80
+ def get_unviewed_ids(user)
81
+ @user_view_lock.synchronize {
82
+ @user_view_cache[user]
83
+ }
84
+ end
85
+
86
+
87
+ private
88
+
89
+
90
+ def cleanup_cache
91
+ files = Dir.entries(@path)
92
+ @timer_struct_lock.synchronize {
93
+ files.each do |f|
94
+ f = @path + '/' + f
95
+ File.delete f if f =~ /^mp_timers/ and (Time.now - File.mtime(f)) > EXPIRE_TIMER_CACHE
96
+ end
97
+ }
98
+ @user_view_lock.synchronize {
99
+ files.each do |f|
100
+ f = @path + '/' + f
101
+ File.delete f if f =~ /^mp_views/ and (Time.now - File.mtime(f)) > EXPIRE_TIMER_CACHE
102
+ end
103
+ }
104
+ end
105
+
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,68 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ class MemoryStore < AbstractStore
4
+
5
+ EXPIRE_TIMER_CACHE = 3600 * 24
6
+
7
+ def initialize(args)
8
+ @timer_struct_lock = Mutex.new
9
+ @timer_struct_cache = {}
10
+ @user_view_lock = Mutex.new
11
+ @user_view_cache = {}
12
+
13
+ # TODO: fix it to use weak ref, trouble is may be broken in 1.9 so need to use the 'ref' gem
14
+ me = self
15
+ Thread.new do
16
+ while true do
17
+ me.cleanup_cache
18
+ sleep(3600)
19
+ end
20
+ end
21
+ end
22
+
23
+ def save(page_struct)
24
+ @timer_struct_lock.synchronize {
25
+ @timer_struct_cache[page_struct['Id']] = page_struct
26
+ }
27
+ end
28
+
29
+ def load(id)
30
+ @timer_struct_lock.synchronize {
31
+ @timer_struct_cache[id]
32
+ }
33
+ end
34
+
35
+ def set_unviewed(user, id)
36
+ @user_view_lock.synchronize {
37
+ @user_view_cache[user] ||= []
38
+ @user_view_cache[user] << id
39
+ }
40
+ end
41
+
42
+ def set_viewed(user, id)
43
+ @user_view_lock.synchronize {
44
+ @user_view_cache[user] ||= []
45
+ @user_view_cache[user].delete(id)
46
+ }
47
+ end
48
+
49
+ def get_unviewed_ids(user)
50
+ @user_view_lock.synchronize {
51
+ @user_view_cache[user]
52
+ }
53
+ end
54
+
55
+
56
+ private
57
+
58
+
59
+ def cleanup_cache
60
+ expire_older_than = ((Time.now.to_f - MiniProfiler::MemoryStore::EXPIRE_TIMER_CACHE) * 1000).to_i
61
+ @timer_struct_lock.synchronize {
62
+ @timer_struct_cache.delete_if { |k, v| v['Root']['StartMilliseconds'] < expire_older_than }
63
+ }
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,44 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ class RedisStore < AbstractStore
4
+
5
+ EXPIRE_SECONDS = 60 * 60 * 24
6
+
7
+ def initialize(args)
8
+ args ||= {}
9
+ @prefix = args[:prefix] || 'MPRedisStore'
10
+ end
11
+
12
+ def save(page_struct)
13
+ redis.setex "#{@prefix}#{page_struct['Id']}", EXPIRE_SECONDS, Marshal::dump(page_struct)
14
+ end
15
+
16
+ def load(id)
17
+ raw = redis.get "#{@prefix}#{id}"
18
+ if raw
19
+ Marshal::load raw
20
+ end
21
+ end
22
+
23
+ def set_unviewed(user, id)
24
+ redis.sadd "#{@prefix}-#{user}-v", id
25
+ end
26
+
27
+ def set_viewed(user, id)
28
+ redis.srem "#{@prefix}-#{user}-v", id
29
+ end
30
+
31
+ def get_unviewed_ids(user)
32
+ redis.smembers "#{@prefix}-#{user}-v"
33
+ end
34
+
35
+ private
36
+
37
+ def redis
38
+ require 'redis' unless defined? Redis
39
+ Redis.new
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,31 @@
1
+ module Rack
2
+ class MiniProfiler
3
+
4
+ # A base class for timing structures
5
+ class TimerStruct
6
+
7
+ def initialize(attrs={})
8
+ @attributes = attrs
9
+ end
10
+
11
+ def attributes
12
+ @attributes ||= {}
13
+ end
14
+
15
+ def [](name)
16
+ attributes[name]
17
+ end
18
+
19
+ def []=(name, val)
20
+ attributes[name] = val
21
+ self
22
+ end
23
+
24
+ def to_json(*a)
25
+ ::JSON.generate(@attributes, a[0])
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,84 @@
1
+ require 'fileutils'
2
+
3
+ module MiniProfilerRails
4
+
5
+ class Railtie < ::Rails::Railtie
6
+
7
+ initializer "rack_mini_profiler.configure_rails_initialization" do |app|
8
+ c = Rack::MiniProfiler.config
9
+
10
+ # By default, only show the MiniProfiler in development mode, in production allow profiling if post_authorize_cb is set
11
+ c.pre_authorize_cb = lambda { |env|
12
+ Rails.env.development? || Rails.env.production?
13
+ }
14
+
15
+ if Rails.env.development?
16
+ c.skip_paths ||= []
17
+ c.skip_paths << "/assets/"
18
+ c.skip_schema_queries = true
19
+ end
20
+
21
+ if Rails.env.production?
22
+ c.authorization_mode = :whitelist
23
+ end
24
+
25
+ # The file store is just so much less flaky
26
+ tmp = Rails.root.to_s + "/tmp/miniprofiler"
27
+ FileUtils.mkdir_p(tmp) unless File.exists?(tmp)
28
+
29
+ c.storage_options = {:path => tmp}
30
+ c.storage = Rack::MiniProfiler::FileStore
31
+
32
+ # Quiet the SQL stack traces
33
+ c.backtrace_remove = Rails.root.to_s + "/"
34
+ c.backtrace_filter = /^\/?(app|config|lib|test)/
35
+ c.skip_schema_queries = Rails.env != 'production'
36
+
37
+ # Install the Middleware
38
+ app.middleware.insert(0, Rack::MiniProfiler)
39
+
40
+ # Attach to various Rails methods
41
+ ::Rack::MiniProfiler.profile_method(ActionController::Base, :process) {|action| "Executing action: #{action}"}
42
+ ::Rack::MiniProfiler.profile_method(ActionView::Template, :render) {|x,y| "Rendering: #{@virtual_path}"}
43
+
44
+ end
45
+
46
+ # TODO: Implement something better here
47
+ # config.after_initialize do
48
+ #
49
+ # class ::ActionView::Helpers::AssetTagHelper::JavascriptIncludeTag
50
+ # alias_method :asset_tag_orig, :asset_tag
51
+ # def asset_tag(source,options)
52
+ # current = Rack::MiniProfiler.current
53
+ # return asset_tag_orig(source,options) unless current
54
+ # wrapped = ""
55
+ # unless current.mpt_init
56
+ # current.mpt_init = true
57
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
58
+ # end
59
+ # name = source.split('/')[-1]
60
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
61
+ # wrapped
62
+ # end
63
+ # end
64
+
65
+ # class ::ActionView::Helpers::AssetTagHelper::StylesheetIncludeTag
66
+ # alias_method :asset_tag_orig, :asset_tag
67
+ # def asset_tag(source,options)
68
+ # current = Rack::MiniProfiler.current
69
+ # return asset_tag_orig(source,options) unless current
70
+ # wrapped = ""
71
+ # unless current.mpt_init
72
+ # current.mpt_init = true
73
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
74
+ # end
75
+ # name = source.split('/')[-1]
76
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
77
+ # wrapped
78
+ # end
79
+ # end
80
+
81
+ # end
82
+
83
+ end
84
+ end
@@ -0,0 +1,185 @@
1
+ class SqlPatches
2
+
3
+ def self.patched?
4
+ @patched
5
+ end
6
+
7
+ def self.patched=(val)
8
+ @patched = val
9
+ end
10
+
11
+ def self.class_exists?(name)
12
+ eval(name + ".class").to_s.eql?('Class')
13
+ rescue NameError
14
+ false
15
+ end
16
+
17
+ def self.module_exists?(name)
18
+ eval(name + ".class").to_s.eql?('Module')
19
+ rescue NameError
20
+ false
21
+ end
22
+ end
23
+
24
+ # The best kind of instrumentation is in the actual db provider, however we don't want to double instrument
25
+ if SqlPatches.class_exists? "Mysql2::Client"
26
+
27
+ class Mysql2::Result
28
+ alias_method :each_without_profiling, :each
29
+ def each(*args, &blk)
30
+ return each_without_profiling(*args, &blk) unless @miniprofiler_sql_id
31
+
32
+ start = Time.now
33
+ result = each_without_profiling(*args,&blk)
34
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
35
+
36
+ @miniprofiler_sql_id.report_reader_duration(elapsed_time)
37
+ result
38
+ end
39
+ end
40
+
41
+ class Mysql2::Client
42
+ alias_method :query_without_profiling, :query
43
+ def query(*args,&blk)
44
+ current = ::Rack::MiniProfiler.current
45
+ return query_without_profiling(*args,&blk) unless current
46
+
47
+ start = Time.now
48
+ result = query_without_profiling(*args,&blk)
49
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
50
+ result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
51
+
52
+ result
53
+
54
+ end
55
+ end
56
+
57
+ SqlPatches.patched = true
58
+ end
59
+
60
+
61
+ # PG patches, keep in mind exec and async_exec have a exec{|r| } semantics that is yet to be implemented
62
+ if SqlPatches.class_exists? "PG::Result"
63
+
64
+ class PG::Result
65
+ alias_method :each_without_profiling, :each
66
+ alias_method :values_without_profiling, :values
67
+
68
+ def values(*args, &blk)
69
+ return values_without_profiling(*args, &blk) unless @miniprofiler_sql_id
70
+
71
+ start = Time.now
72
+ result = values_without_profiling(*args,&blk)
73
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
74
+
75
+ @miniprofiler_sql_id.report_reader_duration(elapsed_time)
76
+ result
77
+ end
78
+
79
+ def each(*args, &blk)
80
+ return each_without_profiling(*args, &blk) unless @miniprofiler_sql_id
81
+
82
+ start = Time.now
83
+ result = each_without_profiling(*args,&blk)
84
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
85
+
86
+ @miniprofiler_sql_id.report_reader_duration(elapsed_time)
87
+ result
88
+ end
89
+ end
90
+
91
+ class PG::Connection
92
+ alias_method :exec_without_profiling, :exec
93
+ alias_method :async_exec_without_profiling, :async_exec
94
+
95
+ def exec(*args,&blk)
96
+ current = ::Rack::MiniProfiler.current
97
+ return exec_without_profiling(*args,&blk) unless current
98
+
99
+ start = Time.now
100
+ result = exec_without_profiling(*args,&blk)
101
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
102
+ result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
103
+
104
+ result
105
+ end
106
+
107
+ def async_exec(*args,&blk)
108
+ current = ::Rack::MiniProfiler.current
109
+ return exec_without_profiling(*args,&blk) unless current
110
+
111
+ start = Time.now
112
+ result = exec_without_profiling(*args,&blk)
113
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
114
+ result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
115
+
116
+ result
117
+ end
118
+
119
+ alias_method :query, :exec
120
+ end
121
+
122
+ SqlPatches.patched = true
123
+ end
124
+
125
+
126
+
127
+ # Fallback for sequel
128
+ if SqlPatches.class_exists?("Sequel::Database") && !SqlPatches.patched?
129
+ module Sequel
130
+ class Database
131
+ alias_method :log_duration_original, :log_duration
132
+ def log_duration(duration, message)
133
+ ::Rack::MiniProfiler.record_sql(message, duration)
134
+ log_duration_original(duration, message)
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+
141
+ ## based off https://github.com/newrelic/rpm/blob/master/lib/new_relic/agent/instrumentation/active_record.rb
142
+ ## fallback for alls sorts of weird dbs
143
+ if SqlPatches.module_exists?('ActiveRecord')
144
+ module Rack
145
+ class MiniProfiler
146
+ module ActiveRecordInstrumentation
147
+ def self.included(instrumented_class)
148
+ instrumented_class.class_eval do
149
+ unless instrumented_class.method_defined?(:log_without_miniprofiler)
150
+ alias_method :log_without_miniprofiler, :log
151
+ alias_method :log, :log_with_miniprofiler
152
+ protected :log
153
+ end
154
+ end
155
+ end
156
+
157
+ def log_with_miniprofiler(*args, &block)
158
+ current = ::Rack::MiniProfiler.current
159
+ return log_without_miniprofiler(*args, &block) unless current
160
+
161
+ sql, name, binds = args
162
+ t0 = Time.now
163
+ rval = log_without_miniprofiler(*args, &block)
164
+
165
+ # Don't log schema queries if the option is set
166
+ return rval if Rack::MiniProfiler.config.skip_schema_queries and name =~ /SCHEMA/
167
+
168
+ elapsed_time = ((Time.now - t0).to_f * 1000).round(1)
169
+ Rack::MiniProfiler.record_sql(sql, elapsed_time)
170
+ rval
171
+ end
172
+ end
173
+ end
174
+
175
+ def self.insert_instrumentation
176
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.module_eval do
177
+ include ::Rack::MiniProfiler::ActiveRecordInstrumentation
178
+ end
179
+ end
180
+
181
+ if defined?(::Rails) && !SqlPatches.patched?
182
+ insert_instrumentation
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,6 @@
1
+ require File.expand_path('mini_profiler/profiler', File.dirname(__FILE__) )
2
+ require File.expand_path('patches/sql_patches', File.dirname(__FILE__) )
3
+
4
+ if defined?(::Rails) && ::Rails::VERSION::MAJOR.to_i == 3
5
+ require File.expand_path('mini_profiler_rails/railtie', File.dirname(__FILE__) )
6
+ end
@@ -0,0 +1,23 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "miniprofiler"
3
+ s.version = "0.1.7.1"
4
+ s.summary = "Profiles loading speed for rack applications."
5
+ s.authors = ["Aleks Totic","Sam Saffron", "Robin Ward"]
6
+ s.description = "Page loading speed displayed on every page. Optimize while you develop, performance is a feature."
7
+ s.email = "sam.saffron@gmail.com"
8
+ s.homepage = "http://miniprofiler.com"
9
+ s.files = `git ls-files`.split("\n")
10
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
11
+ s.extra_rdoc_files = [
12
+ "README.md",
13
+ "CHANGELOG"
14
+ ]
15
+ s.add_runtime_dependency 'rack', '>= 1.1.3'
16
+ if RUBY_VERSION < "1.9"
17
+ s.add_runtime_dependency 'json', '>= 1.6'
18
+ end
19
+
20
+ s.add_development_dependency 'rake'
21
+ s.add_development_dependency 'rack-test'
22
+ s.add_development_dependency 'activerecord', '~> 3.0'
23
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+ require 'mini_profiler/body_add_proxy'
3
+
4
+ describe Rack::MiniProfiler::BodyAddProxy do
5
+
6
+ context 'body as an array' do
7
+
8
+ before do
9
+ @proxy = Rack::MiniProfiler::BodyAddProxy.new(%w(a b c), 'd')
10
+ end
11
+
12
+ it 'contains the appended value' do
13
+ @proxy.should expand_each_to %w(a b c d)
14
+ end
15
+
16
+ it 'has a correct value in to_str' do
17
+ @proxy.to_str.should == "abcd"
18
+ end
19
+
20
+ describe 'delegation' do
21
+ it 'delegates respond to <<' do
22
+ @proxy.respond_to?('<<').should be_true
23
+ end
24
+
25
+ it 'delegates respond to first' do
26
+ @proxy.respond_to?(:first).should be_true
27
+ end
28
+
29
+ it 'delegates method_missing' do
30
+ @proxy.first.should == 'a'
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ context 'body as a custom object' do
37
+
38
+ # A class and a super class to help us test appending to a custom object, such as
39
+ # Rails' ActionDispatch::Response
40
+ class Band
41
+ def style
42
+ 'rock'
43
+ end
44
+ end
45
+
46
+ class Beatles < Band
47
+ def each
48
+ yield 'john'
49
+ yield 'paul'
50
+ yield 'george'
51
+ end
52
+
53
+ def fake_method; nil; end
54
+
55
+ def method_missing(*args, &block)
56
+ 'yoko'
57
+ end
58
+ end
59
+
60
+ before do
61
+ @proxy = Rack::MiniProfiler::BodyAddProxy.new(Beatles.new, 'ringo')
62
+ end
63
+
64
+ it 'contains the appended value' do
65
+ @proxy.should expand_each_to %w(john paul george ringo)
66
+ end
67
+
68
+ it 'has a correct value in to_str' do
69
+ @proxy.to_str.should == "johnpaulgeorgeringo"
70
+ end
71
+
72
+ describe 'delegation' do
73
+ it 'delegates respond to fake_method' do
74
+ @proxy.respond_to?(:fake_method).should be_true
75
+ end
76
+
77
+ it 'delegates respond to a super class' do
78
+ @proxy.respond_to?(:style).should be_true
79
+ end
80
+
81
+ it 'delegates method_missing' do
82
+ @proxy.doesnt_exist.should == 'yoko'
83
+ end
84
+
85
+ end
86
+
87
+ end
88
+
89
+
90
+ end