test-prof 0.1.0.beta2 → 0.1.0.beta3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d7202c99e7ad5d1430b4a281dea1e5b8fa8f508c
4
- data.tar.gz: 4b835921de89f894022a8b1620f650d15cbe7956
3
+ metadata.gz: 58805aeec9869d4d5b15a7a936ff1f0f10c6b7cb
4
+ data.tar.gz: 6e89ccd56db3e421d5660573ea4cfea9dceb8010
5
5
  SHA512:
6
- metadata.gz: e6a6afe56f4973b7f17806f5aa2b0c1c4833fc813e123328603eec65e4bafeec5eaceae2247e038743f961ca8257543b4b1b15676c5452bb3ce78f55e485612c
7
- data.tar.gz: d402f3373d470a031537f8c40305cbd97334739eb94d9e29e2f7a1d877fea7f28b0b658efc9ce2c3a75dc202f7334da7998add883a27d06988459cab5893a7dd
6
+ metadata.gz: b1985d22be179fffe92e3a1f87bf76eb5b08a4667195badd55c3caa9e4705462358708e6c9362a14cfbaf0928e0ed23baa65e3358a42b773426c567007915d83
7
+ data.tar.gz: 96fd3e35e266b7e5cdb2558bab332a63b0d7ccc0233866444d108a7b7549e9ffbe528d2b51b44ab558ebb80947d49c061cf69c92bec9161117c4b337d7c61c0e
data/README.md CHANGED
@@ -53,6 +53,8 @@ Checkout our guides for each specific tool:
53
53
 
54
54
  - [Instrumentation Profiler](https://github.com/palkan/test-prof/tree/master/guides/event_prof.md) (e.g. ActiveSupport notifications)
55
55
 
56
+ - [Tag Profiler](https://github.com/palkan/test-prof/tree/master/guides/tag_prof.md)
57
+
56
58
  - [Factory Doctor](https://github.com/palkan/test-prof/tree/master/guides/factory_doctor.md)
57
59
 
58
60
  - [Factory Profiler](https://github.com/palkan/test-prof/tree/master/guides/factory_prof.md)
@@ -58,4 +58,6 @@ end
58
58
 
59
59
  By default, we use `CallStackPrinter`.
60
60
 
61
+ Also, you can specify RubyProf mode (`wall`, `cpu`, etc) through `TEST_RUBY_PROF_MODE` env variable.
62
+
61
63
  See [ruby_prof.rb](https://github.com/palkan/test-prof/tree/master/lib/test_prof/ruby_prof.rb) for all available configuration options and their usage.
@@ -40,4 +40,8 @@ end
40
40
 
41
41
  ### Configuration
42
42
 
43
+ You can change StackProf mode (which is `wall` by default) through `TEST_STACK_PROF_MODE` env variable.
44
+
45
+ If you want to generate flame graphs you should collect _raw_ data. Turn _raw_ collection on by passing `TEST_STACK_PROF_RAW=1`.
46
+
43
47
  See [stack_prof.rb](https://github.com/palkan/test-prof/tree/master/lib/test_prof/stack_prof.rb) for all available configuration options and their usage.
@@ -0,0 +1,52 @@
1
+ # TagProf
2
+
3
+ TagProf is a simple profiler which collects examples statistics grouped by a provided tag value.
4
+
5
+ That's pretty useful in conjunction with `rspec-rails` built-in feature – `infer_spec_types_from_location!` – which automatically adds `type` to examples metadata.
6
+
7
+ Example output:
8
+
9
+ ```sh
10
+ [TEST PROF INFO] TagProf report for type
11
+
12
+ type time total %total %time avg
13
+
14
+ request 00:04.808 42 33.87 54.70 00:00.114
15
+ controller 00:02.855 42 33.87 32.48 00:00.067
16
+ model 00:01.127 40 32.26 12.82 00:00.028
17
+ ```
18
+
19
+
20
+ It shows both the total number of examples in each group and the total time spent (as long as percentages and average values).
21
+
22
+
23
+ ## Instructions
24
+
25
+ TagProf can only be used with RSpec.
26
+
27
+ To activate TagProf use `TAG_PROF` environment variable:
28
+
29
+ ```sh
30
+ # Group by type
31
+ TAG_PROF=type rspec
32
+ ```
33
+
34
+ ## Pro-Tip: More Types
35
+
36
+ By default, RSpec only infers types for default Rails app entities (such as controllers, models, mailers, etc.).
37
+ Modern Rails applications typically contain other abstractions too (e.g. services, forms, presenters, etc.), but RSpec is not aware of them and doesn't add any metadata.
38
+
39
+ That's the quick workaround:
40
+
41
+ ```ruby
42
+ RSpec.configure do |config|
43
+
44
+ ...
45
+ config.define_derived_metadata(file_path: %r{/spec/}) do |metadata|
46
+ # do not overwrite type if it's already set
47
+ next if metadata.key?(:type)
48
+ match = metadata[:location].match(%r{/spec/([^/]+)/})
49
+ metadata[:type] = match[1].singularize.to_sym
50
+ end
51
+ end
52
+ ```
@@ -42,13 +42,14 @@ module TestProf
42
42
  false
43
43
  end
44
44
 
45
- # Run block only if provided env var is present.
45
+ # Run block only if provided env var is present and
46
+ # equal to the provided value (if any).
46
47
  # Contains workaround for applications using Spring.
47
- def activate(env_var)
48
- if defined?(::Spring) && !ENV['DISABLE_SPRING']
49
- Spring.after_fork { yield if ENV[env_var] }
50
- elsif ENV[env_var]
51
- yield
48
+ def activate(env_var, val = nil)
49
+ if defined?(::Spring)
50
+ ::Spring.after_fork { activate!(env_var, val) { yield } }
51
+ else
52
+ activate!(env_var, val) { yield }
52
53
  end
53
54
  end
54
55
 
@@ -69,6 +70,10 @@ module TestProf
69
70
 
70
71
  private
71
72
 
73
+ def activate!(env_var, val)
74
+ yield if ENV[env_var] && (val.nil? || ENV[env_var] == val)
75
+ end
76
+
72
77
  def with_timestamps(path)
73
78
  return path unless config.timestamps?
74
79
  timestamps = "-#{Time.now.to_i}"
@@ -106,3 +111,4 @@ require "test_prof/event_prof"
106
111
  require "test_prof/factory_doctor"
107
112
  require "test_prof/factory_prof"
108
113
  require "test_prof/rspec_stamp"
114
+ require "test_prof/tag_prof"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "test_prof/event_prof/custom_events/factory_create" if ENV['EVENT_PROF'] == "factory.create"
4
- require "test_prof/event_prof/custom_events/sidekiq_inline" if ENV['EVENT_PROF'] == "sidekiq.inline"
5
- require "test_prof/event_prof/custom_events/sidekiq_jobs" if ENV['EVENT_PROF'] == "sidekiq.jobs"
3
+ require "test_prof/event_prof/custom_events/factory_create"
4
+ require "test_prof/event_prof/custom_events/sidekiq_inline"
5
+ require "test_prof/event_prof/custom_events/sidekiq_jobs"
@@ -39,13 +39,15 @@ module TestProf::EventProf::CustomEvents
39
39
  end
40
40
  end
41
41
 
42
- if TestProf.require(
43
- 'factory_girl',
44
- <<~MSG
45
- Failed to load FactoryGirl.
42
+ TestProf.activate('EVENT_PROF', 'factory.create') do
43
+ if TestProf.require(
44
+ 'factory_girl',
45
+ <<~MSG
46
+ Failed to load FactoryGirl.
46
47
 
47
- Make sure that "factory_girl" gem is in your Gemfile.
48
- MSG
49
- )
50
- TestProf::EventProf::CustomEvents::FactoryCreate.setup!
48
+ Make sure that "factory_girl" gem is in your Gemfile.
49
+ MSG
50
+ )
51
+ TestProf::EventProf::CustomEvents::FactoryCreate.setup!
52
+ end
51
53
  end
@@ -36,13 +36,15 @@ module TestProf::EventProf::CustomEvents
36
36
  end
37
37
  end
38
38
 
39
- if TestProf.require(
40
- 'sidekiq/testing',
41
- <<~MSG
42
- Failed to load Sidekiq.
39
+ TestProf.activate('EVENT_PROF', 'sidekiq.inline') do
40
+ if TestProf.require(
41
+ 'sidekiq/testing',
42
+ <<~MSG
43
+ Failed to load Sidekiq.
43
44
 
44
- Make sure that "sidekiq" gem is in your Gemfile.
45
- MSG
46
- )
47
- TestProf::EventProf::CustomEvents::SidekiqInline.setup!
45
+ Make sure that "sidekiq" gem is in your Gemfile.
46
+ MSG
47
+ )
48
+ TestProf::EventProf::CustomEvents::SidekiqInline.setup!
49
+ end
48
50
  end
@@ -23,16 +23,18 @@ module TestProf::EventProf::CustomEvents
23
23
  end
24
24
  end
25
25
 
26
- if TestProf.require(
27
- 'sidekiq/testing',
28
- <<~MSG
29
- Failed to load Sidekiq.
26
+ TestProf.activate('EVENT_PROF', 'sidekiq.jobs') do
27
+ if TestProf.require(
28
+ 'sidekiq/testing',
29
+ <<~MSG
30
+ Failed to load Sidekiq.
30
31
 
31
- Make sure that "sidekiq" gem is in your Gemfile.
32
- MSG
33
- )
34
- TestProf::EventProf::CustomEvents::SidekiqJobs.setup!
35
- TestProf::EventProf.configure do |config|
36
- config.rank_by = :count
32
+ Make sure that "sidekiq" gem is in your Gemfile.
33
+ MSG
34
+ )
35
+ TestProf::EventProf::CustomEvents::SidekiqJobs.setup!
36
+ TestProf::EventProf.configure do |config|
37
+ config.rank_by = :count
38
+ end
37
39
  end
38
40
  end
@@ -87,7 +87,11 @@ TestProf.activate('EVENT_PROF') do
87
87
  RSpec.configure do |config|
88
88
  listener = TestProf::EventProf::RSpecListener.new
89
89
 
90
- config.reporter.register_listener(listener, *TestProf::EventProf::RSpecListener::NOTIFICATIONS)
90
+ config.before(:suite) do
91
+ config.reporter.register_listener(
92
+ listener, *TestProf::EventProf::RSpecListener::NOTIFICATIONS
93
+ )
94
+ end
91
95
 
92
96
  config.after(:suite) { listener.print }
93
97
  end
@@ -83,9 +83,11 @@ TestProf.activate('FDOC') do
83
83
  RSpec.configure do |config|
84
84
  listener = TestProf::FactoryDoctor::RSpecListener.new
85
85
 
86
- config.reporter.register_listener(
87
- listener, *TestProf::FactoryDoctor::RSpecListener::NOTIFICATIONS
88
- )
86
+ config.before(:suite) do
87
+ config.reporter.register_listener(
88
+ listener, *TestProf::FactoryDoctor::RSpecListener::NOTIFICATIONS
89
+ )
90
+ end
89
91
 
90
92
  config.after(:suite) { listener.print }
91
93
  end
@@ -84,7 +84,11 @@ TestProf.activate('RSTAMP') do
84
84
  RSpec.configure do |config|
85
85
  listener = TestProf::RSpecStamp::RSpecListener.new
86
86
 
87
- config.reporter.register_listener(listener, *TestProf::RSpecStamp::RSpecListener::NOTIFICATIONS)
87
+ config.before(:suite) do
88
+ config.reporter.register_listener(
89
+ listener, *TestProf::RSpecStamp::RSpecListener::NOTIFICATIONS
90
+ )
91
+ end
88
92
 
89
93
  config.after(:suite) { listener.stamp! }
90
94
  end
@@ -48,8 +48,8 @@ module TestProf
48
48
  :include_threads, :eliminate_methods
49
49
 
50
50
  def initialize
51
- @printer = :call_stack
52
- @mode = :wall
51
+ @printer = ENV.fetch('TEST_RUBY_PROF_PRINTER', :call_stack).to_sym
52
+ @mode = ENV.fetch('TEST_RUBY_PROF_MODE', :wall).to_sym
53
53
  @min_percent = 1
54
54
  @include_threads = false
55
55
  @eliminate_methods = ELIMINATE_METHODS
@@ -65,13 +65,10 @@ module TestProf
65
65
  end
66
66
 
67
67
  # Returns an array of printer type (ID) and class.
68
- # Takes ENV variable TEST_RUBY_PROF_PRINTER into account.
69
68
  def resolve_printer
70
- type = ENV['TEST_RUBY_PROF_PRINTER'] || printer
69
+ return ['custom', printer] if printer.is_a?(Module)
71
70
 
72
- return ['custom', type] if type.is_a?(Module)
73
-
74
- type = type.to_s
71
+ type = printer.to_s
75
72
 
76
73
  raise ArgumentError, "Unknown printer: #{type}" unless
77
74
  PRINTERS.key?(type)
@@ -25,8 +25,8 @@ module TestProf
25
25
  attr_accessor :mode, :interval, :raw
26
26
 
27
27
  def initialize
28
- @mode = :wall
29
- @raw = false
28
+ @mode = ENV.fetch('TEST_STACK_PROF_MODE', :wall).to_sym
29
+ @raw = ENV['TEST_STACK_PROF_RAW'] == '1'
30
30
  end
31
31
  end
32
32
 
@@ -89,7 +89,7 @@ module TestProf
89
89
 
90
90
  def build_path(name)
91
91
  TestProf.artefact_path(
92
- "stack-prof-report-#{config.mode}-#{name}.dump"
92
+ "stack-prof-report-#{config.mode}#{config.raw ? '-raw' : ''}-#{name}.dump"
93
93
  )
94
94
  end
95
95
 
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TestProf
4
+ module TagProf # :nodoc:
5
+ end
6
+ end
7
+
8
+ require "test_prof/tag_prof/rspec" if defined?(RSpec)
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_prof/ext/float_duration"
4
+
5
+ module TestProf
6
+ module TagProf
7
+ class RSpecListener # :nodoc:
8
+ include Logging
9
+ using FloatDuration
10
+
11
+ NOTIFICATIONS = %i[
12
+ example_started
13
+ example_finished
14
+ ].freeze
15
+
16
+ def initialize
17
+ @tag = ENV['TAG_PROF'].to_sym
18
+ @tags = Hash.new { |h, k| h[k] = { val: k, count: 0, time: 0.0 } }
19
+
20
+ log :info, "TagProf enabled (#{@tag})"
21
+ end
22
+
23
+ def example_started(_notification)
24
+ @ts = Time.now
25
+ end
26
+
27
+ def example_finished(notification)
28
+ return if notification.example.pending?
29
+
30
+ tag = notification.example.metadata.fetch(@tag, :__unknown__)
31
+
32
+ @tags[tag][:count] += 1
33
+ @tags[tag][:time] += (Time.now - @ts)
34
+ end
35
+
36
+ def print
37
+ msgs = []
38
+
39
+ msgs <<
40
+ <<~MSG
41
+ TagProf report for #{@tag}
42
+ MSG
43
+
44
+ msgs << format(
45
+ "%15s %12s %6s %6s %6s %12s",
46
+ @tag,
47
+ 'time', 'total', '%total', '%time', 'avg'
48
+ )
49
+
50
+ msgs << ""
51
+
52
+ total = @tags.values.sum { |v| v[:count] }
53
+ total_time = @tags.values.sum { |v| v[:time] }
54
+
55
+ @tags.values.sort_by { |v| -v[:time] }.each do |tag|
56
+ msgs << format(
57
+ "%15s %12s %6d %6.2f %6.2f %12s",
58
+ tag[:val], tag[:time].duration, tag[:count],
59
+ 100 * tag[:count].to_f / total,
60
+ 100 * tag[:time] / total_time,
61
+ (tag[:time] / tag[:count]).duration
62
+ )
63
+ end
64
+
65
+ log :info, msgs.join("\n")
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ # Register TagProf listener
72
+ TestProf.activate('TAG_PROF') do
73
+ RSpec.configure do |config|
74
+ listener = TestProf::TagProf::RSpecListener.new
75
+
76
+ config.before(:suite) do
77
+ config.reporter.register_listener(
78
+ listener, *TestProf::TagProf::RSpecListener::NOTIFICATIONS
79
+ )
80
+ end
81
+
82
+ config.after(:suite) { listener.print }
83
+ end
84
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TestProf
4
- VERSION = "0.1.0.beta2"
4
+ VERSION = "0.1.0.beta3"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test-prof
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.beta2
4
+ version: 0.1.0.beta3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-07-13 00:00:00.000000000 Z
11
+ date: 2017-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -167,6 +167,7 @@ files:
167
167
  - guides/rubocop.md
168
168
  - guides/ruby_prof.md
169
169
  - guides/stack_prof.md
170
+ - guides/tag_prof.md
170
171
  - lib/test-prof.rb
171
172
  - lib/test_prof.rb
172
173
  - lib/test_prof/any_fixture.rb
@@ -199,6 +200,8 @@ files:
199
200
  - lib/test_prof/ruby_prof/rspec.rb
200
201
  - lib/test_prof/stack_prof.rb
201
202
  - lib/test_prof/stack_prof/rspec.rb
203
+ - lib/test_prof/tag_prof.rb
204
+ - lib/test_prof/tag_prof/rspec.rb
202
205
  - lib/test_prof/version.rb
203
206
  homepage: http://github.com/palkan/test-prof
204
207
  licenses: