1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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
87
88
89
90
91
92
93
94
95
96
97
98
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