526 changes: 451 additions & 75 deletions Ruby/lib/html/includes.css

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Ruby/lib/mini_profiler/context.rb
@@ -1,7 +1,8 @@
class Rack::MiniProfiler::Context
attr_accessor :inject_js,:current_timer,:page_struct,:skip_backtrace,:full_backtrace,:discard, :mpt_init
attr_accessor :inject_js,:current_timer,:page_struct,:skip_backtrace,:full_backtrace,:discard, :mpt_init, :measure

def initialize(opts = {})
opts["measure"] = true unless opts.key? "measure"
opts.each do |k,v|
self.instance_variable_set('@' + k, v)
end
Expand Down
54 changes: 54 additions & 0 deletions Ruby/lib/mini_profiler/flame_graph.rb
@@ -0,0 +1,54 @@
# inspired by https://github.com/brendangregg/FlameGraph

class Rack::MiniProfiler::FlameGraph
def initialize(stacks)
@stacks = stacks
end

def graph_data
height = 0

table = []
prev = []

# a 2d array makes collapsing easy
@stacks.each_with_index do |stack, pos|
col = []

stack.reverse.map{|r| r.to_s}.each_with_index do |frame, i|

if !prev[i].nil?
last_col = prev[i]
if last_col[0] == frame
last_col[1] += 1
col << nil
next
end
end

prev[i] = [frame, 1]
col << prev[i]
end
prev = prev[0..col.length-1].to_a
table << col
end

data = []

# a 1d array makes rendering easy
table.each_with_index do |col, col_num|
col.each_with_index do |row, row_num|
next unless row && row.length == 2
data << {
:x => col_num + 1,
:y => row_num + 1,
:width => row[1],
:frame => row[0]
}
end
end

data
end

end
88 changes: 36 additions & 52 deletions Ruby/lib/mini_profiler/profiler.rb
Expand Up @@ -18,6 +18,7 @@
require 'mini_profiler/context'
require 'mini_profiler/client_settings'
require 'mini_profiler/gc_profiler'
require 'mini_profiler/flame_graph'

module Rack

Expand Down Expand Up @@ -79,28 +80,6 @@ def request_authorized?
Thread.current[:mp_authorized]
end

# Add a custom timing. These are displayed similar to SQL/query time in
# columns expanding to the right.
#
# type - String counter type. Each distinct type gets its own column.
# duration_ms - Duration of the call in ms. Either this or a block must be
# given but not both.
#
# When a block is given, calculate the duration by yielding to the block
# and keeping a record of its run time.
#
# Returns the result of the block, or nil when no block is given.
def counter(type, duration_ms=nil)
result = nil
if block_given?
start = Time.now
result = yield
duration_ms = (Time.now - start).to_f * 1000
end
return result if current.nil? || !request_authorized?
current.current_timer.add_custom(type, duration_ms, current.page_struct)
result
end
end

#
Expand Down Expand Up @@ -236,19 +215,13 @@ def call(env)
end

if query_string =~ /pp=profile-gc/
# begin
if query_string =~ /pp=profile-gc-time/
return Rack::MiniProfiler::GCProfiler.new.profile_gc_time(@app, env)
else
return Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env)
end
# rescue => e
# p e
# e.backtrace.each do |s|
# puts s
# end
# end
if query_string =~ /pp=profile-gc-time/
return Rack::MiniProfiler::GCProfiler.new.profile_gc_time(@app, env)
else
return Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env)
end
end

MiniProfiler.create_current(env, @config)
MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
if query_string =~ /pp=normal-backtrace/
Expand All @@ -266,31 +239,25 @@ def call(env)
done_sampling = false
quit_sampler = false
backtraces = nil
stacktrace_installed = true
if query_string =~ /pp=sample/
if query_string =~ /pp=sample/ || query_string =~ /pp=flamegraph/
current.measure = false
skip_frames = 0
backtraces = []
t = Thread.current

begin
require 'stacktrace'
skip_frames = stacktrace.length
rescue LoadError
stacktrace_installed = false
end

Thread.new {
# new in Ruby 2.0
has_backtrace_locations = t.respond_to?(:backtrace_locations)
begin
i = 10000 # for sanity never grab more than 10k samples
while i > 0
break if done_sampling
i -= 1
if stacktrace_installed
backtraces << t.stacktrace(0,-(1+skip_frames), StackFrame::Flags::METHOD | StackFrame::Flags::KLASS)
else
backtraces << t.backtrace
end
sleep 0.001
backtraces << (has_backtrace_locations ? t.backtrace_locations : t.backtrace)

# On my machine using Ruby 2.0 this give me excellent fidelity of stack trace per 1.2ms
# with this fidelity analysis becomes very powerful
sleep 0.0005
end
ensure
quit_sampler = true
Expand Down Expand Up @@ -345,7 +312,11 @@ def call(env)

if backtraces
body.close if body.respond_to? :close
return analyze(backtraces, page_struct)
if query_string =~ /pp=sample/
return analyze(backtraces, page_struct)
else
return flame_graph(backtraces, page_struct)
end
end


Expand Down Expand Up @@ -447,17 +418,30 @@ def help(client_settings)
pp=no-backtrace #{"(*) " if client_settings.backtrace_none?}: don't collect stack traces from all the SQL executed (sticky, use pp=normal-backtrace to enable)
pp=normal-backtrace #{"(*) " if client_settings.backtrace_default?}: collect stack traces from all the SQL executed and filter normally
pp=full-backtrace #{"(*) " if client_settings.backtrace_full?}: enable full backtraces for SQL executed (use pp=normal-backtrace to disable)
pp=sample : sample stack traces and return a report isolating heavy usage (experimental works best with the stacktrace gem)
pp=sample : sample stack traces and return a report isolating heavy usage (works best on Ruby 2.0)
pp=disable : disable profiling for this session
pp=enable : enable profiling for this session (if previously disabled)
pp=profile-gc: perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only)
pp=profile-gc-time: perform built-in gc profiling on this request (ruby 1.9.3 only)
pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity.
"

client_settings.write!(headers)
[200, headers, [body]]
end

def flame_graph(traces, page_struct)
graph = FlameGraph.new(traces)
data = graph.graph_data

headers = {'Content-Type' => 'text/html'}

body = IO.read(::File.expand_path('../html/flamegraph.html', ::File.dirname(__FILE__)))
body.gsub!("/*DATA*/", ::JSON.generate(data));

[200, headers, [body]]
end

def analyze(traces, page_struct)
headers = {'Content-Type' => 'text/plain'}
body = "Collected: #{traces.count} stack traces. Duration(ms): #{page_struct.duration_ms}"
Expand All @@ -468,7 +452,7 @@ def analyze(traces, page_struct)
fulldump << "\n\n"
distinct = {}
trace.each do |frame|
frame = "#{frame.klass}::#{frame.method}" unless String === frame
frame = frame.to_s unless String === frame
unless distinct[frame]
distinct[frame] = true
seen[frame] ||= 0
Expand Down
23 changes: 23 additions & 0 deletions Ruby/lib/mini_profiler/profiling_methods.rb
Expand Up @@ -96,6 +96,29 @@ def profile_method(klass, method, &blk)
end
klass.send :alias_method, method, with_profiling
end

# Add a custom timing. These are displayed similar to SQL/query time in
# columns expanding to the right.
#
# type - String counter type. Each distinct type gets its own column.
# duration_ms - Duration of the call in ms. Either this or a block must be
# given but not both.
#
# When a block is given, calculate the duration by yielding to the block
# and keeping a record of its run time.
#
# Returns the result of the block, or nil when no block is given.
def counter(type, duration_ms=nil)
result = nil
if block_given?
start = Time.now
result = yield
duration_ms = (Time.now - start).to_f * 1000
end
return result if current.nil? || !request_authorized?
current.current_timer.add_custom(type, duration_ms, current.page_struct)
result
end

private

Expand Down
4 changes: 2 additions & 2 deletions Ruby/lib/mini_profiler/version.rb
@@ -1,5 +1,5 @@
module Rack
class MiniProfiler
VERSION = '33d69ecf833daec8db07a9a0b6cf0bd3'.freeze
class MiniProfiler
VERSION = '843cab636fb537adf19b58eff230ff75'.freeze
end
end
18 changes: 9 additions & 9 deletions Ruby/lib/patches/sql_patches.rb
Expand Up @@ -42,7 +42,7 @@ class Mysql2::Client
alias_method :query_without_profiling, :query
def query(*args,&blk)
current = ::Rack::MiniProfiler.current
return query_without_profiling(*args,&blk) unless current
return query_without_profiling(*args,&blk) unless current && current.measure

start = Time.now
result = query_without_profiling(*args,&blk)
Expand Down Expand Up @@ -105,14 +105,14 @@ def prepare(*args,&blk)
@prepare_map = {} if @prepare_map.length > 1000

current = ::Rack::MiniProfiler.current
return prepare_without_profiling(*args,&blk) unless current
return prepare_without_profiling(*args,&blk) unless current && current.measure

prepare_without_profiling(*args,&blk)
end

def exec(*args,&blk)
current = ::Rack::MiniProfiler.current
return exec_without_profiling(*args,&blk) unless current
return exec_without_profiling(*args,&blk) unless current && current.measure

start = Time.now
result = exec_without_profiling(*args,&blk)
Expand All @@ -124,7 +124,7 @@ def exec(*args,&blk)

def exec_prepared(*args,&blk)
current = ::Rack::MiniProfiler.current
return exec_prepared_without_profiling(*args,&blk) unless current
return exec_prepared_without_profiling(*args,&blk) unless current && current.measure

start = Time.now
result = exec_prepared_without_profiling(*args,&blk)
Expand All @@ -138,7 +138,7 @@ def exec_prepared(*args,&blk)

def send_query_prepared(*args,&blk)
current = ::Rack::MiniProfiler.current
return send_query_prepared_without_profiling(*args,&blk) unless current
return send_query_prepared_without_profiling(*args,&blk) unless current && current.measure

start = Time.now
result = send_query_prepared_without_profiling(*args,&blk)
Expand All @@ -152,7 +152,7 @@ def send_query_prepared(*args,&blk)

def async_exec(*args,&blk)
current = ::Rack::MiniProfiler.current
return exec_without_profiling(*args,&blk) unless current
return exec_without_profiling(*args,&blk) unless current && current.measure

start = Time.now
result = exec_without_profiling(*args,&blk)
Expand All @@ -175,7 +175,7 @@ class Moped::Node
alias_method :process_without_profiling, :process
def process(*args,&blk)
current = ::Rack::MiniProfiler.current
return process_without_profiling(*args,&blk) unless current
return process_without_profiling(*args,&blk) unless current && current.measure

start = Time.now
result = process_without_profiling(*args,&blk)
Expand All @@ -192,7 +192,7 @@ class RSolr::Connection
alias_method :execute_without_profiling, :execute
def execute_with_profiling(client, request_context)
current = ::Rack::MiniProfiler.current
return execute_without_profiling(client, request_context) unless current
return execute_without_profiling(client, request_context) unless current && current.measure

start = Time.now
result = execute_without_profiling(client, request_context)
Expand Down Expand Up @@ -243,7 +243,7 @@ def self.included(instrumented_class)

def log_with_miniprofiler(*args, &block)
current = ::Rack::MiniProfiler.current
return log_without_miniprofiler(*args, &block) unless current
return log_without_miniprofiler(*args, &block) unless current && current.measure

sql, name, binds = args
t0 = Time.now
Expand Down
1 change: 0 additions & 1 deletion Ruby/spec/components/file_store_spec.rb
@@ -1,4 +1,3 @@

require 'spec_helper'
require 'rack-mini-profiler'
require 'mini_profiler/page_timer_struct'
Expand Down
37 changes: 37 additions & 0 deletions Ruby/spec/components/flame_graph_spec.rb
@@ -0,0 +1,37 @@
require 'spec_helper'
require 'rack-mini-profiler'
require 'mini_profiler/flame_graph'

describe Rack::MiniProfiler::FlameGraph do

it "builds a table correctly" do
stacks = [["3","2","1"],["4","1"],["4","5"]]

g = Rack::MiniProfiler::FlameGraph.new(stacks)

g.graph_data.should == [
{:x => 1, :y => 1, :frame => "1", :width => 2},
{:x => 1, :y => 2, :frame => "2", :width => 1},
{:x => 1, :y => 3, :frame => "3", :width => 1},
{:x => 2, :y => 2, :frame => "4", :width => 2},
{:x => 3, :y => 1, :frame => "5", :width => 1}
]

end


it "avoids bridges" do
stacks = [["3","2","1"],["1"],["3","2","1"]]

g = Rack::MiniProfiler::FlameGraph.new(stacks)

g.graph_data.should == [
{:x => 1, :y => 1, :frame => "1", :width => 3},
{:x => 1, :y => 2, :frame => "2", :width => 1},
{:x => 1, :y => 3, :frame => "3", :width => 1},
{:x => 3, :y => 2, :frame => "2", :width => 1},
{:x => 3, :y => 3, :frame => "3", :width => 1}
]
end

end