test-prof 0.1.0.beta2 → 0.1.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
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: