class NewRelic::Rack::DeveloperMode

This middleware provides the 'developer mode' feature of newrelic_rpm, which allows you to see data about local web transactions in development mode immediately without needing to send this data to New Relic'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

profiling_enabled[W]

Public Class Methods

profiling_enabled?() click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 42
def self.profiling_enabled?
  @profiling_enabled
end

Public Instance Methods

traced_call(env) click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 46
def traced_call(env)
  return @app.call(env) unless /^\/newrelic/ =~ ::Rack::Request.new(env).path_info
  dup._call(env)
end

Protected Instance Methods

_call(env) click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 53
def _call(env)
  NewRelic::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 /^\/newrelic\/?$/
    index
  else
    @app.call(env)
  end
end

Private Instance Methods

breakdown_data(sample, limit = nil) click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 271
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
content_tag(tag, contents, opts={}) click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 185
def content_tag(tag, contents, opts={})
  opt_values = opts.map {|k, v| "#{k}=\"#{v}\"" }.join(' ')
  "<#{tag} #{opt_values}>#{contents}</#{tag}>"
end
custom_attributes_for(sample) click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 257
def custom_attributes_for(sample)
  sample.attributes.custom_attributes_for(NewRelic::Agent::AttributeFilter::DST_DEVELOPER_MODE)
end
explain_sql() click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 97
def explain_sql
  get_segment

  return render(:sample_not_found) unless @sample

  @sql = @segment[:sql]
  @trace = @segment[:backtrace]

  if NewRelic::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 < NewRelic::MYSQL_EXPLAIN_COLUMNS.length
        @row_headers = nil
      else
        @row_headers = NewRelic::MYSQL_EXPLAIN_COLUMNS
      end
    end
  end
  render(:explain_sql)
end
get_sample() click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 237
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
get_samples() click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 227
def get_samples
  @samples = NewRelic::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
get_segment() click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 249
def get_segment
  get_sample
  return unless @sample

  segment_id = params['segment'].to_i
  @segment = @sample.root_node.find_node(segment_id)
end
index() click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 86
def index
  get_samples
  render(:index)
end
params() click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 194
def params
  @req.params
end
profile() click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 126
def profile
  should_be_on = (params['start'] == 'true')
  NewRelic::Rack::DeveloperMode.profiling_enabled = should_be_on

  index
end
render(view, layout=true) click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 137
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
render_with_layout(view) click to toggle source

You have to call this with a block - the contents returned from that block are interpolated into the layout

# File lib/new_relic/rack/developer_mode.rb, line 174
def render_with_layout(view)
  body = ERB.new(File.read(File.join(VIEW_PATH, 'layouts/newrelic_default.rhtml')))
  body.result(Proc.new {}.binding)
end
render_without_layout(view, binding) click to toggle source

you have to pass a binding to this (a proc) so that ERB can have access to helper functions and local variables

# File lib/new_relic/rack/developer_mode.rb, line 181
def render_without_layout(view, binding)
  ERB.new(File.read(File.join(VIEW_PATH, 'newrelic', view.to_s + '.rhtml')), nil, nil, 'frobnitz').result(binding)
end
request_attributes_for(sample) click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 263
def request_attributes_for(sample)
  agent_attributes = sample.attributes.agent_attributes_for(NewRelic::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
reset() click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 91
def reset
  NewRelic::Agent.instance.transaction_sampler.reset!
  NewRelic::Agent.instance.sql_sampler.reset!
  ::Rack::Response.new{|r| r.redirect('/newrelic/')}.finish
end
sample() click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 190
def sample
  @sample || @samples[0]
end
segment() click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 198
def segment
  @segment
end
show_sample_data() click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 202
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 = NewRelic::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
sql_segments(sample, show_non_sql_segments = true) click to toggle source

return an array of sql statements executed by this transaction each element in the array contains [sql, parent_segment_metric_name, duration]

# File lib/new_relic/rack/developer_mode.rb, line 310
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
threads() click to toggle source
# File lib/new_relic/rack/developer_mode.rb, line 133
def threads
  render(:threads)
end