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 80 81 82 83 84 85 86
      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

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