Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: Introduce pp=async-flamegraph for asynchronous flamegraphs #494

Merged
merged 6 commits into from Apr 28, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/html/includes.js
Expand Up @@ -1213,6 +1213,9 @@ var _MiniProfiler = (function() {
shareUrl: function shareUrl(id) {
return options.path + "results?id=" + id;
},
flamegraphUrl: function flamegrapgUrl(id) {
return options.path + "flamegraph?id=" + id;
},
moreUrl: function moreUrl(requestName) {
var requestParts = requestName.split(" ");
var linkSrc =
Expand Down
3 changes: 3 additions & 0 deletions lib/html/includes.tmpl
Expand Up @@ -142,6 +142,9 @@
<script id="linksTemplate" type="text/x-dot-tmpl">
<a href="{{= MiniProfiler.shareUrl(it.page.id) }}" class="profiler-share-profiler-results" target="_blank">share</a>
<a href="{{= MiniProfiler.moreUrl(it.timing.name) }}" class="profiler-more-actions">more</a>
{{? it.page.flamegraph}}
OsamaSayegh marked this conversation as resolved.
Show resolved Hide resolved
<a href="{{= MiniProfiler.flamegraphUrl(it.page.id) }}" class="profiler-show-flamegraph" target="_blank">flamegraph</a>
{{?}}
{{? it.custom_link}}
<a href="{{= it.custom_link }}" class="profiler-custom-link" target="_blank">{{= it.custom_link_name }}</a>
{{?}}
Expand Down
2 changes: 1 addition & 1 deletion lib/html/vendor.js
Expand Up @@ -11,7 +11,7 @@ var out=' <div class="profiler-result"> <div class="profiler-button ';if(it.has_
}
MiniProfiler.templates["linksTemplate"] = function anonymous(it
) {
var out=' <a href="'+( MiniProfiler.shareUrl(it.page.id) )+'" class="profiler-share-profiler-results" target="_blank">share</a> <a href="'+( MiniProfiler.moreUrl(it.timing.name) )+'" class="profiler-more-actions">more</a> ';if(it.custom_link){out+=' <a href="'+( it.custom_link )+'" class="profiler-custom-link" target="_blank">'+( it.custom_link_name )+'</a> ';}out+=' ';if(it.page.has_trivial_timings){out+=' <a class="profiler-toggle-trivial" data-show-on-load="'+( it.page.has_all_trivial_timings )+'" title="toggles any rows with &lt; '+( it.page.trivial_duration_threshold_milliseconds )+' ms"> show trivial </a> ';}return out;
var out=' <a href="'+( MiniProfiler.shareUrl(it.page.id) )+'" class="profiler-share-profiler-results" target="_blank">share</a> <a href="'+( MiniProfiler.moreUrl(it.timing.name) )+'" class="profiler-more-actions">more</a> ';if(it.page.flamegraph){out+=' <a href="'+( MiniProfiler.flamegraphUrl(it.page.id) )+'" class="profiler-show-flamegraph" target="_blank">flamegraph</a> ';}out+=' ';if(it.custom_link){out+=' <a href="'+( it.custom_link )+'" class="profiler-custom-link" target="_blank">'+( it.custom_link_name )+'</a> ';}out+=' ';if(it.page.has_trivial_timings){out+=' <a class="profiler-toggle-trivial" data-show-on-load="'+( it.page.has_all_trivial_timings )+'" title="toggles any rows with &lt; '+( it.page.trivial_duration_threshold_milliseconds )+' ms"> show trivial </a> ';}return out;
}
MiniProfiler.templates["timingTemplate"] = function anonymous(it
) {
Expand Down
26 changes: 24 additions & 2 deletions lib/mini_profiler/profiler.rb
Expand Up @@ -182,6 +182,7 @@ def serve_html(env)

return serve_results(env) if file_name.eql?('results')
return handle_snapshots_request(env) if file_name.eql?('snapshots')
return serve_flamegraph(env) if file_name.eql?('flamegraph')

resources_env = env.dup
resources_env['PATH_INFO'] = file_name
Expand Down Expand Up @@ -345,7 +346,7 @@ def call(env)
# Prevent response body from being compressed
env['HTTP_ACCEPT_ENCODING'] = 'identity' if config.suppress_encoding

if query_string =~ /pp=flamegraph/
if query_string =~ /pp=(async-)?flamegraph/ || env['HTTP_REFERER'] =~ /pp=async-flamegraph/
unless defined?(StackProf) && StackProf.respond_to?(:run)

flamegraph = "Please install the stackprof gem and require it: add gem 'stackprof' to your Gemfile"
Expand Down Expand Up @@ -429,9 +430,11 @@ def call(env)
page_struct[:user] = user(env)
page_struct[:root].record_time((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000)

if flamegraph
if flamegraph && query_string =~ /pp=flamegraph/
body.close if body.respond_to? :close
return client_settings.handle_cookie(self.flamegraph(flamegraph, path))
elsif flamegraph # async-flamegraph
page_struct[:flamegraph] = flamegraph
end

begin
Expand Down Expand Up @@ -651,6 +654,7 @@ def help(client_settings, env)
#{make_link "profile-gc", env} : perform gc profiling on this request, analyzes ObjectSpace generated by request
#{make_link "profile-memory", env} : requires the memory_profiler gem, new location based report
#{make_link "flamegraph", env} : a graph representing sampled activity (requires the stackprof gem).
#{make_link "async-flamegraph", env} : store flamegraph data for this page and all its AJAX requests. Flamegraph links will be available in the mini-profiler UI (requires the stackprof gem).
#{make_link "flamegraph&flamegraph_sample_rate=1", env}: creates a flamegraph with the specified sample rate (in ms). Overrides value set in config
#{make_link "flamegraph_embed", env} : a graph representing sampled activity (requires the stackprof gem), embedded resources for use on an intranet.
#{make_link "trace-exceptions", env} : will return all the spots where your application raises exceptions
Expand Down Expand Up @@ -824,6 +828,24 @@ def handle_snapshots_request(env)
response.finish
end

def serve_flamegraph(env)
request = Rack::Request.new(env)
id = request.params['id']
page_struct = @storage.load(id)

if !page_struct
id = ERB::Util.html_escape(id)
user_info = ERB::Util.html_escape(user(env))
return [404, {}, ["Request not found: #{id} - user #{user_info}"]]
end

if !page_struct[:flamegraph]
return [404, {}, ["No flamegraph available for #{ERB::Util.html_escape(id)}"]]
end

self.flamegraph(page_struct[:flamegraph], page_struct[:request_path])
end

def rails_route_from_path(path, method)
if defined?(Rails) && defined?(ActionController::RoutingError)
hash = Rails.application.routes.recognize_path(path, method: method)
Expand Down
3 changes: 2 additions & 1 deletion lib/mini_profiler/timer_struct/page.rb
Expand Up @@ -87,7 +87,8 @@ def initialize(env)
executed_non_queries: 0,
custom_timing_names: [],
custom_timing_stats: {},
custom_fields: {}
custom_fields: {},
flamegraph: nil
)
self[:request_method] = env['REQUEST_METHOD']
self[:request_path] = env['PATH_INFO']
Expand Down