class Mmtrix::Rack::DeveloperMode
This middleware provides the ‘developer mode’ feature of mmtrix_rpm, which allows you to see data about local web transactions in development mode immediately without needing to send this data to Mmtrix’s servers.
Enabling developer mode has serious performance and security impact, and thus you should never use this middleware in a production or non-local environment.
This middleware should be automatically inserted in most contexts, but if automatic middleware insertion fails, you may manually insert it into your middleware chain.
@api public
Constants
- HELPER_PATH
- REQUEST_PARAMETERS_PREFIX
- VIEW_PATH
Attributes
Public Class Methods
# File lib/mmtrix/rack/developer_mode.rb, line 43 def self.profiling_enabled? @profiling_enabled end
Public Instance Methods
# File lib/mmtrix/rack/developer_mode.rb, line 47 def traced_call(env) return @app.call(env) unless /^\/mmtrix/ =~ ::Rack::Request.new(env).path_info dup._call(env) end
Protected Instance Methods
# File lib/mmtrix/rack/developer_mode.rb, line 54 def _call(env) Mmtrix::Agent.ignore_transaction @req = ::Rack::Request.new(env) @rendered = false case @req.path_info when /profile/ profile when /file/ ::Rack::File.new(VIEW_PATH).call(env) when /index/ index when /threads/ threads when /reset/ reset when /show_sample_detail/ show_sample_data when /show_sample_summary/ show_sample_data when /show_sample_sql/ show_sample_data when /explain_sql/ explain_sql when /^\/mmtrix\/?$/ index else @app.call(env) end end
Private Instance Methods
# File lib/mmtrix/rack/developer_mode.rb, line 272 def breakdown_data(sample, limit = nil) metric_hash = {} sample.each_node_with_nest_tracking do |node| unless node == sample.root_node metric_name = node.metric_name metric_hash[metric_name] ||= SegmentSummary.new(metric_name, sample) metric_hash[metric_name] << node metric_hash[metric_name] end end data = metric_hash.values data.sort! do |x,y| y.exclusive_time <=> x.exclusive_time end if limit && data.length > limit data = data[0..limit - 1] end # add one last node for the remaining time if any remainder = sample.duration data.each do |node| remainder -= node.exclusive_time end if (remainder*1000).round > 0 remainder_summary = SegmentSummary.new('Remainder', sample) remainder_summary.total_time = remainder_summary.exclusive_time = remainder remainder_summary.call_count = 1 data << remainder_summary end data end
# File lib/mmtrix/rack/developer_mode.rb, line 186 def content_tag(tag, contents, opts={}) opt_values = opts.map {|k, v| "#{k}=\"#{v}\"" }.join(' ') "<#{tag} #{opt_values}>#{contents}</#{tag}>" end
# File lib/mmtrix/rack/developer_mode.rb, line 258 def custom_attributes_for(sample) sample.attributes.custom_attributes_for(Mmtrix::Agent::AttributeFilter::DST_DEVELOPER_MODE) end
# File lib/mmtrix/rack/developer_mode.rb, line 98 def explain_sql get_segment return render(:sample_not_found) unless @sample @sql = @segment[:sql] @trace = @segment[:backtrace] if Mmtrix::Agent.agent.record_sql == :obfuscated @obfuscated_sql = @segment.obfuscated_sql end _headers, explanations = @segment.explain_sql if explanations @explanation = explanations if !@explanation.blank? first_row = @explanation.first # Show the standard headers if it looks like a mysql explain plan # Otherwise show blank headers if first_row.length < Mmtrix::MYSQL_EXPLAIN_COLUMNS.length @row_headers = nil else @row_headers = Mmtrix::MYSQL_EXPLAIN_COLUMNS end end end render(:explain_sql) end
# File lib/mmtrix/rack/developer_mode.rb, line 238 def get_sample get_samples id = params['id'] sample_id = id.to_i @samples.each do |s| if s.sample_id == sample_id @sample = s return end end end
# File lib/mmtrix/rack/developer_mode.rb, line 228 def get_samples @samples = Mmtrix::Agent.instance.transaction_sampler.dev_mode_sample_buffer.samples.select do |sample| sample.transaction_name != nil end return @samples = @samples.sort_by(&:duration).reverse if params['h'] return @samples = @samples.sort{|x,y| x.params[:uri] <=> y.params[:uri]} if params['u'] @samples = @samples.reverse end
# File lib/mmtrix/rack/developer_mode.rb, line 250 def get_segment get_sample return unless @sample segment_id = params['segment'].to_i @segment = @sample.root_node.find_node(segment_id) end
# File lib/mmtrix/rack/developer_mode.rb, line 87 def index get_samples render(:index) end
# File lib/mmtrix/rack/developer_mode.rb, line 195 def params @req.params end
# File lib/mmtrix/rack/developer_mode.rb, line 127 def profile should_be_on = (params['start'] == 'true') Mmtrix::Rack::DeveloperMode.profiling_enabled = should_be_on index end
# File lib/mmtrix/rack/developer_mode.rb, line 138 def render(view, layout=true) add_rack_array = true if view.is_a? Hash layout = false if view[:object] # object *is* used here, as it is capture in the binding below object = view[:object] end if view[:collection] return view[:collection].map do |obj| render({:partial => view[:partial], :object => obj}) end.join(' ') end if view[:partial] add_rack_array = false view = "_#{view[:partial]}" end end binding = Proc.new {}.binding if layout body = render_with_layout(view) do render_without_layout(view, binding) end else body = render_without_layout(view, binding) end if add_rack_array ::Rack::Response.new(body, 200, {'Content-Type' => 'text/html'}).finish else body end end
You have to call this with a block - the contents returned from that block are interpolated into the layout
# File lib/mmtrix/rack/developer_mode.rb, line 175 def render_with_layout(view) body = ERB.new(File.read(File.join(VIEW_PATH, 'layouts/mmtrix_default.rhtml'))) body.result(Proc.new {}.binding) end
you have to pass a binding to this (a proc) so that ERB can have access to helper functions and local variables
# File lib/mmtrix/rack/developer_mode.rb, line 182 def render_without_layout(view, binding) ERB.new(File.read(File.join(VIEW_PATH, 'mmtrix', view.to_s + '.rhtml')), nil, nil, 'frobnitz').result(binding) end
# File lib/mmtrix/rack/developer_mode.rb, line 264 def request_attributes_for(sample) agent_attributes = sample.attributes.agent_attributes_for(Mmtrix::Agent::AttributeFilter::DST_DEVELOPER_MODE) agent_attributes.inject({}) do |memo, (key, value)| memo[key] = value if key.to_s.start_with?(REQUEST_PARAMETERS_PREFIX) memo end end
# File lib/mmtrix/rack/developer_mode.rb, line 92 def reset Mmtrix::Agent.instance.transaction_sampler.reset! Mmtrix::Agent.instance.sql_sampler.reset! ::Rack::Response.new{|r| r.redirect('/mmtrix/')}.finish end
# File lib/mmtrix/rack/developer_mode.rb, line 191 def sample @sample || @samples[0] end
# File lib/mmtrix/rack/developer_mode.rb, line 199 def segment @segment end
# File lib/mmtrix/rack/developer_mode.rb, line 203 def show_sample_data get_sample return render(:sample_not_found) unless @sample @request_params = request_attributes_for(@sample) @custom_params = custom_attributes_for(@sample) controller_metric = @sample.transaction_name metric_parser = Mmtrix::MetricParser::MetricParser.for_metric_named controller_metric @sample_controller_name = metric_parser.controller_name @sample_action_name = metric_parser.action_name @sql_segments = sql_segments(@sample) if params['d'] @sql_segments.sort!{|a,b| b.duration <=> a.duration } end sort_method = params['sort'] || :total_time @profile_options = {:min_percent => 0.5, :sort_method => sort_method.to_sym} render(:show_sample) end
return an array of sql statements executed by this transaction each element in the array contains [sql, parent_segment_metric_name, duration]
# File lib/mmtrix/rack/developer_mode.rb, line 311 def sql_segments(sample, show_non_sql_segments = true) segments = [] sample.each_node do |segment| segments << segment if segment[:sql] || segment[:sql_obfuscated] || (show_non_sql_segments && segment[:key]) end segments end
# File lib/mmtrix/rack/developer_mode.rb, line 134 def threads render(:threads) end