rack-mini-profiler 0.1 → 0.1.21

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.
@@ -0,0 +1,108 @@
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
+ def start_step(name)
12
+ if current
13
+ parent_timer = current.current_timer
14
+ current.current_timer = current_timer = current.current_timer.add_child(name)
15
+ [current_timer,parent_timer]
16
+ end
17
+ end
18
+
19
+ def finish_step(obj)
20
+ if obj && current
21
+ current_timer, parent_timer = obj
22
+ current_timer.record_time
23
+ current.current_timer = parent_timer
24
+ end
25
+ end
26
+
27
+ # perform a profiling step on given block
28
+ def step(name, opts = nil)
29
+ if current
30
+ parent_timer = current.current_timer
31
+ result = nil
32
+ current.current_timer = current_timer = current.current_timer.add_child(name)
33
+ begin
34
+ result = yield if block_given?
35
+ ensure
36
+ current_timer.record_time
37
+ current.current_timer = parent_timer
38
+ end
39
+ else
40
+ yield if block_given?
41
+ end
42
+ end
43
+
44
+ def unprofile_method(klass, method)
45
+
46
+ clean = clean_method_name(method)
47
+
48
+ with_profiling = ("#{clean}_with_mini_profiler").intern
49
+ without_profiling = ("#{clean}_without_mini_profiler").intern
50
+
51
+ if klass.send :method_defined?, with_profiling
52
+ klass.send :alias_method, method, without_profiling
53
+ klass.send :remove_method, with_profiling
54
+ klass.send :remove_method, without_profiling
55
+ end
56
+ end
57
+
58
+ def profile_method(klass, method, &blk)
59
+ default_name = klass.to_s + " " + method.to_s
60
+ clean = clean_method_name(method)
61
+
62
+ with_profiling = ("#{clean}_with_mini_profiler").intern
63
+ without_profiling = ("#{clean}_without_mini_profiler").intern
64
+
65
+ if klass.send :method_defined?, with_profiling
66
+ return # dont double profile
67
+ end
68
+
69
+ klass.send :alias_method, without_profiling, method
70
+ klass.send :define_method, with_profiling do |*args, &orig|
71
+ return self.send without_profiling, *args, &orig unless Rack::MiniProfiler.current
72
+
73
+ name = default_name
74
+ if blk
75
+ name =
76
+ if respond_to?(:instance_exec)
77
+ instance_exec(*args, &blk)
78
+ else
79
+ # deprecated in Rails 4.x
80
+ blk.bind(self).call(*args)
81
+ end
82
+ end
83
+
84
+ parent_timer = Rack::MiniProfiler.current.current_timer
85
+ page_struct = Rack::MiniProfiler.current.page_struct
86
+ result = nil
87
+
88
+ Rack::MiniProfiler.current.current_timer = current_timer = parent_timer.add_child(name)
89
+ begin
90
+ result = self.send without_profiling, *args, &orig
91
+ ensure
92
+ current_timer.record_time
93
+ Rack::MiniProfiler.current.current_timer = parent_timer
94
+ end
95
+ result
96
+ end
97
+ klass.send :alias_method, method, with_profiling
98
+ end
99
+
100
+ private
101
+
102
+ def clean_method_name(method)
103
+ method.to_s.gsub(/[\?\!]/, "")
104
+ end
105
+
106
+ end
107
+ end
108
+ 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,45 @@ 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
18
+ (
19
+ (
20
+ Rack::MiniProfiler.config.backtrace_includes.nil? or
21
+ Rack::MiniProfiler.config.backtrace_includes.all?{|regex| ln =~ regex}
22
+ ) and
23
+ (
24
+ Rack::MiniProfiler.config.backtrace_ignores.nil? or
25
+ Rack::MiniProfiler.config.backtrace_ignores.all?{|regex| !(ln =~ regex)}
26
+ )
27
+ )
18
28
  stack_trace << ln << "\n"
19
29
  end
20
30
  end
21
31
  end
22
32
 
33
+ @parent = parent
34
+ @page = page
35
+
23
36
  super("ExecuteType" => 3, # TODO
24
37
  "FormattedCommandString" => query,
25
38
  "StackTraceSnippet" => stack_trace,
26
- "StartMilliseconds" => (Time.now.to_f * 1000).to_i - page['Started'],
39
+ "StartMilliseconds" => ((Time.now.to_f * 1000).to_i - page['Started']) - duration_ms,
27
40
  "DurationMilliseconds" => duration_ms,
28
- "FirstFetchDurationMilliseconds" => 0,
41
+ "FirstFetchDurationMilliseconds" => duration_ms,
29
42
  "Parameters" => nil,
30
43
  "ParentTimingId" => nil,
31
44
  "IsDuplicate" => false)
32
45
  end
33
46
 
47
+ def report_reader_duration(elapsed_ms)
48
+ return if @reported
49
+ @reported = true
50
+ self["DurationMilliseconds"] += elapsed_ms
51
+ @parent["SqlTimingsDurationMilliseconds"] += elapsed_ms
52
+ @page["DurationMillisecondsInSql"] += elapsed_ms
53
+ end
54
+
34
55
  end
35
56
 
36
57
  end
File without changes
@@ -39,9 +39,14 @@ module Rack
39
39
 
40
40
  me = self
41
41
  Thread.new do
42
- while true do
43
- me.cleanup_cache if MiniProfiler.instance
44
- sleep(3600)
42
+ begin
43
+ while true do
44
+ # TODO: a sane retry count before bailing
45
+ me.cleanup_cache
46
+ sleep(3600)
47
+ end
48
+ rescue
49
+ # don't crash the thread, we can clean up next time
45
50
  end
46
51
  end
47
52
  end
@@ -83,10 +88,6 @@ module Rack
83
88
  }
84
89
  end
85
90
 
86
-
87
- private
88
-
89
-
90
91
  def cleanup_cache
91
92
  files = Dir.entries(@path)
92
93
  @timer_struct_lock.synchronize {
@@ -0,0 +1,51 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ class MemcacheStore < AbstractStore
4
+
5
+ EXPIRE_SECONDS = 60*60*24
6
+ MAX_RETRIES = 10
7
+
8
+ def initialize(client = nil, prefix = "MPMemcacheStore")
9
+ require 'dalli' unless defined? Dalli
10
+ @prefix = prefix
11
+ @client = client || Dalli::Client.new(['localhost:11211'])
12
+ end
13
+
14
+ def save(page_struct)
15
+ @client.set("#{@prefix}#{page_struct['Id']}", Marshal::dump(page_struct), EXPIRE_SECONDS)
16
+ end
17
+
18
+ def load(id)
19
+ raw = @client.get("#{@prefix}#{id}")
20
+ if raw
21
+ Marshal::load raw
22
+ end
23
+ end
24
+
25
+ def set_unviewed(user, id)
26
+ @client.add("#{@prefix}-#{user}-v", [], EXPIRE_SECONDS)
27
+ MAX_RETRIES.times do
28
+ break if @client.cas("#{@prefix}-#{user}-v", EXPIRE_SECONDS) do |ids|
29
+ ids << id unless ids.include?(id)
30
+ ids
31
+ end
32
+ end
33
+ end
34
+
35
+ def set_viewed(user, id)
36
+ @client.add("#{@prefix}-#{user}-v", [], EXPIRE_SECONDS)
37
+ MAX_RETRIES.times do
38
+ break if @client.cas("#{@prefix}-#{user}-v", EXPIRE_SECONDS) do |ids|
39
+ ids.delete id
40
+ ids
41
+ end
42
+ end
43
+ end
44
+
45
+ def get_unviewed_ids(user)
46
+ @client.get("#{@prefix}-#{user}-v") || []
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -52,17 +52,12 @@ module Rack
52
52
  }
53
53
  end
54
54
 
55
-
56
- private
57
-
58
-
59
55
  def cleanup_cache
60
56
  expire_older_than = ((Time.now.to_f - MiniProfiler::MemoryStore::EXPIRE_TIMER_CACHE) * 1000).to_i
61
57
  @timer_struct_lock.synchronize {
62
- @timer_struct_cache.delete_if { |k, v| v['Root']['StartMilliseconds'] < expire_older_than }
58
+ @timer_struct_cache.delete_if { |k, v| v['Started'] < expire_older_than }
63
59
  }
64
60
  end
65
-
66
61
  end
67
62
  end
68
63
  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
 
@@ -22,7 +22,9 @@ module Rack
22
22
  end
23
23
 
24
24
  def to_json(*a)
25
- ::JSON.generate(@attributes, a[0])
25
+ # this does could take in an option hash, but the only interesting there is max_nesting.
26
+ # if this becomes an option we could increase
27
+ ::JSON.generate( @attributes, :max_nesting => 100 )
26
28
  end
27
29
 
28
30
  end
@@ -1,30 +1,85 @@
1
+ require 'fileutils'
2
+
1
3
  module MiniProfilerRails
4
+
2
5
  class Railtie < ::Rails::Railtie
3
6
 
4
7
  initializer "rack_mini_profiler.configure_rails_initialization" do |app|
8
+ c = Rack::MiniProfiler.config
5
9
 
6
- # By default, only show the MiniProfiler in development mode
7
- Rack::MiniProfiler.configuration[:authorize_cb] = lambda {|env| Rails.env.development? }
8
- tmp = Rails.root.to_s + "/tmp/miniprofiler"
9
- Dir::mkdir(tmp) unless File.exists?(tmp)
10
- Rack::MiniProfiler.configuration[:storage_options] = {:path => tmp}
11
- Rack::MiniProfiler.configuration[:storage] = Rack::MiniProfiler::FileStore
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
+ }
12
14
 
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)/
15
+ c.skip_paths ||= []
16
+
17
+ if Rails.env.development?
18
+ c.skip_paths << "/assets/"
19
+ c.skip_schema_queries = true
20
+ end
21
+
22
+ if Rails.env.production?
23
+ c.authorization_mode = :whitelist
24
+ end
16
25
 
17
26
  # The file store is just so much less flaky
27
+ tmp = Rails.root.to_s + "/tmp/miniprofiler"
28
+ FileUtils.mkdir_p(tmp) unless File.exists?(tmp)
29
+
30
+ c.storage_options = {:path => tmp}
31
+ c.storage = Rack::MiniProfiler::FileStore
32
+
33
+ # Quiet the SQL stack traces
34
+ c.backtrace_remove = Rails.root.to_s + "/"
35
+ c.backtrace_includes = [/^\/?(app|config|lib|test)/]
36
+ c.skip_schema_queries = Rails.env != 'production'
18
37
 
19
38
  # Install the Middleware
20
- app.middleware.insert_before 'Rack::Lock', 'Rack::MiniProfiler'
39
+ app.middleware.insert(0, Rack::MiniProfiler)
21
40
 
22
41
  # Attach to various Rails methods
23
42
  ::Rack::MiniProfiler.profile_method(ActionController::Base, :process) {|action| "Executing action: #{action}"}
24
43
  ::Rack::MiniProfiler.profile_method(ActionView::Template, :render) {|x,y| "Rendering: #{@virtual_path}"}
25
44
 
26
-
27
45
  end
28
46
 
47
+ # TODO: Implement something better here
48
+ # config.after_initialize do
49
+ #
50
+ # class ::ActionView::Helpers::AssetTagHelper::JavascriptIncludeTag
51
+ # alias_method :asset_tag_orig, :asset_tag
52
+ # def asset_tag(source,options)
53
+ # current = Rack::MiniProfiler.current
54
+ # return asset_tag_orig(source,options) unless current
55
+ # wrapped = ""
56
+ # unless current.mpt_init
57
+ # current.mpt_init = true
58
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
59
+ # end
60
+ # name = source.split('/')[-1]
61
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
62
+ # wrapped
63
+ # end
64
+ # end
65
+
66
+ # class ::ActionView::Helpers::AssetTagHelper::StylesheetIncludeTag
67
+ # alias_method :asset_tag_orig, :asset_tag
68
+ # def asset_tag(source,options)
69
+ # current = Rack::MiniProfiler.current
70
+ # return asset_tag_orig(source,options) unless current
71
+ # wrapped = ""
72
+ # unless current.mpt_init
73
+ # current.mpt_init = true
74
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
75
+ # end
76
+ # name = source.split('/')[-1]
77
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
78
+ # wrapped
79
+ # end
80
+ # end
81
+
82
+ # end
83
+
29
84
  end
30
85
  end