rspec_profiling 0.0.6 → 0.0.7

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