mini-mini-profiler 0.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 (39) hide show
  1. data/Ruby/CHANGELOG +135 -0
  2. data/Ruby/README.md +161 -0
  3. data/Ruby/lib/html/flamegraph.html +325 -0
  4. data/Ruby/lib/html/includes.css +451 -0
  5. data/Ruby/lib/html/includes.js +945 -0
  6. data/Ruby/lib/html/includes.less +471 -0
  7. data/Ruby/lib/html/includes.tmpl +108 -0
  8. data/Ruby/lib/html/jquery.1.7.1.js +4 -0
  9. data/Ruby/lib/html/jquery.tmpl.js +486 -0
  10. data/Ruby/lib/html/list.css +9 -0
  11. data/Ruby/lib/html/list.js +38 -0
  12. data/Ruby/lib/html/list.tmpl +34 -0
  13. data/Ruby/lib/html/profile_handler.js +1 -0
  14. data/Ruby/lib/html/share.html +11 -0
  15. data/Ruby/lib/mini_profiler/client_settings.rb +65 -0
  16. data/Ruby/lib/mini_profiler/client_timer_struct.rb +78 -0
  17. data/Ruby/lib/mini_profiler/config.rb +57 -0
  18. data/Ruby/lib/mini_profiler/context.rb +11 -0
  19. data/Ruby/lib/mini_profiler/custom_timer_struct.rb +22 -0
  20. data/Ruby/lib/mini_profiler/flame_graph.rb +54 -0
  21. data/Ruby/lib/mini_profiler/gc_profiler.rb +107 -0
  22. data/Ruby/lib/mini_profiler/page_timer_struct.rb +58 -0
  23. data/Ruby/lib/mini_profiler/profiler.rb +544 -0
  24. data/Ruby/lib/mini_profiler/profiling_methods.rb +133 -0
  25. data/Ruby/lib/mini_profiler/request_timer_struct.rb +115 -0
  26. data/Ruby/lib/mini_profiler/sql_timer_struct.rb +58 -0
  27. data/Ruby/lib/mini_profiler/storage/abstract_store.rb +31 -0
  28. data/Ruby/lib/mini_profiler/storage/file_store.rb +111 -0
  29. data/Ruby/lib/mini_profiler/storage/memcache_store.rb +53 -0
  30. data/Ruby/lib/mini_profiler/storage/memory_store.rb +65 -0
  31. data/Ruby/lib/mini_profiler/storage/redis_store.rb +54 -0
  32. data/Ruby/lib/mini_profiler/timer_struct.rb +33 -0
  33. data/Ruby/lib/mini_profiler/version.rb +5 -0
  34. data/Ruby/lib/mini_profiler_rails/railtie.rb +107 -0
  35. data/Ruby/lib/patches/net_patches.rb +14 -0
  36. data/Ruby/lib/patches/sql_patches.rb +272 -0
  37. data/Ruby/lib/rack-mini-profiler.rb +7 -0
  38. data/mini-mini-profiler.gemspec +26 -0
  39. metadata +154 -0
@@ -0,0 +1,133 @@
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, instance_method = false, &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
+ klass = ( class << klass; self; end ) if instance_method
66
+
67
+ if klass.send :method_defined?, with_profiling
68
+ return # dont double profile
69
+ end
70
+
71
+ klass.send :alias_method, without_profiling, method
72
+ klass.send :define_method, with_profiling do |*args, &orig|
73
+ return self.send without_profiling, *args, &orig unless Rack::MiniProfiler.current
74
+
75
+ name = default_name
76
+ if blk
77
+ name =
78
+ if respond_to?(:instance_exec)
79
+ instance_exec(*args, &blk)
80
+ else
81
+ # deprecated in Rails 4.x
82
+ blk.bind(self).call(*args)
83
+ end
84
+ end
85
+
86
+ parent_timer = Rack::MiniProfiler.current.current_timer
87
+ page_struct = Rack::MiniProfiler.current.page_struct
88
+ result = nil
89
+
90
+ Rack::MiniProfiler.current.current_timer = current_timer = parent_timer.add_child(name)
91
+ begin
92
+ result = self.send without_profiling, *args, &orig
93
+ ensure
94
+ current_timer.record_time
95
+ Rack::MiniProfiler.current.current_timer = parent_timer
96
+ end
97
+ result
98
+ end
99
+ klass.send :alias_method, method, with_profiling
100
+ end
101
+
102
+ # Add a custom timing. These are displayed similar to SQL/query time in
103
+ # columns expanding to the right.
104
+ #
105
+ # type - String counter type. Each distinct type gets its own column.
106
+ # duration_ms - Duration of the call in ms. Either this or a block must be
107
+ # given but not both.
108
+ #
109
+ # When a block is given, calculate the duration by yielding to the block
110
+ # and keeping a record of its run time.
111
+ #
112
+ # Returns the result of the block, or nil when no block is given.
113
+ def counter(type, duration_ms=nil)
114
+ result = nil
115
+ if block_given?
116
+ start = Time.now
117
+ result = yield
118
+ duration_ms = (Time.now - start).to_f * 1000
119
+ end
120
+ return result if current.nil? || !request_authorized?
121
+ current.current_timer.add_custom(type, duration_ms, current.page_struct)
122
+ result
123
+ end
124
+
125
+ private
126
+
127
+ def clean_method_name(method)
128
+ method.to_s.gsub(/[\?\!]/, "")
129
+ end
130
+
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,115 @@
1
+ require 'mini_profiler/timer_struct'
2
+
3
+ module Rack
4
+ class MiniProfiler
5
+
6
+ class RequestTimerStruct < TimerStruct
7
+
8
+ def self.createRoot(name, page)
9
+ rt = RequestTimerStruct.new(name, page, nil)
10
+ rt["IsRoot"]= true
11
+ rt
12
+ end
13
+
14
+ attr_accessor :children_duration
15
+
16
+ def initialize(name, page, parent)
17
+ super("Id" => MiniProfiler.generate_id,
18
+ "Name" => name,
19
+ "DurationMilliseconds" => 0,
20
+ "DurationWithoutChildrenMilliseconds"=> 0,
21
+ "StartMilliseconds" => (Time.now.to_f * 1000).to_i - page['Started'],
22
+ "ParentTimingId" => nil,
23
+ "Children" => [],
24
+ "HasChildren"=> false,
25
+ "KeyValues" => nil,
26
+ "HasSqlTimings"=> false,
27
+ "HasDuplicateSqlTimings"=> false,
28
+ "TrivialDurationThresholdMilliseconds" => 2,
29
+ "SqlTimings" => [],
30
+ "SqlTimingsDurationMilliseconds"=> 0,
31
+ "IsTrivial"=> false,
32
+ "IsRoot"=> false,
33
+ "Depth"=> parent ? parent.depth + 1 : 0,
34
+ "ExecutedReaders"=> 0,
35
+ "ExecutedScalars"=> 0,
36
+ "ExecutedNonQueries"=> 0,
37
+ "CustomTimingStats" => {},
38
+ "CustomTimings" => {})
39
+ @children_duration = 0
40
+ @start = Time.now
41
+ @parent = parent
42
+ @page = page
43
+ end
44
+
45
+ def duration_ms
46
+ self['DurationMilliseconds']
47
+ end
48
+
49
+ def start_ms
50
+ self['StartMilliseconds']
51
+ end
52
+
53
+ def start
54
+ @start
55
+ end
56
+
57
+ def depth
58
+ self['Depth']
59
+ end
60
+
61
+ def children
62
+ self['Children']
63
+ end
64
+
65
+ def add_child(name)
66
+ request_timer = RequestTimerStruct.new(name, @page, self)
67
+ self['Children'].push(request_timer)
68
+ self['HasChildren'] = true
69
+ request_timer['ParentTimingId'] = self['Id']
70
+ request_timer['Depth'] = self['Depth'] + 1
71
+ request_timer
72
+ end
73
+
74
+ def add_sql(query, elapsed_ms, page, skip_backtrace = false, full_backtrace = false)
75
+ timer = SqlTimerStruct.new(query, elapsed_ms, page, self , skip_backtrace, full_backtrace)
76
+ timer['ParentTimingId'] = self['Id']
77
+ self['SqlTimings'].push(timer)
78
+ self['HasSqlTimings'] = true
79
+ self['SqlTimingsDurationMilliseconds'] += elapsed_ms
80
+ page['DurationMillisecondsInSql'] += elapsed_ms
81
+ timer
82
+ end
83
+
84
+ def add_custom(type, elapsed_ms, page)
85
+ timer = CustomTimerStruct.new(type, elapsed_ms, page, self)
86
+ timer['ParentTimingId'] = self['Id']
87
+ self['CustomTimings'][type] ||= []
88
+ self['CustomTimings'][type].push(timer)
89
+
90
+ self['CustomTimingStats'][type] ||= {"Count" => 0, "Duration" => 0.0}
91
+ self['CustomTimingStats'][type]['Count'] += 1
92
+ self['CustomTimingStats'][type]['Duration'] += elapsed_ms
93
+
94
+ page['CustomTimingStats'][type] ||= {"Count" => 0, "Duration" => 0.0}
95
+ page['CustomTimingStats'][type]['Count'] += 1
96
+ page['CustomTimingStats'][type]['Duration'] += elapsed_ms
97
+
98
+ timer
99
+ end
100
+
101
+ def record_time(milliseconds = nil)
102
+ milliseconds ||= (Time.now - @start) * 1000
103
+ self['DurationMilliseconds'] = milliseconds
104
+ self['IsTrivial'] = true if milliseconds < self["TrivialDurationThresholdMilliseconds"]
105
+ self['DurationWithoutChildrenMilliseconds'] = milliseconds - @children_duration
106
+
107
+ if @parent
108
+ @parent.children_duration += milliseconds
109
+ end
110
+
111
+ end
112
+ end
113
+ end
114
+
115
+ end
@@ -0,0 +1,58 @@
1
+ require 'mini_profiler/timer_struct'
2
+
3
+ module Rack
4
+ class MiniProfiler
5
+
6
+ # Timing system for a SQL query
7
+ class SqlTimerStruct < TimerStruct
8
+ def initialize(query, duration_ms, page, parent, skip_backtrace = false, full_backtrace = false)
9
+
10
+ stack_trace = nil
11
+ unless skip_backtrace
12
+ # Allow us to filter the stack trace
13
+ stack_trace = ""
14
+ # Clean up the stack trace if there are options to do so
15
+ Kernel.caller.each do |ln|
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
+ )
28
+ stack_trace << ln << "\n"
29
+ end
30
+ end
31
+ end
32
+
33
+ @parent = parent
34
+ @page = page
35
+
36
+ super("ExecuteType" => 3, # TODO
37
+ "FormattedCommandString" => query,
38
+ "StackTraceSnippet" => stack_trace,
39
+ "StartMilliseconds" => ((Time.now.to_f * 1000).to_i - page['Started']) - duration_ms,
40
+ "DurationMilliseconds" => duration_ms,
41
+ "FirstFetchDurationMilliseconds" => duration_ms,
42
+ "Parameters" => nil,
43
+ "ParentTimingId" => nil,
44
+ "IsDuplicate" => false)
45
+ end
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
+
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,31 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ class AbstractStore
4
+
5
+ def save(page_struct)
6
+ raise NotImplementedError.new("save is not implemented")
7
+ end
8
+
9
+ def load(id)
10
+ raise NotImplementedError.new("load is not implemented")
11
+ end
12
+
13
+ def set_unviewed(user, id)
14
+ raise NotImplementedError.new("set_unviewed is not implemented")
15
+ end
16
+
17
+ def set_viewed(user, id)
18
+ raise NotImplementedError.new("set_viewed is not implemented")
19
+ end
20
+
21
+ def get_unviewed_ids(user)
22
+ raise NotImplementedError.new("get_unviewed_ids is not implemented")
23
+ end
24
+
25
+ def diagnostics(user)
26
+ raise NotImplementedError.new("diagnostics not implemented")
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,111 @@
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
+ EXPIRES_IN_SECONDS = 60 * 60 * 24
31
+
32
+ def initialize(args = nil)
33
+ args ||= {}
34
+ @path = args[:path]
35
+ @expires_in_seconds = args[:expires_in] || EXPIRES_IN_SECONDS
36
+ raise ArgumentError.new :path unless @path
37
+ @timer_struct_cache = FileCache.new(@path, "mp_timers")
38
+ @timer_struct_lock = Mutex.new
39
+ @user_view_cache = FileCache.new(@path, "mp_views")
40
+ @user_view_lock = Mutex.new
41
+
42
+ me = self
43
+ Thread.new do
44
+ begin
45
+ while true do
46
+ # TODO: a sane retry count before bailing
47
+ me.cleanup_cache
48
+ sleep(3600)
49
+ end
50
+ rescue
51
+ # don't crash the thread, we can clean up next time
52
+ end
53
+ end
54
+ end
55
+
56
+ def save(page_struct)
57
+ @timer_struct_lock.synchronize {
58
+ @timer_struct_cache[page_struct['Id']] = page_struct
59
+ }
60
+ end
61
+
62
+ def load(id)
63
+ @timer_struct_lock.synchronize {
64
+ @timer_struct_cache[id]
65
+ }
66
+ end
67
+
68
+ def set_unviewed(user, id)
69
+ @user_view_lock.synchronize {
70
+ current = @user_view_cache[user]
71
+ current = [] unless Array === current
72
+ current << id
73
+ @user_view_cache[user] = current.uniq
74
+ }
75
+ end
76
+
77
+ def set_viewed(user, id)
78
+ @user_view_lock.synchronize {
79
+ @user_view_cache[user] ||= []
80
+ current = @user_view_cache[user]
81
+ current = [] unless Array === current
82
+ current.delete(id)
83
+ @user_view_cache[user] = current.uniq
84
+ }
85
+ end
86
+
87
+ def get_unviewed_ids(user)
88
+ @user_view_lock.synchronize {
89
+ @user_view_cache[user]
90
+ }
91
+ end
92
+
93
+ def cleanup_cache
94
+ files = Dir.entries(@path)
95
+ @timer_struct_lock.synchronize {
96
+ files.each do |f|
97
+ f = @path + '/' + f
98
+ ::File.delete f if ::File.basename(f) =~ /^mp_timers/ and (Time.now - ::File.mtime(f)) > @expires_in_seconds
99
+ end
100
+ }
101
+ @user_view_lock.synchronize {
102
+ files.each do |f|
103
+ f = @path + '/' + f
104
+ ::File.delete f if ::File.basename(f) =~ /^mp_views/ and (Time.now - ::File.mtime(f)) > @expires_in_seconds
105
+ end
106
+ }
107
+ end
108
+
109
+ end
110
+ end
111
+ end