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,9 @@
1
+ tbody tr:nth-child(odd) { background-color:#eee; }
2
+ tbody tr:nth-child(even) { background-color:#fff; }
3
+ table { border: 0; border-spacing:0;}
4
+ tr {border: 0;}
5
+ .date {font-size: 11px; color: #666;}
6
+ td {padding: 8px;}
7
+ .time {text-align:center;}
8
+ thead tr {background-color: #bbb; color: #444; font-size: 12px;}
9
+ thead tr th { padding: 5px 15px;}
@@ -0,0 +1,38 @@
1
+ var MiniProfiler = MiniProfiler || {};
2
+ MiniProfiler.list = {
3
+ init:
4
+ function (options) {
5
+ var $ = MiniProfiler.jQuery;
6
+ var opt = options || {};
7
+
8
+ var updateGrid = function (id) {
9
+ $.ajax({
10
+ url: options.path + 'results-list',
11
+ data: { "last-id": id },
12
+ dataType: 'json',
13
+ type: 'GET',
14
+ success: function (data) {
15
+ $('table tbody').append($("#rowTemplate").tmpl(data));
16
+ var oldId = id;
17
+ var oldData = data;
18
+ setTimeout(function () {
19
+ var newId = oldId;
20
+ if (oldData.length > 0) {
21
+ newId = oldData[oldData.length - 1].Id;
22
+ }
23
+ updateGrid(newId);
24
+ }, 4000);
25
+ }
26
+ });
27
+ }
28
+
29
+ MiniProfiler.path = options.path;
30
+ $.get(options.path + 'list.tmpl?v=' + options.version, function (data) {
31
+ if (data) {
32
+ $('body').append(data);
33
+ $('body').append($('#tableTemplate').tmpl());
34
+ updateGrid();
35
+ }
36
+ });
37
+ }
38
+ };
@@ -0,0 +1,34 @@
1
+ <script id="tableTemplate" type="text/x-jquery-tmpl">
2
+ <table>
3
+ <thead>
4
+ <tr>
5
+ <th>Name</th>
6
+ <th>Started</th>
7
+ <th>Sql Duration</th>
8
+ <th>Total Duration</th>
9
+ <th>Request Start</th>
10
+ <th>Response Start</th>
11
+ <th>Dom Complete</th>
12
+ </tr>
13
+ </thead>
14
+ <tbody>
15
+
16
+ </tbody>
17
+ </table>
18
+ </script>
19
+ <script id="rowTemplate" type="text/x-jquery-tmpl">
20
+ <tr>
21
+ <td>
22
+ <a href="${MiniProfiler.path}results?id=${Id}">${Name}</a></td>
23
+ <td class="date">${MiniProfiler.renderDate(Started)}</td>
24
+ <td class="time">${DurationMillisecondsInSql}</td>
25
+ <td class="time">${DurationMilliseconds}</td>
26
+ {{if ClientTimings}}
27
+ <td class="time">${MiniProfiler.getClientTimingByName(ClientTimings,"Request").Start}</td>
28
+ <td class="time">${MiniProfiler.getClientTimingByName(ClientTimings,"Response").Start}</td>
29
+ <td class="time">${MiniProfiler.getClientTimingByName(ClientTimings,"Dom Complete").Start}</td>
30
+ {{else}}
31
+ <td colspan="3"></td>
32
+ {{/if}}
33
+ </tr>
34
+ </script>
@@ -0,0 +1 @@
1
+ <script async type="text/javascript" id="mini-profiler" src="{path}includes.js?v={version}" data-version="{version}" data-path="{path}" data-current-id="{currentId}" data-ids="{ids}" data-position="{position}" data-trivial="{showTrivial}" data-children="{showChildren}" data-max-traces="{maxTracesToShow}" data-controls="{showControls}" data-authorized="{authorized}" data-toggle-shortcut="{toggleShortcut}" data-start-hidden="{startHidden}"></script>
@@ -0,0 +1,11 @@
1
+ <html>
2
+ <head>
3
+ <title>{name} ({duration} ms) - Profiling Results</title>
4
+ <script type='text/javascript' src='{path}jquery.1.7.1.js?v={version}'></script>
5
+ <script type='text/javascript'> var profiler = {json}; </script>
6
+ {includes}
7
+ </head>
8
+ <body>
9
+ <div class='profiler-result-full'></div>
10
+ </body>
11
+ </html>
@@ -0,0 +1,65 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ class ClientSettings
4
+
5
+ COOKIE_NAME = "__profilin"
6
+
7
+ BACKTRACE_DEFAULT = nil
8
+ BACKTRACE_FULL = 1
9
+ BACKTRACE_NONE = 2
10
+
11
+ attr_accessor :disable_profiling
12
+ attr_accessor :backtrace_level
13
+
14
+
15
+ def initialize(env)
16
+ request = ::Rack::Request.new(env)
17
+ @cookie = request.cookies[COOKIE_NAME]
18
+ if @cookie
19
+ @cookie.split(",").map{|pair| pair.split("=")}.each do |k,v|
20
+ @orig_disable_profiling = @disable_profiling = (v=='t') if k == "dp"
21
+ @backtrace_level = v.to_i if k == "bt"
22
+ end
23
+ end
24
+
25
+ @backtrace_level = nil if !@backtrace_level.nil? && (@backtrace_level == 0 || @backtrace_level > BACKTRACE_NONE)
26
+ @orig_backtrace_level = @backtrace_level
27
+
28
+ end
29
+
30
+ def write!(headers)
31
+ if @orig_disable_profiling != @disable_profiling || @orig_backtrace_level != @backtrace_level || @cookie.nil?
32
+ settings = {"p" => "t" }
33
+ settings["dp"] = "t" if @disable_profiling
34
+ settings["bt"] = @backtrace_level if @backtrace_level
35
+ settings_string = settings.map{|k,v| "#{k}=#{v}"}.join(",")
36
+ Rack::Utils.set_cookie_header!(headers, COOKIE_NAME, :value => settings_string, :path => '/')
37
+ end
38
+ end
39
+
40
+ def discard_cookie!(headers)
41
+ Rack::Utils.delete_cookie_header!(headers, COOKIE_NAME, :path => '/')
42
+ end
43
+
44
+ def has_cookie?
45
+ !@cookie.nil?
46
+ end
47
+
48
+ def disable_profiling?
49
+ @disable_profiling
50
+ end
51
+
52
+ def backtrace_full?
53
+ @backtrace_level == BACKTRACE_FULL
54
+ end
55
+
56
+ def backtrace_default?
57
+ @backtrace_level == BACKTRACE_DEFAULT
58
+ end
59
+
60
+ def backtrace_none?
61
+ @backtrace_level == BACKTRACE_NONE
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,78 @@
1
+ require 'mini_profiler/timer_struct'
2
+
3
+ module Rack
4
+ class MiniProfiler
5
+
6
+ # This class holds the client timings
7
+ class ClientTimerStruct < TimerStruct
8
+
9
+ def self.init_instrumentation
10
+ "<script type=\"text/javascript\">mPt=function(){var t=[];return{t:t,probe:function(n){t.push({d:new Date(),n:n})}}}()</script>"
11
+ end
12
+
13
+ def self.instrument(name,orig)
14
+ probe = "<script>mPt.probe('#{name}')</script>"
15
+ wrapped = probe
16
+ wrapped << orig
17
+ wrapped << probe
18
+ wrapped
19
+ end
20
+
21
+
22
+ def initialize(env={})
23
+ super
24
+ end
25
+
26
+ def self.init_from_form_data(env, page_struct)
27
+ timings = []
28
+ clientTimes, clientPerf, baseTime = nil
29
+ form = env['rack.request.form_hash']
30
+
31
+ clientPerf = form['clientPerformance'] if form
32
+ clientTimes = clientPerf['timing'] if clientPerf
33
+
34
+ baseTime = clientTimes['navigationStart'].to_i if clientTimes
35
+ return unless clientTimes && baseTime
36
+
37
+ probes = form['clientProbes']
38
+ translated = {}
39
+ if probes && !["null", ""].include?(probes)
40
+ probes.each do |id, val|
41
+ name = val["n"]
42
+ translated[name] ||= {}
43
+ if translated[name][:start]
44
+ translated[name][:finish] = val["d"]
45
+ else
46
+ translated[name][:start] = val["d"]
47
+ end
48
+ end
49
+ end
50
+
51
+ translated.each do |name, data|
52
+ h = {"Name" => name, "Start" => data[:start].to_i - baseTime}
53
+ h["Duration"] = data[:finish].to_i - data[:start].to_i if data[:finish]
54
+ timings.push(h)
55
+ end
56
+
57
+ clientTimes.keys.find_all{|k| k =~ /Start$/ }.each do |k|
58
+ start = clientTimes[k].to_i - baseTime
59
+ finish = clientTimes[k.sub(/Start$/, "End")].to_i - baseTime
60
+ duration = 0
61
+ duration = finish - start if finish > start
62
+ name = k.sub(/Start$/, "").split(/(?=[A-Z])/).map{|s| s.capitalize}.join(' ')
63
+ timings.push({"Name" => name, "Start" => start, "Duration" => duration}) if start >= 0
64
+ end
65
+
66
+ clientTimes.keys.find_all{|k| !(k =~ /(End|Start)$/)}.each do |k|
67
+ timings.push("Name" => k, "Start" => clientTimes[k].to_i - baseTime, "Duration" => -1)
68
+ end
69
+
70
+ rval = self.new
71
+ rval['RedirectCount'] = env['rack.request.form_hash']['clientPerformance']['navigation']['redirectCount']
72
+ rval['Timings'] = timings
73
+ rval
74
+ end
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,57 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ class Config
4
+
5
+ def self.attr_accessor(*vars)
6
+ @attributes ||= []
7
+ @attributes.concat vars
8
+ super(*vars)
9
+ end
10
+
11
+ def self.attributes
12
+ @attributes
13
+ end
14
+
15
+ attr_accessor :auto_inject, :base_url_path, :pre_authorize_cb, :position,
16
+ :backtrace_remove, :backtrace_includes, :backtrace_ignores, :skip_schema_queries,
17
+ :storage, :user_provider, :storage_instance, :storage_options, :skip_paths, :authorization_mode,
18
+ :toggle_shortcut, :start_hidden
19
+
20
+ # Deprecated options
21
+ attr_accessor :use_existing_jquery
22
+
23
+ def self.default
24
+ new.instance_eval {
25
+ @auto_inject = true # automatically inject on every html page
26
+ @base_url_path = "/mini-profiler-resources/"
27
+
28
+ # called prior to rack chain, to ensure we are allowed to profile
29
+ @pre_authorize_cb = lambda {|env| true}
30
+
31
+ # called after rack chain, to ensure we are REALLY allowed to profile
32
+ @position = 'left' # Where it is displayed
33
+ @skip_schema_queries = false
34
+ @storage = MiniProfiler::MemoryStore
35
+ @user_provider = Proc.new{|env| Rack::Request.new(env).ip}
36
+ @authorization_mode = :allow_all
37
+ @toggle_shortcut = 'Alt+P'
38
+ @start_hidden = false
39
+ self
40
+ }
41
+ end
42
+
43
+ def merge!(config)
44
+ return unless config
45
+ if Hash === config
46
+ config.each{|k,v| instance_variable_set "@#{k}",v}
47
+ else
48
+ self.class.attributes.each{ |k|
49
+ v = config.send k
50
+ instance_variable_set "@#{k}", v if v
51
+ }
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,11 @@
1
+ class Rack::MiniProfiler::Context
2
+ attr_accessor :inject_js,:current_timer,:page_struct,:skip_backtrace,:full_backtrace,:discard, :mpt_init, :measure
3
+
4
+ def initialize(opts = {})
5
+ opts["measure"] = true unless opts.key? "measure"
6
+ opts.each do |k,v|
7
+ self.instance_variable_set('@' + k, v)
8
+ end
9
+ end
10
+
11
+ end
@@ -0,0 +1,22 @@
1
+ require 'mini_profiler/timer_struct'
2
+
3
+ module Rack
4
+ class MiniProfiler
5
+
6
+ # Timing system for a custom timers such as cache, redis, RPC, external API
7
+ # calls, etc.
8
+ class CustomTimerStruct < TimerStruct
9
+ def initialize(type, duration_ms, page, parent)
10
+ @parent = parent
11
+ @page = page
12
+ @type = type
13
+
14
+ super("Type" => type,
15
+ "StartMilliseconds" => ((Time.now.to_f * 1000).to_i - page['Started']) - duration_ms,
16
+ "DurationMilliseconds" => duration_ms,
17
+ "ParentTimingId" => nil)
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,54 @@
1
+ # inspired by https://github.com/brendangregg/FlameGraph
2
+
3
+ class Rack::MiniProfiler::FlameGraph
4
+ def initialize(stacks)
5
+ @stacks = stacks
6
+ end
7
+
8
+ def graph_data
9
+ height = 0
10
+
11
+ table = []
12
+ prev = []
13
+
14
+ # a 2d array makes collapsing easy
15
+ @stacks.each_with_index do |stack, pos|
16
+ col = []
17
+
18
+ stack.reverse.map{|r| r.to_s}.each_with_index do |frame, i|
19
+
20
+ if !prev[i].nil?
21
+ last_col = prev[i]
22
+ if last_col[0] == frame
23
+ last_col[1] += 1
24
+ col << nil
25
+ next
26
+ end
27
+ end
28
+
29
+ prev[i] = [frame, 1]
30
+ col << prev[i]
31
+ end
32
+ prev = prev[0..col.length-1].to_a
33
+ table << col
34
+ end
35
+
36
+ data = []
37
+
38
+ # a 1d array makes rendering easy
39
+ table.each_with_index do |col, col_num|
40
+ col.each_with_index do |row, row_num|
41
+ next unless row && row.length == 2
42
+ data << {
43
+ :x => col_num + 1,
44
+ :y => row_num + 1,
45
+ :width => row[1],
46
+ :frame => row[0]
47
+ }
48
+ end
49
+ end
50
+
51
+ data
52
+ end
53
+
54
+ end
@@ -0,0 +1,107 @@
1
+ class Rack::MiniProfiler::GCProfiler
2
+
3
+ def object_space_stats
4
+ stats = {}
5
+ ids = Set.new
6
+ i=0
7
+ ObjectSpace.each_object { |o|
8
+ begin
9
+ i = stats[o.class] || 0
10
+ i += 1
11
+ stats[o.class] = i
12
+ ids << o.object_id if Integer === o.object_id
13
+ rescue NoMethodError
14
+ # Redis::Future undefines .class and .object_id super weird
15
+ end
16
+ }
17
+ {:stats => stats, :ids => ids}
18
+ end
19
+
20
+ def diff_object_stats(before,after)
21
+ diff = {}
22
+ after.each do |k,v|
23
+ diff[k] = v - (before[k] || 0)
24
+ end
25
+ before.each do |k,v|
26
+ diff[k] = 0 - v unless after[k]
27
+ end
28
+
29
+ diff
30
+ end
31
+
32
+ def analyze_strings(ids_before,ids_after)
33
+ result = {}
34
+ ids_after.each do |id|
35
+ obj = ObjectSpace._id2ref(id)
36
+ if String === obj && !ids_before.include?(obj.object_id)
37
+ result[obj] ||= 0
38
+ result[obj] += 1
39
+ end
40
+ end
41
+ result
42
+ end
43
+
44
+ def profile_gc_time(app,env)
45
+ body = []
46
+
47
+ begin
48
+ GC::Profiler.clear
49
+ GC::Profiler.enable
50
+ b = app.call(env)[2]
51
+ b.close if b.respond_to? :close
52
+ body << "GC Profiler ran during this request, if it fired you will see the cost below:\n\n"
53
+ body << GC::Profiler.result
54
+ ensure
55
+ GC.enable
56
+ GC::Profiler.disable
57
+ end
58
+
59
+ return [200, {'Content-Type' => 'text/plain'}, body]
60
+ end
61
+
62
+ def profile_gc(app,env)
63
+
64
+ body = [];
65
+
66
+ stat_before,stat_after,diff,string_analysis = nil
67
+ begin
68
+ GC.disable
69
+ stat_before = object_space_stats
70
+ b = app.call(env)[2]
71
+ b.close if b.respond_to? :close
72
+ stat_after = object_space_stats
73
+
74
+ diff = diff_object_stats(stat_before[:stats],stat_after[:stats])
75
+ string_analysis = analyze_strings(stat_before[:ids], stat_after[:ids])
76
+ ensure
77
+ GC.enable
78
+ end
79
+
80
+
81
+ body << "
82
+ ObjectSpace delta caused by request:
83
+ --------------------------------------------\n"
84
+ diff.to_a.reject{|k,v| v == 0}.sort{|x,y| y[1] <=> x[1]}.each do |k,v|
85
+ body << "#{k} : #{v}\n" if v != 0
86
+ end
87
+
88
+ body << "\n
89
+ ObjectSpace stats:
90
+ -----------------\n"
91
+
92
+ stat_after[:stats].to_a.sort{|x,y| y[1] <=> x[1]}.each do |k,v|
93
+ body << "#{k} : #{v}\n"
94
+ end
95
+
96
+
97
+ body << "\n
98
+ String stats:
99
+ ------------\n"
100
+
101
+ string_analysis.to_a.sort{|x,y| y[1] <=> x[1] }.take(1000).each do |string,count|
102
+ body << "#{count} : #{string}\n"
103
+ end
104
+
105
+ return [200, {'Content-Type' => 'text/plain'}, body]
106
+ end
107
+ end