2019-02-15 15:39:39 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2015-11-26 14:37:03 +05:30
|
|
|
module Gitlab
|
|
|
|
module Sherlock
|
|
|
|
# Class for profiling code on a per line basis.
|
|
|
|
#
|
|
|
|
# The LineProfiler class can be used to profile code on per line basis
|
|
|
|
# without littering your code with Ruby implementation specific profiling
|
|
|
|
# methods.
|
|
|
|
#
|
|
|
|
# This profiler only includes samples taking longer than a given threshold
|
|
|
|
# and those that occur in the actual application (e.g. files from Gems are
|
|
|
|
# ignored).
|
|
|
|
class LineProfiler
|
|
|
|
# The minimum amount of time that has to be spent in a file for it to be
|
|
|
|
# included in a list of samples.
|
|
|
|
MINIMUM_DURATION = 10.0
|
|
|
|
|
|
|
|
# Profiles the given block.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
#
|
|
|
|
# profiler = LineProfiler.new
|
|
|
|
#
|
|
|
|
# retval, samples = profiler.profile do
|
|
|
|
# "cats are amazing"
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# retval # => "cats are amazing"
|
|
|
|
# samples # => [#<Gitlab::Sherlock::FileSample ...>, ...]
|
|
|
|
#
|
|
|
|
# Returns an Array containing the block's return value and an Array of
|
|
|
|
# FileSample objects.
|
|
|
|
def profile(&block)
|
|
|
|
if mri?
|
|
|
|
profile_mri(&block)
|
|
|
|
else
|
|
|
|
raise NotImplementedError,
|
|
|
|
'Line profiling is not supported on this platform'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Profiles the given block using rblineprof (MRI only).
|
|
|
|
def profile_mri
|
|
|
|
require 'rblineprof'
|
|
|
|
|
|
|
|
retval = nil
|
2020-03-09 13:42:32 +05:30
|
|
|
samples = lineprof(/^#{Rails.root}/) { retval = yield }
|
2015-11-26 14:37:03 +05:30
|
|
|
|
|
|
|
file_samples = aggregate_rblineprof(samples)
|
|
|
|
|
|
|
|
[retval, file_samples]
|
|
|
|
end
|
|
|
|
|
|
|
|
# Returns an Array of file samples based on the output of rblineprof.
|
|
|
|
#
|
|
|
|
# lineprof_stats - A Hash containing rblineprof statistics on a per file
|
|
|
|
# basis.
|
|
|
|
#
|
|
|
|
# Returns an Array of FileSample objects.
|
|
|
|
def aggregate_rblineprof(lineprof_stats)
|
|
|
|
samples = []
|
|
|
|
|
|
|
|
lineprof_stats.each do |(file, stats)|
|
|
|
|
source_lines = File.read(file).each_line.to_a
|
|
|
|
line_samples = []
|
|
|
|
|
|
|
|
total_duration = microsec_to_millisec(stats[0][0])
|
|
|
|
total_events = stats[0][2]
|
|
|
|
|
|
|
|
next if total_duration <= MINIMUM_DURATION
|
|
|
|
|
|
|
|
stats[1..-1].each_with_index do |data, index|
|
|
|
|
next unless source_lines[index]
|
|
|
|
|
|
|
|
duration = microsec_to_millisec(data[0])
|
|
|
|
events = data[2]
|
|
|
|
|
|
|
|
line_samples << LineSample.new(duration, events)
|
|
|
|
end
|
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
samples << FileSample
|
|
|
|
.new(file, line_samples, total_duration, total_events)
|
2015-11-26 14:37:03 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
samples
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def microsec_to_millisec(microsec)
|
|
|
|
microsec / 1000.0
|
|
|
|
end
|
|
|
|
|
|
|
|
def mri?
|
|
|
|
RUBY_ENGINE == 'ruby'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|