rack-mini-profiler 0.1 → 0.1.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.

Potentially problematic release.


This version of rack-mini-profiler might be problematic. Click here for more details.

@@ -0,0 +1,73 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ module ProfilingMethods
4
+
5
+ def record_sql(query, elapsed_ms)
6
+ c = current
7
+ return unless c
8
+ c.current_timer.add_sql(query, elapsed_ms, c.page_struct, c.skip_backtrace, c.full_backtrace) if (c && c.current_timer)
9
+ end
10
+
11
+ # perform a profiling step on given block
12
+ def step(name)
13
+ if current
14
+ parent_timer = current.current_timer
15
+ result = nil
16
+ current.current_timer = current_timer = current.current_timer.add_child(name)
17
+ begin
18
+ result = yield if block_given?
19
+ ensure
20
+ current_timer.record_time
21
+ current.current_timer = parent_timer
22
+ end
23
+ result
24
+ else
25
+ yield if block_given?
26
+ end
27
+ end
28
+
29
+ def unprofile_method(klass, method)
30
+ with_profiling = (method.to_s + "_with_mini_profiler").intern
31
+ without_profiling = (method.to_s + "_without_mini_profiler").intern
32
+
33
+ if klass.send :method_defined?, with_profiling
34
+ klass.send :alias_method, method, without_profiling
35
+ klass.send :remove_method, with_profiling
36
+ klass.send :remove_method, without_profiling
37
+ end
38
+ end
39
+
40
+ def profile_method(klass, method, &blk)
41
+ default_name = klass.to_s + " " + method.to_s
42
+ with_profiling = (method.to_s + "_with_mini_profiler").intern
43
+ without_profiling = (method.to_s + "_without_mini_profiler").intern
44
+
45
+ if klass.send :method_defined?, with_profiling
46
+ return # dont double profile
47
+ end
48
+
49
+ klass.send :alias_method, without_profiling, method
50
+ klass.send :define_method, with_profiling do |*args, &orig|
51
+ return self.send without_profiling, *args, &orig unless Rack::MiniProfiler.current
52
+
53
+ name = default_name
54
+ name = blk.bind(self).call(*args) if blk
55
+
56
+ parent_timer = Rack::MiniProfiler.current.current_timer
57
+ page_struct = Rack::MiniProfiler.current.page_struct
58
+ result = nil
59
+
60
+ Rack::MiniProfiler.current.current_timer = current_timer = parent_timer.add_child(name)
61
+ begin
62
+ result = self.send without_profiling, *args, &orig
63
+ ensure
64
+ current_timer.record_time
65
+ Rack::MiniProfiler.current.current_timer = parent_timer
66
+ end
67
+ result
68
+ end
69
+ klass.send :alias_method, method, with_profiling
70
+ end
71
+ end
72
+ end
73
+ end
@@ -6,14 +6,14 @@ module Rack
6
6
  class RequestTimerStruct < TimerStruct
7
7
 
8
8
  def self.createRoot(name, page)
9
- rt = RequestTimerStruct.new(name, page)
9
+ rt = RequestTimerStruct.new(name, page, nil)
10
10
  rt["IsRoot"]= true
11
11
  rt
12
12
  end
13
13
 
14
- attr_reader :children_duration
14
+ attr_accessor :children_duration
15
15
 
16
- def initialize(name, page)
16
+ def initialize(name, page, parent)
17
17
  super("Id" => MiniProfiler.generate_id,
18
18
  "Name" => name,
19
19
  "DurationMilliseconds" => 0,
@@ -30,34 +30,65 @@ module Rack
30
30
  "SqlTimingsDurationMilliseconds"=> 0,
31
31
  "IsTrivial"=> false,
32
32
  "IsRoot"=> false,
33
- "Depth"=> 0,
33
+ "Depth"=> parent ? parent.depth + 1 : 0,
34
34
  "ExecutedReaders"=> 0,
35
35
  "ExecutedScalars"=> 0,
36
36
  "ExecutedNonQueries"=> 0)
37
37
  @children_duration = 0
38
+ @start = Time.now
39
+ @parent = parent
40
+ @page = page
38
41
  end
39
42
 
40
- def add_child(request_timer)
43
+ def duration_ms
44
+ self['DurationMilliseconds']
45
+ end
46
+
47
+ def start_ms
48
+ self['StartMilliseconds']
49
+ end
50
+
51
+ def start
52
+ @start
53
+ end
54
+
55
+ def depth
56
+ self['Depth']
57
+ end
58
+
59
+ def children
60
+ self['Children']
61
+ end
62
+
63
+ def add_child(name)
64
+ request_timer = RequestTimerStruct.new(name, @page, self)
41
65
  self['Children'].push(request_timer)
42
66
  self['HasChildren'] = true
43
67
  request_timer['ParentTimingId'] = self['Id']
44
68
  request_timer['Depth'] = self['Depth'] + 1
45
- @children_duration += request_timer['DurationMilliseconds']
69
+ request_timer
46
70
  end
47
71
 
48
- def add_sql(query, elapsed_ms, page, skip_backtrace = false)
49
- timer = SqlTimerStruct.new(query, elapsed_ms, page, skip_backtrace)
72
+ def add_sql(query, elapsed_ms, page, skip_backtrace = false, full_backtrace = false)
73
+ timer = SqlTimerStruct.new(query, elapsed_ms, page, self , skip_backtrace, full_backtrace)
50
74
  timer['ParentTimingId'] = self['Id']
51
75
  self['SqlTimings'].push(timer)
52
76
  self['HasSqlTimings'] = true
53
77
  self['SqlTimingsDurationMilliseconds'] += elapsed_ms
54
78
  page['DurationMillisecondsInSql'] += elapsed_ms
79
+ timer
55
80
  end
56
81
 
57
- def record_time(milliseconds)
82
+ def record_time(milliseconds = nil)
83
+ milliseconds ||= (Time.now - @start) * 1000
58
84
  self['DurationMilliseconds'] = milliseconds
59
85
  self['IsTrivial'] = true if milliseconds < self["TrivialDurationThresholdMilliseconds"]
60
86
  self['DurationWithoutChildrenMilliseconds'] = milliseconds - @children_duration
87
+
88
+ if @parent
89
+ @parent.children_duration += milliseconds
90
+ end
91
+
61
92
  end
62
93
  end
63
94
  end
@@ -5,7 +5,7 @@ module Rack
5
5
 
6
6
  # Timing system for a SQL query
7
7
  class SqlTimerStruct < TimerStruct
8
- def initialize(query, duration_ms, page, skip_backtrace = false)
8
+ def initialize(query, duration_ms, page, parent, skip_backtrace = false, full_backtrace = false)
9
9
 
10
10
  stack_trace = nil
11
11
  unless skip_backtrace
@@ -13,24 +13,35 @@ module Rack
13
13
  stack_trace = ""
14
14
  # Clean up the stack trace if there are options to do so
15
15
  Kernel.caller.each do |ln|
16
- ln.gsub!(Rack::MiniProfiler.configuration[:backtrace_remove], '') if Rack::MiniProfiler.configuration[:backtrace_remove]
17
- if Rack::MiniProfiler.configuration[:backtrace_filter].nil? or ln =~ Rack::MiniProfiler.configuration[:backtrace_filter]
16
+ ln.gsub!(Rack::MiniProfiler.config.backtrace_remove, '') if Rack::MiniProfiler.config.backtrace_remove and !full_backtrace
17
+ if full_backtrace or Rack::MiniProfiler.config.backtrace_filter.nil? or ln =~ Rack::MiniProfiler.config.backtrace_filter
18
18
  stack_trace << ln << "\n"
19
19
  end
20
20
  end
21
21
  end
22
22
 
23
+ @parent = parent
24
+ @page = page
25
+
23
26
  super("ExecuteType" => 3, # TODO
24
27
  "FormattedCommandString" => query,
25
28
  "StackTraceSnippet" => stack_trace,
26
- "StartMilliseconds" => (Time.now.to_f * 1000).to_i - page['Started'],
29
+ "StartMilliseconds" => ((Time.now.to_f * 1000).to_i - page['Started']) - duration_ms,
27
30
  "DurationMilliseconds" => duration_ms,
28
- "FirstFetchDurationMilliseconds" => 0,
31
+ "FirstFetchDurationMilliseconds" => duration_ms,
29
32
  "Parameters" => nil,
30
33
  "ParentTimingId" => nil,
31
34
  "IsDuplicate" => false)
32
35
  end
33
36
 
37
+ def report_reader_duration(elapsed_ms)
38
+ return if @reported
39
+ @reported = true
40
+ self["DurationMilliseconds"] += elapsed_ms
41
+ @parent["SqlTimingsDurationMilliseconds"] += elapsed_ms
42
+ @page["DurationMillisecondsInSql"] += elapsed_ms
43
+ end
44
+
34
45
  end
35
46
 
36
47
  end
@@ -4,7 +4,8 @@ module Rack
4
4
 
5
5
  EXPIRE_SECONDS = 60 * 60 * 24
6
6
 
7
- def initialize(args = {})
7
+ def initialize(args)
8
+ args ||= {}
8
9
  @prefix = args[:prefix] || 'MPRedisStore'
9
10
  end
10
11
 
@@ -1,30 +1,82 @@
1
1
  module MiniProfilerRails
2
+
2
3
  class Railtie < ::Rails::Railtie
3
4
 
4
5
  initializer "rack_mini_profiler.configure_rails_initialization" do |app|
6
+ c = Rack::MiniProfiler.config
7
+
8
+ # By default, only show the MiniProfiler in development mode, in production allow profiling if post_authorize_cb is set
9
+ c.pre_authorize_cb = lambda { |env|
10
+ Rails.env.development? || Rails.env.production?
11
+ }
12
+
13
+ if Rails.env.development?
14
+ c.skip_paths ||= []
15
+ c.skip_paths << "/assets/"
16
+ c.skip_schema_queries = true
17
+ end
5
18
 
6
- # By default, only show the MiniProfiler in development mode
7
- Rack::MiniProfiler.configuration[:authorize_cb] = lambda {|env| Rails.env.development? }
19
+ if Rails.env.production?
20
+ c.authorization_mode = :whitelist
21
+ end
22
+
23
+ # The file store is just so much less flaky
8
24
  tmp = Rails.root.to_s + "/tmp/miniprofiler"
9
25
  Dir::mkdir(tmp) unless File.exists?(tmp)
10
- Rack::MiniProfiler.configuration[:storage_options] = {:path => tmp}
11
- Rack::MiniProfiler.configuration[:storage] = Rack::MiniProfiler::FileStore
12
26
 
13
- # Quiet the SQL stack traces
14
- Rack::MiniProfiler.configuration[:backtrace_remove] = Rails.root.to_s + "/"
15
- Rack::MiniProfiler.configuration[:backtrace_filter] = /^\/?(app|config|lib|test)/
27
+ c.storage_options = {:path => tmp}
28
+ c.storage = Rack::MiniProfiler::FileStore
16
29
 
17
- # The file store is just so much less flaky
30
+ # Quiet the SQL stack traces
31
+ c.backtrace_remove = Rails.root.to_s + "/"
32
+ c.backtrace_filter = /^\/?(app|config|lib|test)/
33
+ c.skip_schema_queries = Rails.env != 'production'
18
34
 
19
35
  # Install the Middleware
20
- app.middleware.insert_before 'Rack::Lock', 'Rack::MiniProfiler'
36
+ app.middleware.insert(0, Rack::MiniProfiler)
21
37
 
22
38
  # Attach to various Rails methods
23
39
  ::Rack::MiniProfiler.profile_method(ActionController::Base, :process) {|action| "Executing action: #{action}"}
24
40
  ::Rack::MiniProfiler.profile_method(ActionView::Template, :render) {|x,y| "Rendering: #{@virtual_path}"}
25
41
 
26
-
27
42
  end
28
43
 
44
+ # TODO: Implement something better here
45
+ # config.after_initialize do
46
+ #
47
+ # class ::ActionView::Helpers::AssetTagHelper::JavascriptIncludeTag
48
+ # alias_method :asset_tag_orig, :asset_tag
49
+ # def asset_tag(source,options)
50
+ # current = Rack::MiniProfiler.current
51
+ # return asset_tag_orig(source,options) unless current
52
+ # wrapped = ""
53
+ # unless current.mpt_init
54
+ # current.mpt_init = true
55
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
56
+ # end
57
+ # name = source.split('/')[-1]
58
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
59
+ # wrapped
60
+ # end
61
+ # end
62
+
63
+ # class ::ActionView::Helpers::AssetTagHelper::StylesheetIncludeTag
64
+ # alias_method :asset_tag_orig, :asset_tag
65
+ # def asset_tag(source,options)
66
+ # current = Rack::MiniProfiler.current
67
+ # return asset_tag_orig(source,options) unless current
68
+ # wrapped = ""
69
+ # unless current.mpt_init
70
+ # current.mpt_init = true
71
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
72
+ # end
73
+ # name = source.split('/')[-1]
74
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
75
+ # wrapped
76
+ # end
77
+ # end
78
+
79
+ # end
80
+
29
81
  end
30
82
  end
@@ -1,4 +1,13 @@
1
- module SqlPatches
1
+ class SqlPatches
2
+
3
+ def self.patched?
4
+ @patched
5
+ end
6
+
7
+ def self.patched=(val)
8
+ @patched = val
9
+ end
10
+
2
11
  def self.class_exists?(name)
3
12
  eval(name + ".class").to_s.eql?('Class')
4
13
  rescue NameError
@@ -6,12 +15,116 @@ module SqlPatches
6
15
  end
7
16
  end
8
17
 
9
- if SqlPatches.class_exists? "Sequel::Database" then
18
+ # The best kind of instrumentation is in the actual db provider, however we don't want to double instrument
19
+ if SqlPatches.class_exists? "Mysql2::Client"
20
+
21
+ class Mysql2::Result
22
+ alias_method :each_without_profiling, :each
23
+ def each(*args, &blk)
24
+ return each_without_profiling(*args, &blk) unless @miniprofiler_sql_id
25
+
26
+ start = Time.now
27
+ result = each_without_profiling(*args,&blk)
28
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
29
+
30
+ @miniprofiler_sql_id.report_reader_duration(elapsed_time)
31
+ result
32
+ end
33
+ end
34
+
35
+ class Mysql2::Client
36
+ alias_method :query_without_profiling, :query
37
+ def query(*args,&blk)
38
+ current = ::Rack::MiniProfiler.current
39
+ return query_without_profiling(*args,&blk) unless current
40
+
41
+ start = Time.now
42
+ result = query_without_profiling(*args,&blk)
43
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
44
+ result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
45
+
46
+ result
47
+
48
+ end
49
+ end
50
+
51
+ SqlPatches.patched = true
52
+ end
53
+
54
+
55
+ # PG patches, keep in mind exec and async_exec have a exec{|r| } semantics that is yet to be implemented
56
+ if SqlPatches.class_exists? "PG::Result"
57
+
58
+ class PG::Result
59
+ alias_method :each_without_profiling, :each
60
+ alias_method :values_without_profiling, :values
61
+
62
+ def values(*args, &blk)
63
+ return values_without_profiling(*args, &blk) unless @miniprofiler_sql_id
64
+
65
+ start = Time.now
66
+ result = values_without_profiling(*args,&blk)
67
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
68
+
69
+ @miniprofiler_sql_id.report_reader_duration(elapsed_time)
70
+ result
71
+ end
72
+
73
+ def each(*args, &blk)
74
+ return each_without_profiling(*args, &blk) unless @miniprofiler_sql_id
75
+
76
+ start = Time.now
77
+ result = each_without_profiling(*args,&blk)
78
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
79
+
80
+ @miniprofiler_sql_id.report_reader_duration(elapsed_time)
81
+ result
82
+ end
83
+ end
84
+
85
+ class PG::Connection
86
+ alias_method :exec_without_profiling, :exec
87
+ alias_method :async_exec_without_profiling, :async_exec
88
+
89
+ def exec(*args,&blk)
90
+ current = ::Rack::MiniProfiler.current
91
+ return exec_without_profiling(*args,&blk) unless current
92
+
93
+ start = Time.now
94
+ result = exec_without_profiling(*args,&blk)
95
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
96
+ result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
97
+
98
+ result
99
+ end
100
+
101
+ def async_exec(*args,&blk)
102
+ current = ::Rack::MiniProfiler.current
103
+ return exec_without_profiling(*args,&blk) unless current
104
+
105
+ start = Time.now
106
+ result = exec_without_profiling(*args,&blk)
107
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
108
+ result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
109
+
110
+ result
111
+ end
112
+
113
+ alias_method :query, :exec
114
+ end
115
+
116
+ SqlPatches.patched = true
117
+ end
118
+
119
+
120
+
121
+ # Fallback for sequel
122
+ if SqlPatches.class_exists?("Sequel::Database") && !SqlPatches.patched?
10
123
  module Sequel
11
124
  class Database
12
125
  alias_method :log_duration_original, :log_duration
13
126
  def log_duration(duration, message)
14
- ::Rack::MiniProfiler.instance.record_sql(message, duration) if ::Rack::MiniProfiler.instance
127
+ ::Rack::MiniProfiler.record_sql(message, duration)
15
128
  log_duration_original(duration, message)
16
129
  end
17
130
  end
@@ -20,6 +133,7 @@ end
20
133
 
21
134
 
22
135
  ## based off https://github.com/newrelic/rpm/blob/master/lib/new_relic/agent/instrumentation/active_record.rb
136
+ ## fallback for alls sorts of weird dbs
23
137
  module Rack
24
138
  class MiniProfiler
25
139
  module ActiveRecordInstrumentation
@@ -34,19 +148,18 @@ module Rack
34
148
  end
35
149
 
36
150
  def log_with_miniprofiler(*args, &block)
151
+ current = ::Rack::MiniProfiler.current
152
+ return log_without_miniprofiler(*args, &block) unless current
153
+
37
154
  sql, name, binds = args
38
155
  t0 = Time.now
39
156
  rval = log_without_miniprofiler(*args, &block)
40
-
41
- # Get our MP Instance
42
- instance = ::Rack::MiniProfiler.instance
43
- return rval unless instance
44
-
157
+
45
158
  # Don't log schema queries if the option is set
46
- return rval if instance.options[:skip_schema_queries] and name =~ /SCHEMA/
159
+ return rval if Rack::MiniProfiler.config.skip_schema_queries and name =~ /SCHEMA/
47
160
 
48
161
  elapsed_time = ((Time.now - t0).to_f * 1000).round(1)
49
- instance.record_sql(sql, elapsed_time)
162
+ Rack::MiniProfiler.record_sql(sql, elapsed_time)
50
163
  rval
51
164
  end
52
165
  end
@@ -58,15 +171,8 @@ module Rack
58
171
  end
59
172
  end
60
173
 
61
- if defined?(::Rails)
62
- if ::Rails::VERSION::MAJOR.to_i == 3
63
- # in theory this is the right thing to do for rails 3 ... but it seems to work anyway
64
- #Rails.configuration.after_initialize do
65
- insert_instrumentation
66
- #end
67
- else
68
- insert_instrumentation
69
- end
174
+ if defined?(::Rails) && !SqlPatches.patched?
175
+ insert_instrumentation
70
176
  end
71
177
  end
72
178