BigW Consortium Gitlab

line_profiler.rb 2.68 KB
Newer Older
1 2
module Gitlab
  module Sherlock
3 4 5 6 7 8 9 10 11
    # 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).
12 13 14 15 16
    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

17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
      # 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.
32
      def profile(&block)
33
        if mri?
34 35 36 37 38 39 40
          profile_mri(&block)
        else
          raise NotImplementedError,
            'Line profiling is not supported on this platform'
        end
      end

41
      # Profiles the given block using rblineprof (MRI only).
42
      def profile_mri
43 44
        require 'rblineprof'

45 46 47 48 49 50 51 52 53
        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.
54 55 56 57 58
      #
      # lineprof_stats - A Hash containing rblineprof statistics on a per file
      #                  basis.
      #
      # Returns an Array of FileSample objects.
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
      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

80 81
          samples << FileSample
            .new(file, line_samples, total_duration, total_events)
82 83 84 85 86
        end

        samples
      end

87 88
      private

89 90 91
      def microsec_to_millisec(microsec)
        microsec / 1000.0
      end
92 93 94 95

      def mri?
        RUBY_ENGINE == 'ruby'
      end
96 97 98
    end
  end
end