99 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			99 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Ruby
		
	
	
	
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
 | 
						|
        samples = lineprof(/^#{Rails.root.to_s}/) { retval = yield }
 | 
						|
 | 
						|
        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
 | 
						|
 | 
						|
          samples << FileSample.
 | 
						|
            new(file, line_samples, total_duration, total_events)
 | 
						|
        end
 | 
						|
 | 
						|
        samples
 | 
						|
      end
 | 
						|
 | 
						|
      private
 | 
						|
 | 
						|
      def microsec_to_millisec(microsec)
 | 
						|
        microsec / 1000.0
 | 
						|
      end
 | 
						|
 | 
						|
      def mri?
 | 
						|
        RUBY_ENGINE == 'ruby'
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
end
 |