test-prof 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -2
- data/README.md +1 -1
- data/lib/minitest/test_prof_plugin.rb +10 -0
- data/lib/test_prof/any_fixture/dump/sqlite.rb +1 -1
- data/lib/test_prof/any_fixture.rb +1 -1
- data/lib/test_prof/before_all/adapters/active_record.rb +18 -1
- data/lib/test_prof/before_all.rb +1 -1
- data/lib/test_prof/core.rb +167 -0
- data/lib/test_prof/factory_all_stub.rb +1 -1
- data/lib/test_prof/factory_default.rb +1 -1
- data/lib/test_prof/memory_prof/minitest.rb +56 -0
- data/lib/test_prof/memory_prof/printer/number_to_human.rb +44 -0
- data/lib/test_prof/memory_prof/printer.rb +93 -0
- data/lib/test_prof/memory_prof/rspec.rb +76 -0
- data/lib/test_prof/memory_prof/tracker/linked_list.rb +119 -0
- data/lib/test_prof/memory_prof/tracker/rss_tool.rb +87 -0
- data/lib/test_prof/memory_prof/tracker.rb +84 -0
- data/lib/test_prof/memory_prof.rb +78 -0
- data/lib/test_prof/recipes/logging.rb +1 -1
- data/lib/test_prof/recipes/minitest/sample.rb +2 -2
- data/lib/test_prof/recipes/rspec/before_all.rb +2 -0
- data/lib/test_prof/recipes/rspec/let_it_be.rb +6 -4
- data/lib/test_prof/ruby_prof.rb +21 -7
- data/lib/test_prof/stack_prof/rspec.rb +2 -1
- data/lib/test_prof/stack_prof.rb +3 -3
- data/lib/test_prof/utils/sized_ordered_set.rb +4 -0
- data/lib/test_prof/vernier/rspec.rb +59 -0
- data/lib/test_prof/vernier.rb +152 -0
- data/lib/test_prof/version.rb +1 -1
- data/lib/test_prof.rb +3 -165
- metadata +19 -8
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TestProf
|
|
4
|
+
module MemoryProf
|
|
5
|
+
class Tracker
|
|
6
|
+
# LinkedList is a linked list that track memory usage for individual examples/groups.
|
|
7
|
+
# A list node (`LinkedListNode`) represents an example/group and its memory usage info:
|
|
8
|
+
#
|
|
9
|
+
# * memory_at_start - the amount of memory at the start of an example/group
|
|
10
|
+
# * memory_at_finish - the amount of memory at the end of an example/group
|
|
11
|
+
# * nested_memory - the amount of memory allocated by examples/groups defined inside a group
|
|
12
|
+
# * previous - a link to the previous node
|
|
13
|
+
#
|
|
14
|
+
# Each node has a link to its previous node, and the head node points to the current example/group.
|
|
15
|
+
# If we picture a linked list as a tree with root being the top level group and leaves being
|
|
16
|
+
# current examples/groups, then the head node will always point to a leaf in that tree.
|
|
17
|
+
#
|
|
18
|
+
# For example, if we have the following spec:
|
|
19
|
+
#
|
|
20
|
+
# describe Question do
|
|
21
|
+
# decribe "#publish" do
|
|
22
|
+
# context "when not published" do
|
|
23
|
+
# it "makes the question visible" do
|
|
24
|
+
# ...
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# At the moment when rspec is executing the example, the list has the following structure
|
|
31
|
+
# (^ denotes the head node):
|
|
32
|
+
#
|
|
33
|
+
# ^"makes the question visible" -> "when not published" -> "#publish" -> Question
|
|
34
|
+
#
|
|
35
|
+
# LinkedList supports two method for working with it:
|
|
36
|
+
#
|
|
37
|
+
# * add_node – adds a node to the beginig of the list. At this point an example or group
|
|
38
|
+
# has started and we track how much memory has already been used.
|
|
39
|
+
# * remove_node – removes and returns the head node from the list. It means that the node
|
|
40
|
+
# example/group has finished and it is time to calculate its memory usage.
|
|
41
|
+
#
|
|
42
|
+
# When we remove a node we add its total_memory to the previous node.nested_memory, thus
|
|
43
|
+
# gradually tracking the amount of memory used by nested examples inside a group.
|
|
44
|
+
#
|
|
45
|
+
# In the example above, after we remove the node "makes the question visible", we add its total
|
|
46
|
+
# memory usage to nested_memory of the "when not published" node. If the "when not published"
|
|
47
|
+
# group contains other examples or sub-groups, their total_memory will also be added to
|
|
48
|
+
# "when not published" nested_memory. So when the group finishes we will have the total amount
|
|
49
|
+
# of memory used by its nested examples/groups, and thus we will be able to calculate the memory
|
|
50
|
+
# used by hooks and other code inside a group by subtracting nested_memory from total_memory.
|
|
51
|
+
class LinkedList
|
|
52
|
+
attr_reader :head
|
|
53
|
+
|
|
54
|
+
def initialize(memory_at_start)
|
|
55
|
+
add_node(:total, memory_at_start)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def add_node(item, memory_at_start)
|
|
59
|
+
@head = LinkedListNode.new(
|
|
60
|
+
item: item,
|
|
61
|
+
previous: head,
|
|
62
|
+
memory_at_start: memory_at_start
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def remove_node(item, memory_at_finish)
|
|
67
|
+
return if head.item != item
|
|
68
|
+
head.finish(memory_at_finish)
|
|
69
|
+
|
|
70
|
+
current = head
|
|
71
|
+
@head = head.previous
|
|
72
|
+
|
|
73
|
+
current
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class LinkedListNode
|
|
78
|
+
attr_reader :item, :previous, :memory_at_start, :memory_at_finish, :nested_memory
|
|
79
|
+
|
|
80
|
+
def initialize(item:, memory_at_start:, previous:)
|
|
81
|
+
@item = item
|
|
82
|
+
@previous = previous
|
|
83
|
+
|
|
84
|
+
@memory_at_start = memory_at_start || 0
|
|
85
|
+
@memory_at_finish = nil
|
|
86
|
+
@nested_memory = 0
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def total_memory
|
|
90
|
+
return 0 if memory_at_finish.nil?
|
|
91
|
+
# It seems that on Windows Minitest may release a lot of memory to
|
|
92
|
+
# the OS when it finishes and executes #report, leading to memory_at_finish
|
|
93
|
+
# being less than memory_at_start. In this case we return nested_memory
|
|
94
|
+
# which does not account for the memory used in `after` hooks, but it
|
|
95
|
+
# is better than nothing.
|
|
96
|
+
return nested_memory if memory_at_start > memory_at_finish
|
|
97
|
+
|
|
98
|
+
memory_at_finish - memory_at_start
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def hooks_memory
|
|
102
|
+
total_memory - nested_memory
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def finish(memory_at_finish)
|
|
106
|
+
@memory_at_finish = memory_at_finish
|
|
107
|
+
|
|
108
|
+
previous&.add_nested(self)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
protected
|
|
112
|
+
|
|
113
|
+
def add_nested(node)
|
|
114
|
+
@nested_memory += node.total_memory
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbconfig"
|
|
4
|
+
|
|
5
|
+
module TestProf
|
|
6
|
+
module MemoryProf
|
|
7
|
+
class Tracker
|
|
8
|
+
module RssTool
|
|
9
|
+
class ProcFS
|
|
10
|
+
def initialize
|
|
11
|
+
@statm = File.open("/proc/#{$$}/statm", "r")
|
|
12
|
+
@page_size = get_page_size
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def track
|
|
16
|
+
@statm.seek(0)
|
|
17
|
+
@statm.gets.split(/\s/)[1].to_i * @page_size
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def get_page_size
|
|
23
|
+
[
|
|
24
|
+
-> {
|
|
25
|
+
require "etc"
|
|
26
|
+
Etc.sysconf(Etc::SC_PAGE_SIZE)
|
|
27
|
+
},
|
|
28
|
+
-> { `getconf PAGE_SIZE`.to_i },
|
|
29
|
+
-> { 0x1000 }
|
|
30
|
+
].each do |strategy|
|
|
31
|
+
page_size = begin
|
|
32
|
+
strategy.call
|
|
33
|
+
rescue
|
|
34
|
+
next
|
|
35
|
+
end
|
|
36
|
+
return page_size
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class PS
|
|
42
|
+
def track
|
|
43
|
+
`ps -o rss -p #{$$}`.strip.split.last.to_i * 1024
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class GetProcess
|
|
48
|
+
def track
|
|
49
|
+
command.strip.split.last.to_i
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def command
|
|
55
|
+
`powershell -Command "Get-Process -Id #{$$} | select WS"`
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
TOOLS = {
|
|
60
|
+
linux: ProcFS,
|
|
61
|
+
macosx: PS,
|
|
62
|
+
unix: PS,
|
|
63
|
+
windows: GetProcess
|
|
64
|
+
}.freeze
|
|
65
|
+
|
|
66
|
+
class << self
|
|
67
|
+
def tool
|
|
68
|
+
TOOLS[os_type]&.new
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def os_type
|
|
72
|
+
case RbConfig::CONFIG["host_os"]
|
|
73
|
+
when /linux/
|
|
74
|
+
:linux
|
|
75
|
+
when /darwin|mac os/
|
|
76
|
+
:macosx
|
|
77
|
+
when /solaris|bsd/
|
|
78
|
+
:unix
|
|
79
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
|
80
|
+
:windows
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_prof/memory_prof/tracker/linked_list"
|
|
4
|
+
require "test_prof/memory_prof/tracker/rss_tool"
|
|
5
|
+
|
|
6
|
+
module TestProf
|
|
7
|
+
module MemoryProf
|
|
8
|
+
# Tracker is responsible for tracking memory usage and determining
|
|
9
|
+
# the top n examples and groups. There are two types of trackers:
|
|
10
|
+
# AllocTracker and RssTracker.
|
|
11
|
+
#
|
|
12
|
+
# A tracker consists of four main parts:
|
|
13
|
+
# * list - a linked list that is being used to track memmory for individual groups/examples.
|
|
14
|
+
# list is an instance of LinkedList (for more info see tracker/linked_list.rb)
|
|
15
|
+
# * examples – the top n examples, an instance of Utils::SizedOrderedSet.
|
|
16
|
+
# * groups – the top n groups, an instance of Utils::SizedOrderedSet.
|
|
17
|
+
# * track - a method that fetches the amount of memory in use at a certain point.
|
|
18
|
+
class Tracker
|
|
19
|
+
attr_reader :top_count, :examples, :groups, :total_memory, :list
|
|
20
|
+
|
|
21
|
+
def initialize(top_count)
|
|
22
|
+
raise "Your Ruby Engine or OS is not supported" unless supported?
|
|
23
|
+
|
|
24
|
+
@top_count = top_count
|
|
25
|
+
|
|
26
|
+
@examples = Utils::SizedOrderedSet.new(top_count, sort_by: :memory)
|
|
27
|
+
@groups = Utils::SizedOrderedSet.new(top_count, sort_by: :memory)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def start
|
|
31
|
+
@list = LinkedList.new(track)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def finish
|
|
35
|
+
node = list.remove_node(:total, track)
|
|
36
|
+
@total_memory = node.total_memory
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def example_started(example)
|
|
40
|
+
list.add_node(example, track)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def example_finished(example)
|
|
44
|
+
node = list.remove_node(example, track)
|
|
45
|
+
examples << {**example, memory: node.total_memory}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def group_started(group)
|
|
49
|
+
list.add_node(group, track)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def group_finished(group)
|
|
53
|
+
node = list.remove_node(group, track)
|
|
54
|
+
groups << {**group, memory: node.hooks_memory}
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class AllocTracker < Tracker
|
|
59
|
+
def track
|
|
60
|
+
GC.stat[:total_allocated_objects]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def supported?
|
|
64
|
+
RUBY_ENGINE != "jruby"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class RssTracker < Tracker
|
|
69
|
+
def initialize(top_count)
|
|
70
|
+
@rss_tool = RssTool.tool
|
|
71
|
+
|
|
72
|
+
super
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def track
|
|
76
|
+
@rss_tool.track
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def supported?
|
|
80
|
+
!!@rss_tool
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_prof/memory_prof/tracker"
|
|
4
|
+
require "test_prof/memory_prof/printer"
|
|
5
|
+
|
|
6
|
+
module TestProf
|
|
7
|
+
# MemoryProf can help in detecting test examples causing memory spikes.
|
|
8
|
+
# It supports two metrics: RSS and allocations.
|
|
9
|
+
#
|
|
10
|
+
# Example:
|
|
11
|
+
#
|
|
12
|
+
# TEST_MEM_PROF='rss' rspec ...
|
|
13
|
+
# TEST_MEM_PROF='alloc' rspec ...
|
|
14
|
+
#
|
|
15
|
+
# By default MemoryProf shows the top 5 examples and groups (for RSpec) but you can
|
|
16
|
+
# set how many items to display with `TEST_MEM_PROF_COUNT`:
|
|
17
|
+
#
|
|
18
|
+
# TEST_MEM_PROF='rss' TEST_MEM_PROF_COUNT=10 rspec ...
|
|
19
|
+
#
|
|
20
|
+
# The examples block shows the amount of memory used by each example, and the groups
|
|
21
|
+
# block displays the memory allocated by other code defined in the groups. For example,
|
|
22
|
+
# RSpec groups may include heavy `before(:all)` (or `before_all`) setup blocks, so it is
|
|
23
|
+
# helpful to see which groups use the most amount of memory outside of their examples.
|
|
24
|
+
|
|
25
|
+
module MemoryProf
|
|
26
|
+
# MemoryProf configuration
|
|
27
|
+
class Configuration
|
|
28
|
+
attr_reader :mode, :top_count
|
|
29
|
+
|
|
30
|
+
def initialize
|
|
31
|
+
self.mode = ENV["TEST_MEM_PROF"]
|
|
32
|
+
self.top_count = ENV["TEST_MEM_PROF_COUNT"]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def mode=(value)
|
|
36
|
+
@mode = (value == "alloc") ? :alloc : :rss
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def top_count=(value)
|
|
40
|
+
@top_count = value.to_i
|
|
41
|
+
@top_count = 5 unless @top_count.positive?
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class << self
|
|
46
|
+
TRACKERS = {
|
|
47
|
+
alloc: AllocTracker,
|
|
48
|
+
rss: RssTracker
|
|
49
|
+
}.freeze
|
|
50
|
+
|
|
51
|
+
PRINTERS = {
|
|
52
|
+
alloc: AllocPrinter,
|
|
53
|
+
rss: RssPrinter
|
|
54
|
+
}.freeze
|
|
55
|
+
|
|
56
|
+
def config
|
|
57
|
+
@config ||= Configuration.new
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def configure
|
|
61
|
+
yield config
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def tracker
|
|
65
|
+
tracker = TRACKERS[config.mode]
|
|
66
|
+
tracker.new(config.top_count)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def printer(tracker)
|
|
70
|
+
printer = PRINTERS[config.mode]
|
|
71
|
+
printer.new(tracker)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
require "test_prof/memory_prof/rspec" if TestProf.rspec?
|
|
78
|
+
require "test_prof/memory_prof/minitest" if TestProf.minitest?
|
|
@@ -6,9 +6,9 @@ module TestProf
|
|
|
6
6
|
# Do not add these classes to resulted sample
|
|
7
7
|
CORE_RUNNABLES = [
|
|
8
8
|
Minitest::Test,
|
|
9
|
-
Minitest::Unit::TestCase,
|
|
9
|
+
defined?(Minitest::Unit::TestCase) ? Minitest::Unit::TestCase : nil,
|
|
10
10
|
Minitest::Spec
|
|
11
|
-
].freeze
|
|
11
|
+
].compact.freeze
|
|
12
12
|
|
|
13
13
|
class << self
|
|
14
14
|
def suites
|
|
@@ -12,6 +12,7 @@ module TestProf
|
|
|
12
12
|
if within_before_all?
|
|
13
13
|
before(:all) do
|
|
14
14
|
@__inspect_output = "before_all hook"
|
|
15
|
+
::RSpec.current_scope = :before_all if ::RSpec.respond_to?(:current_scope=)
|
|
15
16
|
instance_eval(&block)
|
|
16
17
|
end
|
|
17
18
|
return
|
|
@@ -22,6 +23,7 @@ module TestProf
|
|
|
22
23
|
|
|
23
24
|
before(:all) do
|
|
24
25
|
@__inspect_output = "before_all hook"
|
|
26
|
+
::RSpec.current_scope = :before_all if ::RSpec.respond_to?(:current_scope=)
|
|
25
27
|
BeforeAll.setup_fixtures(self) if setup_fixtures
|
|
26
28
|
BeforeAll.begin_transaction(context, current_metadata) do
|
|
27
29
|
instance_eval(&block)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "test_prof"
|
|
3
|
+
require "test_prof/core"
|
|
4
4
|
require "test_prof/recipes/rspec/before_all"
|
|
5
5
|
|
|
6
6
|
module TestProf
|
|
@@ -120,11 +120,13 @@ module TestProf
|
|
|
120
120
|
LetItBe.module_for(self).module_eval do
|
|
121
121
|
define_method(identifier) do
|
|
122
122
|
# Trying to detect the context
|
|
123
|
-
#
|
|
124
|
-
|
|
123
|
+
# First, check for ::RSpec.current_scope (modern RSpec) and then read @__inspect_output
|
|
124
|
+
# (based on https://github.com/rspec/rspec-rails/commit/7cb796db064f58da7790a92e73ab906ef50b1f34)
|
|
125
|
+
if ::RSpec.respond_to?(:current_scope) && %i[before_all before_context_hook after_context_hook].include?(::RSpec.current_scope)
|
|
126
|
+
instance_variable_get(:"#{PREFIX}#{identifier}")
|
|
127
|
+
elsif /(before|after)\(:context\)/.match?(@__inspect_output) || @__inspect_output.include?("before_all")
|
|
125
128
|
instance_variable_get(:"#{PREFIX}#{identifier}")
|
|
126
129
|
else
|
|
127
|
-
# Fallback to let definition
|
|
128
130
|
super()
|
|
129
131
|
end
|
|
130
132
|
end
|
data/lib/test_prof/ruby_prof.rb
CHANGED
|
@@ -53,7 +53,7 @@ module TestProf
|
|
|
53
53
|
def initialize
|
|
54
54
|
@printer = ENV["TEST_RUBY_PROF"].to_sym if PRINTERS.key?(ENV["TEST_RUBY_PROF"])
|
|
55
55
|
@printer ||= ENV.fetch("TEST_RUBY_PROF_PRINTER", :flat).to_sym
|
|
56
|
-
@mode = ENV.fetch("TEST_RUBY_PROF_MODE", :wall).
|
|
56
|
+
@mode = ENV.fetch("TEST_RUBY_PROF_MODE", :wall).to_s
|
|
57
57
|
@min_percent = 1
|
|
58
58
|
@include_threads = false
|
|
59
59
|
@exclude_common_methods = true
|
|
@@ -84,6 +84,22 @@ module TestProf
|
|
|
84
84
|
|
|
85
85
|
[type, ::RubyProf.const_get(PRINTERS[type])]
|
|
86
86
|
end
|
|
87
|
+
|
|
88
|
+
# Based on deprecated https://github.com/ruby-prof/ruby-prof/blob/fd3a5236a459586c5ca7ce4de506c1835129516a/lib/ruby-prof.rb#L36
|
|
89
|
+
def ruby_prof_mode
|
|
90
|
+
case mode
|
|
91
|
+
when "wall", "wall_time"
|
|
92
|
+
::RubyProf::WALL_TIME
|
|
93
|
+
when "allocations"
|
|
94
|
+
::RubyProf::ALLOCATIONS
|
|
95
|
+
when "memory"
|
|
96
|
+
::RubyProf::MEMORY
|
|
97
|
+
when "process", "process_time"
|
|
98
|
+
::RubyProf::PROCESS_TIME
|
|
99
|
+
else
|
|
100
|
+
::RubyProf::WALL_TIME
|
|
101
|
+
end
|
|
102
|
+
end
|
|
87
103
|
end
|
|
88
104
|
|
|
89
105
|
# Wrapper over RubyProf profiler and printer
|
|
@@ -166,19 +182,18 @@ module TestProf
|
|
|
166
182
|
log :warn, <<~MSG
|
|
167
183
|
RubyProf is activated globally, you cannot generate per-example report.
|
|
168
184
|
|
|
169
|
-
Make sure you haven'
|
|
185
|
+
Make sure you haven't set the TEST_RUBY_PROF environmental variable.
|
|
170
186
|
MSG
|
|
171
187
|
return
|
|
172
188
|
end
|
|
173
189
|
|
|
174
190
|
return unless init_ruby_prof
|
|
175
191
|
|
|
176
|
-
options = {
|
|
177
|
-
merge_fibers: true
|
|
178
|
-
}
|
|
192
|
+
options = {}
|
|
179
193
|
|
|
180
194
|
options[:include_threads] = [Thread.current] unless
|
|
181
195
|
config.include_threads?
|
|
196
|
+
options[:measure_mode] = config.ruby_prof_mode
|
|
182
197
|
|
|
183
198
|
profiler = ::RubyProf::Profile.new(options)
|
|
184
199
|
profiler.exclude_common_methods! if config.exclude_common_methods?
|
|
@@ -208,13 +223,12 @@ module TestProf
|
|
|
208
223
|
|
|
209
224
|
def init_ruby_prof
|
|
210
225
|
return @initialized if instance_variable_defined?(:@initialized)
|
|
211
|
-
ENV["RUBY_PROF_MEASURE_MODE"] = config.mode.to_s
|
|
212
226
|
@initialized = TestProf.require(
|
|
213
227
|
"ruby-prof",
|
|
214
228
|
<<~MSG
|
|
215
229
|
Please, install 'ruby-prof' first:
|
|
216
230
|
# Gemfile
|
|
217
|
-
gem 'ruby-prof', '>=
|
|
231
|
+
gem 'ruby-prof', '>= 1.4.0', require: false
|
|
218
232
|
MSG
|
|
219
233
|
) { check_ruby_prof_version }
|
|
220
234
|
end
|
|
@@ -19,12 +19,13 @@ module TestProf
|
|
|
19
19
|
|
|
20
20
|
def example_started(notification)
|
|
21
21
|
return unless profile?(notification.example)
|
|
22
|
+
|
|
22
23
|
notification.example.metadata[:sprof_report] = TestProf::StackProf.profile
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def example_finished(notification)
|
|
26
27
|
return unless profile?(notification.example)
|
|
27
|
-
return
|
|
28
|
+
return if notification.example.metadata[:sprof_report] == false
|
|
28
29
|
|
|
29
30
|
TestProf::StackProf.dump(
|
|
30
31
|
self.class.report_name_generator.call(notification.example)
|
data/lib/test_prof/stack_prof.rb
CHANGED
|
@@ -33,7 +33,7 @@ module TestProf
|
|
|
33
33
|
if FORMATS.include?(ENV["TEST_STACK_PROF_FORMAT"])
|
|
34
34
|
ENV["TEST_STACK_PROF_FORMAT"]
|
|
35
35
|
else
|
|
36
|
-
"
|
|
36
|
+
"json"
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
sample_interval = ENV["TEST_STACK_PROF_INTERVAL"].to_i
|
|
@@ -81,9 +81,9 @@ module TestProf
|
|
|
81
81
|
def profile(name = nil)
|
|
82
82
|
if locked?
|
|
83
83
|
log :warn, <<~MSG
|
|
84
|
-
StackProf
|
|
84
|
+
StackProf has been already activated.
|
|
85
85
|
|
|
86
|
-
Make sure you
|
|
86
|
+
Make sure you have not set the TEST_STACK_PROF environmental variable.
|
|
87
87
|
MSG
|
|
88
88
|
return false
|
|
89
89
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_prof/utils/rspec"
|
|
4
|
+
|
|
5
|
+
module TestProf
|
|
6
|
+
module Vernier
|
|
7
|
+
# Reporter for RSpec to profile specific examples with Vernier
|
|
8
|
+
class Listener # :nodoc:
|
|
9
|
+
class << self
|
|
10
|
+
attr_accessor :report_name_generator
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
self.report_name_generator = Utils::RSpec.method(:example_to_filename)
|
|
14
|
+
|
|
15
|
+
NOTIFICATIONS = %i[
|
|
16
|
+
example_started
|
|
17
|
+
example_finished
|
|
18
|
+
].freeze
|
|
19
|
+
|
|
20
|
+
def example_started(notification)
|
|
21
|
+
return unless profile?(notification.example)
|
|
22
|
+
notification.example.metadata[:vernier_collector] = TestProf::Vernier.profile
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def example_finished(notification)
|
|
26
|
+
return unless profile?(notification.example)
|
|
27
|
+
return unless notification.example.metadata[:vernier_collector]
|
|
28
|
+
|
|
29
|
+
TestProf::Vernier.dump(
|
|
30
|
+
notification.example.metadata[:vernier_collector],
|
|
31
|
+
self.class.report_name_generator.call(notification.example)
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def profile?(example)
|
|
38
|
+
example.metadata.key?(:vernier)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
RSpec.configure do |config|
|
|
45
|
+
config.before(:suite) do
|
|
46
|
+
listener = TestProf::Vernier::Listener.new
|
|
47
|
+
|
|
48
|
+
config.reporter.register_listener(
|
|
49
|
+
listener, *TestProf::Vernier::Listener::NOTIFICATIONS
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Handle boot profiling
|
|
55
|
+
RSpec.configure do |config|
|
|
56
|
+
config.append_before(:suite) do
|
|
57
|
+
TestProf::Vernier.dump(TestProf::Vernier.default_collector, "boot") if TestProf::Vernier.config.boot?
|
|
58
|
+
end
|
|
59
|
+
end
|