rspec_profiling 0.0.6 → 0.0.8

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/ISSUE_TEMPLATE/bug.yml +66 -0
  4. data/.github/ISSUE_TEMPLATE/config.yml +1 -0
  5. data/.github/ISSUE_TEMPLATE/docs.yml +18 -0
  6. data/.github/ISSUE_TEMPLATE/feature-request.yml +36 -0
  7. data/.github/ISSUE_TEMPLATE/question-support.yml +18 -0
  8. data/.github/PULL_REQUEST_TEMPLATE.md +17 -0
  9. data/.github/dependabot.yaml +20 -0
  10. data/.github/workflows/auto-assign-author.yaml +13 -0
  11. data/.github/workflows/codeql.yaml +43 -0
  12. data/.github/workflows/release.yaml +40 -0
  13. data/.github/workflows/stale.yaml +38 -0
  14. data/.github/workflows/test.yaml +54 -0
  15. data/.gitignore +3 -0
  16. data/.ruby-version +1 -1
  17. data/CODE_OF_CONDUCT.md +130 -0
  18. data/CONTRIBUTING.md +37 -0
  19. data/EMERITUS.md +3 -0
  20. data/{LICENSE.txt → LICENSE.md} +2 -1
  21. data/Makefile +60 -0
  22. data/README.md +66 -19
  23. data/SECURITY.md +20 -0
  24. data/docker-compose.yml +13 -0
  25. data/lib/rspec_profiling/collectors/csv.rb +30 -7
  26. data/lib/rspec_profiling/collectors/json.rb +70 -0
  27. data/lib/rspec_profiling/collectors/psql.rb +1 -1
  28. data/lib/rspec_profiling/collectors/sql.rb +1 -1
  29. data/lib/rspec_profiling/config.rb +4 -2
  30. data/lib/rspec_profiling/example.rb +23 -1
  31. data/lib/rspec_profiling/rspec.rb +1 -1
  32. data/lib/rspec_profiling/run.rb +33 -5
  33. data/lib/rspec_profiling/vcs/git.rb +1 -1
  34. data/lib/rspec_profiling/version.rb +1 -1
  35. data/lib/rspec_profiling.rb +14 -2
  36. data/rspec_profiling.gemspec +11 -6
  37. metadata +47 -35
  38. data/.ruby-gemset +0 -1
  39. data/rspec_profiling +0 -0
  40. data/spec/collectors/psql_spec.rb +0 -90
  41. data/spec/collectors/sql_spec.rb +0 -90
  42. data/spec/run_spec.rb +0 -151
  43. data/spec/vcs/git_spec.rb +0 -27
  44. data/spec/vcs/svn_spec.rb +0 -25
data/README.md CHANGED
@@ -1,10 +1,16 @@
1
1
  # RspecProfiling
2
2
 
3
+ [![Test](https://github.com/procore-oss/rspec_profiling/actions/workflows/test.yaml/badge.svg?branch=main)](https://github.com/procore-oss/rspec_profiling/actions/workflows/test.yaml)
4
+ [![Gem Version](https://badge.fury.io/rb/rspec_profiling.svg)](https://badge.fury.io/rb/rspec_profiling)
5
+ [![Discord](https://img.shields.io/badge/Chat-EDEDED?logo=discord)](https://discord.gg/PbntEMmWws)
6
+
7
+
3
8
  Collects profiles of RSpec test suites, enabling you to identify specs
4
9
  with interesting attributes. For example, find the slowest specs, or the
5
10
  spec which issues the most queries.
6
11
 
7
12
  Collected attributes include:
13
+
8
14
  - git commit SHA (or SVN revision) and date
9
15
  - example file, line number and description
10
16
  - example status (i.e. passed or failed)
@@ -21,29 +27,36 @@ RspecProfiling should work with Rails >= 3.2 and RSpec >= 2.14.
21
27
 
22
28
  Add this line to your application's Gemfile:
23
29
 
24
- ```
30
+ ```ruby
25
31
  gem 'rspec_profiling'
26
32
  ```
27
33
 
28
34
  And then execute:
29
35
 
30
- ```
36
+ ```bash
31
37
  bundle
32
38
  ```
33
39
 
34
40
  Require the gem to your `spec_helper.rb`.
35
41
 
36
- ```
42
+ ```ruby
37
43
  require "rspec_profiling/rspec"
38
44
  ```
39
45
 
40
46
  Lastly, run the installation rake tasks to initialize an empty database in
41
47
  which results will be collected.
42
48
 
43
- ```
49
+ ```bash
44
50
  bundle exec rake rspec_profiling:install
45
51
  ```
46
52
 
53
+ If you are planning on using `sqlite` or `pg` ensure to add the dependency to your gemfile
54
+
55
+ ```ruby
56
+ gem 'sqlite', require: false
57
+ gem 'pg', require: false
58
+ ```
59
+
47
60
  ## Usage
48
61
 
49
62
  ### Choose a version control system
@@ -74,19 +87,51 @@ RspecProfiling.configure do |config|
74
87
  end
75
88
  ```
76
89
 
90
+ #### Custom Event Subscriptions
91
+
92
+ ```Ruby
93
+ RspecProfiling.configure do |config|
94
+ config.events = %w[event1 event2]
95
+ end
96
+ ```
97
+
98
+ Note that custom events are only currently reported by the CSV collector.
99
+
100
+ #### Custom Event Recording
101
+
102
+ It is possible to record the event metadata for a spec.
103
+
104
+ ```Ruby
105
+ describe 'Records all active record queries', record_events: %w[sql.active_record] do
106
+ it 'Records Rails deprecations', record_events: %w[deprecation.rails] do
107
+ ...
108
+ end
109
+ it 'Records nothing' do
110
+ ...
111
+ end
112
+ end
113
+ ```
114
+
77
115
  ### Choose a results collector
78
116
 
79
117
  Results are collected just by running the specs.
80
118
 
81
119
  #### SQLite3
82
120
 
83
- By default, profiles are collected in an SQL database. Make sure you've
84
- run the installation rake task before attempting.
121
+ Make sure you've run the installation rake task before attempting.
122
+
123
+ You can configure `RspecProfiling` to collect results in a SQL database in `config/initializers/rspec_profiling.rb`:
124
+
125
+ ```Ruby
126
+ RspecProfiling.configure do |config|
127
+ config.collector = RspecProfiling::Collectors::SQL
128
+ end
129
+ ```
85
130
 
86
131
  You can review results by running the RspecProfiling console.
87
132
  The console has a preloaded `results` variable.
88
133
 
89
- ```
134
+ ```bash
90
135
  bundle exec rake rspec_profiling:console
91
136
 
92
137
  > results.count
@@ -95,14 +140,14 @@ bundle exec rake rspec_profiling:console
95
140
 
96
141
  You can find the spec that runs the most queries:
97
142
 
98
- ```
143
+ ```ruby
99
144
  > results.order(:query_count).last.to_s
100
145
  => "Updating my account - ./spec/features/account_spec.rb:15"
101
146
  ```
102
147
 
103
148
  Or find the spec that takes the most time:
104
149
 
105
- ```
150
+ ```ruby
106
151
  > results.order(:time).last.to_s
107
152
  => "Updating my account - ./spec/features/account_spec.rb:15"
108
153
  ```
@@ -112,7 +157,7 @@ debugging, such as `exception` and `status`.
112
157
 
113
158
  #### CSV
114
159
 
115
- You can configure `RspecProfiling` to collect results in a CSV in `config/initializers/rspec_profiling.rb`:
160
+ By default, profiles are collected in an a CSV file. You can configure `RspecProfiling` to collect results in a CSV in `config/initializers/rspec_profiling.rb`:
116
161
 
117
162
  ```Ruby
118
163
  RspecProfiling.configure do |config|
@@ -177,16 +222,18 @@ To remove the results database, run `bundle exec rake rspec_profiling:uninstall`
177
222
 
178
223
  ## Contributing
179
224
 
180
- 1. Fork it
181
- 2. Create your feature branch (`git checkout -b my-new-feature`)
182
- 3. Commit your changes (`git commit -am 'Add some feature'`)
183
- 4. Push to the branch (`git push origin my-new-feature`)
184
- 5. Create new Pull Request
225
+ Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
185
226
 
186
- ## About Breakwater
227
+ ## Local Development
187
228
 
188
- ![Breakwater Logo](https://images.squarespace-cdn.com/content/5d084fe43b0b620001239437/1565926359217-2LQ1BOFAO5846OAYAGZV/breakwater.png?content-type=image%2Fpng)
229
+ Local tools needed:
189
230
 
190
- Breakwater builds exciting web and mobile apps in Louisville, CO. Our work powers a wide variety of businesses with many different needs. We love open source software, and we're proud to contribute where we can. Interested to learn more? [Contact us today](https://www.breakwaterltd.com/contact).
231
+ - docker
232
+ - docker-compose
233
+ - ruby
191
234
 
192
- This project is maintained by Breakwater. The names and logos of Breakwater are fully owned and copyright Breakwater Limited.
235
+ To run the specs:
236
+
237
+ ```bash
238
+ make spec
239
+ ```
data/SECURITY.md ADDED
@@ -0,0 +1,20 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ Ruby versions that are currently being supported with security updates.
6
+
7
+ | Version | Supported |
8
+ | ------- | ------------------ |
9
+ | <=2.6 | :x: |
10
+ | 2.7 | :white_check_mark: |
11
+ | 3.0 | :white_check_mark: |
12
+ | 3.1 | :white_check_mark: |
13
+ | 3.2 | :white_check_mark: |
14
+ | 3.3 | :white_check_mark: |
15
+
16
+ ## Reporting a Vulnerability
17
+
18
+ Please click the `Report a vulnerability` button [here](https://github.com/procore-oss/rspec_profiling/security) to report a vulnerability.
19
+
20
+ A maintainer will respond to you as soon as possible and discuss the process to get the vulnerability fixed.
@@ -0,0 +1,13 @@
1
+ version: '3.9'
2
+
3
+ services:
4
+ postgres:
5
+ image: postgres:16-alpine
6
+ ports:
7
+ - 5432:5432
8
+ volumes:
9
+ - ~/apps/postgres:/var/lib/postgresql/data
10
+ environment:
11
+ - POSTGRES_HOST=localhost
12
+ - POSTGRES_USER=myuser
13
+ - POSTGRES_PASSWORD=mypassword
@@ -6,6 +6,7 @@ module RspecProfiling
6
6
  HEADERS = %w{
7
7
  branch
8
8
  commit_hash
9
+ seed
9
10
  date
10
11
  file
11
12
  line_number
@@ -17,6 +18,8 @@ module RspecProfiling
17
18
  query_time
18
19
  request_count
19
20
  request_time
21
+ start_memory
22
+ end_memory
20
23
  }
21
24
 
22
25
  def self.install
@@ -31,24 +34,44 @@ module RspecProfiling
31
34
  # no op
32
35
  end
33
36
 
34
- def initialize
35
- RspecProfiling.config.csv_path ||= 'tmp/spec_benchmarks.csv'
37
+ def initialize(config=RspecProfiling.config)
38
+ config.csv_path ||= 'tmp/spec_benchmarks.csv'
39
+
40
+ @config = config
36
41
  end
37
42
 
38
43
  def insert(attributes)
39
- output << HEADERS.map do |field|
40
- attributes.fetch(field.to_sym)
41
- end
44
+ output << static_cells(attributes) + event_cells(attributes)
42
45
  end
43
46
 
44
47
  private
45
48
 
49
+ attr_reader :config
50
+
46
51
  def output
47
- @output ||= ::CSV.open(path, "w").tap { |csv| csv << HEADERS }
52
+ @output ||= ::CSV.open(path, "w").tap { |csv| csv << HEADERS + event_headers }
48
53
  end
49
54
 
50
55
  def path
51
- RspecProfiling.config.csv_path.call
56
+ config.csv_path.call
57
+ end
58
+
59
+ def static_cells(attributes)
60
+ HEADERS.map do |field|
61
+ attributes.fetch(field.to_sym)
62
+ end
63
+ end
64
+
65
+ def event_headers
66
+ config.events.flat_map do |event|
67
+ ["#{event}_count", "#{event}_time", "#{event}_events"]
68
+ end
69
+ end
70
+
71
+ def event_cells(attributes)
72
+ config.events.flat_map do |event|
73
+ [attributes[:event_counts][event], attributes[:event_times][event], attributes[:event_events][event].to_json]
74
+ end
52
75
  end
53
76
  end
54
77
  end
@@ -0,0 +1,70 @@
1
+ module RspecProfiling
2
+ module Collectors
3
+ class JSON
4
+ KEYS = %w{
5
+ branch
6
+ commit_hash
7
+ seed
8
+ date
9
+ file
10
+ line_number
11
+ description
12
+ status
13
+ exception
14
+ time
15
+ query_count
16
+ query_time
17
+ request_count
18
+ request_time
19
+ start_memory
20
+ end_memory
21
+ }
22
+
23
+ def self.install
24
+ # no op
25
+ end
26
+
27
+ def self.uninstall
28
+ # no op
29
+ end
30
+
31
+ def self.reset
32
+ # no op
33
+ end
34
+
35
+ def initialize(config=RspecProfiling.config)
36
+ config.output_file_path ||= 'tmp/spec_benchmarks.json'
37
+
38
+ @config = config
39
+ end
40
+
41
+ def insert(attributes)
42
+ output << merge_attributes_and_events(attributes) + "\n"
43
+ end
44
+
45
+ private
46
+
47
+ attr_reader :config
48
+
49
+ def output
50
+ @output ||= ::File.open(path, "w")
51
+ end
52
+
53
+ def path
54
+ config.output_file_path.call
55
+ end
56
+
57
+ def merge_attributes_and_events(attributes)
58
+ config.events.flat_map do |event|
59
+ attributes["#{event}_counts"] = attributes[:event_counts][event]
60
+ attributes["#{event}_times"] = attributes[:event_times][event]
61
+ attributes["#{event}_events"] = attributes[:event_events][event]
62
+ end
63
+
64
+ attributes.merge!(config.additional_data)
65
+
66
+ attributes.except(:event_counts, :event_times, :event_events, :events).to_json
67
+ end
68
+ end
69
+ end
70
+ end
@@ -47,7 +47,7 @@ module RspecProfiling
47
47
  end
48
48
 
49
49
  def insert(attributes)
50
- results.create!(attributes.except(:created_at))
50
+ results.create!(attributes.except(:created_at, :events, :event_counts, :event_times, :event_events))
51
51
  end
52
52
 
53
53
  def results
@@ -47,7 +47,7 @@ module RspecProfiling
47
47
  end
48
48
 
49
49
  def insert(attributes)
50
- results.create!(attributes.except(:created_at))
50
+ results.create!(attributes.except(:created_at, :events, :event_counts, :event_times, :event_events))
51
51
  end
52
52
 
53
53
  def results
@@ -5,9 +5,11 @@ module RspecProfiling
5
5
 
6
6
  def self.config
7
7
  @config ||= OpenStruct.new({
8
- collector: RspecProfiling::Collectors::SQL,
8
+ collector: RspecProfiling::Collectors::CSV,
9
9
  vcs: RspecProfiling::VCS::Git,
10
- table_name: 'spec_profiling_results'
10
+ table_name: 'spec_profiling_results',
11
+ events: [],
12
+ additional_data: {}
11
13
  })
12
14
  end
13
15
  end
@@ -21,6 +21,9 @@ module RspecProfiling
21
21
  def initialize(example)
22
22
  @example = example
23
23
  @counts = Hash.new(0)
24
+ @event_counts = Hash.new(0)
25
+ @event_times = Hash.new(0)
26
+ @event_events = Hash.new()
24
27
  end
25
28
 
26
29
  def file
@@ -38,7 +41,7 @@ module RspecProfiling
38
41
  def status
39
42
  execution_result.status
40
43
  end
41
-
44
+
42
45
  def exception
43
46
  execution_result.exception
44
47
  end
@@ -63,6 +66,8 @@ module RspecProfiling
63
66
  counts[:request_time]
64
67
  end
65
68
 
69
+ attr_reader :event_counts, :event_times, :event_events
70
+
66
71
  def log_query(query, start, finish)
67
72
  unless query[:sql] =~ IGNORED_QUERIES_PATTERN
68
73
  counts[:query_count] += 1
@@ -75,6 +80,19 @@ module RspecProfiling
75
80
  counts[:request_time] += request[:view_runtime].to_f
76
81
  end
77
82
 
83
+ def log_event(event_name, event, start, finish)
84
+ event_counts[event_name] += 1
85
+ event_times[event_name] += (finish - start)
86
+ event_events[event_name] ||= []
87
+ if verbose_record_event?(event_name)
88
+ begin
89
+ event_events[event_name] << event.as_json
90
+ rescue => e
91
+ # no op
92
+ end
93
+ end
94
+ end
95
+
78
96
  private
79
97
 
80
98
  attr_reader :example, :counts
@@ -90,5 +108,9 @@ module RspecProfiling
90
108
  def metadata
91
109
  example.metadata
92
110
  end
111
+
112
+ def verbose_record_event?(event_name)
113
+ metadata[:record_events].to_a.include?(event_name)
114
+ end
93
115
  end
94
116
  end
@@ -1,7 +1,7 @@
1
1
  require "rspec_profiling"
2
2
 
3
3
  RSpec.configure do |config|
4
- runner = RspecProfiling::Run.new(RspecProfiling.config.collector.new,
4
+ runner = RspecProfiling::Run.new(RspecProfiling.config.collector.new,
5
5
  RspecProfiling.config.vcs.new)
6
6
 
7
7
  config.reporter.register_listener(
@@ -1,26 +1,31 @@
1
+ require "get_process_mem"
1
2
  require "rspec_profiling/example"
2
3
  require "rspec_profiling/vcs/git"
3
4
  require "rspec_profiling/vcs/svn"
4
5
  require "rspec_profiling/vcs/git_svn"
5
- require "rspec_profiling/collectors/sql"
6
- require "rspec_profiling/collectors/psql"
7
6
  require "rspec_profiling/collectors/csv"
7
+ require "rspec_profiling/collectors/json"
8
8
 
9
9
  module RspecProfiling
10
10
  class Run
11
11
  def initialize(collector = RspecProfiling.config.collector.new,
12
- vcs = RspecProfiling.config.vcs.new)
12
+ vcs = RspecProfiling.config.vcs.new,
13
+ events = RspecProfiling.config.events)
13
14
 
14
15
  @collector = collector
15
16
  @vcs = vcs
17
+ @events = events
18
+ @seed = RSpec.configuration.seed
16
19
  end
17
20
 
18
21
  def start(*args)
19
22
  start_counting_queries
20
23
  start_counting_requests
24
+ start_counting_events
21
25
  end
22
26
 
23
27
  def example_started(example)
28
+ start_recording_memory
24
29
  example = example.example if example.respond_to?(:example)
25
30
  @current_example = Example.new(example)
26
31
  end
@@ -29,6 +34,7 @@ module RspecProfiling
29
34
  collector.insert({
30
35
  branch: vcs.branch,
31
36
  commit_hash: vcs.sha,
37
+ seed: @seed,
32
38
  date: vcs.time,
33
39
  file: @current_example.file,
34
40
  line_number: @current_example.line_number,
@@ -39,7 +45,13 @@ module RspecProfiling
39
45
  query_count: @current_example.query_count,
40
46
  query_time: @current_example.query_time,
41
47
  request_count: @current_example.request_count,
42
- request_time: @current_example.request_time
48
+ request_time: @current_example.request_time,
49
+ events: @events,
50
+ event_counts: @current_example.event_counts,
51
+ event_times: @current_example.event_times,
52
+ event_events: @current_example.event_events,
53
+ start_memory: @start_memory,
54
+ end_memory: end_memory
43
55
  })
44
56
  end
45
57
 
@@ -48,7 +60,15 @@ module RspecProfiling
48
60
 
49
61
  private
50
62
 
51
- attr_reader :collector, :vcs
63
+ attr_reader :collector, :vcs, :events, :seed, :start_memory
64
+
65
+ def end_memory
66
+ GetProcessMem.new.mb
67
+ end
68
+
69
+ def start_recording_memory
70
+ @start_memory = GetProcessMem.new.mb
71
+ end
52
72
 
53
73
  def start_counting_queries
54
74
  ActiveSupport::Notifications.subscribe("sql.active_record") do |name, start, finish, id, query|
@@ -61,5 +81,13 @@ module RspecProfiling
61
81
  @current_example.try(:log_request, request, start, finish)
62
82
  end
63
83
  end
84
+
85
+ def start_counting_events
86
+ events.each do |event_name|
87
+ ActiveSupport::Notifications.subscribe(event_name) do |name, start, finish, id, event|
88
+ @current_example.try(:log_event, event_name, event, start, finish)
89
+ end
90
+ end
91
+ end
64
92
  end
65
93
  end
@@ -12,7 +12,7 @@ module RspecProfiling
12
12
  end
13
13
 
14
14
  def time
15
- Time.parse `git show -s --format=%ci #{sha}`
15
+ Time.parse `git show -s --format=%ci #{sha}`.chomp
16
16
  end
17
17
  end
18
18
  end
@@ -1,3 +1,3 @@
1
1
  module RspecProfiling
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.8"
3
3
  end
@@ -4,12 +4,24 @@ require "rspec_profiling/config"
4
4
  require "rspec_profiling/version"
5
5
  require "rspec_profiling/run"
6
6
  require "rspec_profiling/collectors/csv"
7
- require "rspec_profiling/collectors/sql"
8
- require "rspec_profiling/collectors/psql"
9
7
  require "rspec_profiling/vcs/git"
10
8
  require "rspec_profiling/vcs/svn"
11
9
  require "rspec_profiling/vcs/git_svn"
12
10
 
11
+ begin
12
+ require "rspec_profiling/collectors/sql"
13
+ rescue LoadError
14
+ #no op
15
+ end
16
+
17
+ begin
18
+ require "rspec_profiling/collectors/psql"
19
+ rescue LoadError
20
+ #no op
21
+ end
22
+
23
+
24
+
13
25
  module RspecProfiling
14
26
  class Railtie < Rails::Railtie
15
27
  railtie_name :rspec_profiling
@@ -6,24 +6,29 @@ require 'rspec_profiling/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "rspec_profiling"
8
8
  spec.version = RspecProfiling::VERSION
9
- spec.authors = ["Ben Eddy"]
10
- spec.email = ["bae@foraker.com"]
9
+ spec.authors = ["Procore Technologies, Inc."]
10
+ spec.email = ["opensource@procore.com"]
11
11
  spec.description = %q{Profile RSpec test suites}
12
12
  spec.summary = %q{Profile RSpec test suites}
13
- spec.homepage = "https://github.com/foraker/rspec_profiling"
13
+ spec.homepage = "https://github.com/procore-oss/rspec_profiling"
14
14
  spec.license = "MIT"
15
15
 
16
- spec.files = `git ls-files`.split($/)
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "sqlite3"
22
21
  spec.add_dependency "activerecord"
23
- spec.add_dependency "pg"
22
+ spec.add_dependency "get_process_mem"
24
23
  spec.add_dependency "rails"
25
24
 
26
25
  spec.add_development_dependency "bundler", "> 1.3"
26
+ spec.add_development_dependency "pry"
27
27
  spec.add_development_dependency "rake"
28
28
  spec.add_development_dependency "rspec"
29
+
30
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
31
+ spec.metadata['rubygems_mfa_required'] = 'true'
32
+ spec.metadata['homepage_uri'] = spec.homepage
33
+ spec.metadata['source_code_uri'] = spec.homepage
29
34
  end