rspec_profiling 0.0.6 → 0.0.7

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 +61 -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 +9 -6
  37. metadata +45 -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
@@ -5,6 +5,7 @@ with interesting attributes. For example, find the slowest specs, or the
5
5
  spec which issues the most queries.
6
6
 
7
7
  Collected attributes include:
8
+
8
9
  - git commit SHA (or SVN revision) and date
9
10
  - example file, line number and description
10
11
  - example status (i.e. passed or failed)
@@ -21,29 +22,36 @@ RspecProfiling should work with Rails >= 3.2 and RSpec >= 2.14.
21
22
 
22
23
  Add this line to your application's Gemfile:
23
24
 
24
- ```
25
+ ```ruby
25
26
  gem 'rspec_profiling'
26
27
  ```
27
28
 
28
29
  And then execute:
29
30
 
30
- ```
31
+ ```bash
31
32
  bundle
32
33
  ```
33
34
 
34
35
  Require the gem to your `spec_helper.rb`.
35
36
 
36
- ```
37
+ ```ruby
37
38
  require "rspec_profiling/rspec"
38
39
  ```
39
40
 
40
41
  Lastly, run the installation rake tasks to initialize an empty database in
41
42
  which results will be collected.
42
43
 
43
- ```
44
+ ```bash
44
45
  bundle exec rake rspec_profiling:install
45
46
  ```
46
47
 
48
+ If you are planning on using `sqlite` or `pg` ensure to add the dependency to your gemfile
49
+
50
+ ```ruby
51
+ gem 'sqlite', require: false
52
+ gem 'pg', require: false
53
+ ```
54
+
47
55
  ## Usage
48
56
 
49
57
  ### Choose a version control system
@@ -74,19 +82,51 @@ RspecProfiling.configure do |config|
74
82
  end
75
83
  ```
76
84
 
85
+ #### Custom Event Subscriptions
86
+
87
+ ```Ruby
88
+ RspecProfiling.configure do |config|
89
+ config.events = %w[event1 event2]
90
+ end
91
+ ```
92
+
93
+ Note that custom events are only currently reported by the CSV collector.
94
+
95
+ #### Custom Event Recording
96
+
97
+ It is possible to record the event metadata for a spec.
98
+
99
+ ```Ruby
100
+ describe 'Records all active record queries', record_events: %w[sql.active_record] do
101
+ it 'Records Rails deprecations', record_events: %w[deprecation.rails] do
102
+ ...
103
+ end
104
+ it 'Records nothing' do
105
+ ...
106
+ end
107
+ end
108
+ ```
109
+
77
110
  ### Choose a results collector
78
111
 
79
112
  Results are collected just by running the specs.
80
113
 
81
114
  #### SQLite3
82
115
 
83
- By default, profiles are collected in an SQL database. Make sure you've
84
- run the installation rake task before attempting.
116
+ Make sure you've run the installation rake task before attempting.
117
+
118
+ You can configure `RspecProfiling` to collect results in a SQL database in `config/initializers/rspec_profiling.rb`:
119
+
120
+ ```Ruby
121
+ RspecProfiling.configure do |config|
122
+ config.collector = RspecProfiling::Collectors::SQL
123
+ end
124
+ ```
85
125
 
86
126
  You can review results by running the RspecProfiling console.
87
127
  The console has a preloaded `results` variable.
88
128
 
89
- ```
129
+ ```bash
90
130
  bundle exec rake rspec_profiling:console
91
131
 
92
132
  > results.count
@@ -95,14 +135,14 @@ bundle exec rake rspec_profiling:console
95
135
 
96
136
  You can find the spec that runs the most queries:
97
137
 
98
- ```
138
+ ```ruby
99
139
  > results.order(:query_count).last.to_s
100
140
  => "Updating my account - ./spec/features/account_spec.rb:15"
101
141
  ```
102
142
 
103
143
  Or find the spec that takes the most time:
104
144
 
105
- ```
145
+ ```ruby
106
146
  > results.order(:time).last.to_s
107
147
  => "Updating my account - ./spec/features/account_spec.rb:15"
108
148
  ```
@@ -112,7 +152,7 @@ debugging, such as `exception` and `status`.
112
152
 
113
153
  #### CSV
114
154
 
115
- You can configure `RspecProfiling` to collect results in a CSV in `config/initializers/rspec_profiling.rb`:
155
+ 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
156
 
117
157
  ```Ruby
118
158
  RspecProfiling.configure do |config|
@@ -177,16 +217,18 @@ To remove the results database, run `bundle exec rake rspec_profiling:uninstall`
177
217
 
178
218
  ## Contributing
179
219
 
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
220
+ Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
185
221
 
186
- ## About Breakwater
222
+ ## Local Development
187
223
 
188
- ![Breakwater Logo](https://images.squarespace-cdn.com/content/5d084fe43b0b620001239437/1565926359217-2LQ1BOFAO5846OAYAGZV/breakwater.png?content-type=image%2Fpng)
224
+ Local tools needed:
189
225
 
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).
226
+ - docker
227
+ - docker-compose
228
+ - ruby
191
229
 
192
- This project is maintained by Breakwater. The names and logos of Breakwater are fully owned and copyright Breakwater Limited.
230
+ To run the specs:
231
+
232
+ ```bash
233
+ make spec
234
+ ```
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.7"
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,27 @@ 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'
29
32
  end