runtime_profiler 0.1.4 → 0.3.0

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
  SHA256:
3
- metadata.gz: e33ac72fec2841f6fb3c5b50ae46aece8bd6902ff177392dfc056e7f6ae336d6
4
- data.tar.gz: 1053411fdb7d59d1c54f9c89cd1f465dbf8cc1b280bf9d42a93ee5055fc223bf
3
+ metadata.gz: 7839cd9d608c73c35a269e4bf7f2f52ae482e714dd99a108e9078e6628f9a86b
4
+ data.tar.gz: 9eecf865436dcd07b575d96c1bfb21d1ba1c9344c9100aa955ed00a84d2e8011
5
5
  SHA512:
6
- metadata.gz: 430be7e054bd7748c36bf468bf28d134d35a43a441d5d3fff6730e29d15d173a74ce5e64b8e3711ff63291b10dc8c3977f58a3caecff6c649936f632d7b15be7
7
- data.tar.gz: b2d7f4f5019f489def998d8858bbec09c99be054289429482858836a0e88a2e49e22626e2f31461cedf4e9f5473eafb8baac4e0818528c2aec04a824d68683d7
6
+ metadata.gz: eaeedf57a5c02b27e1f75778290f821a4b0ed3901220c7dce8529df44d4bf49fe8d0846a4bb3e7e73b485c96196f85d7d57a6cc3e8df93bfda8b05676526ddc1
7
+ data.tar.gz: 196e71a0244d7d47c8af6585213c45373dcc8d694bac70daa86e9688877d4b02fc029bc50df87baf3ce288e64c8c7850ea717603975eb38a9da367e34998d9a5
@@ -0,0 +1,18 @@
1
+ version: v1.0
2
+ name: Ruby
3
+ agent:
4
+ machine:
5
+ type: e1-standard-2
6
+ os_image: ubuntu1804
7
+ blocks:
8
+ - name: test
9
+ task:
10
+ jobs:
11
+ - name: test
12
+ commands:
13
+ - checkout
14
+ - sem-version ruby 2.6.5
15
+ - cache restore
16
+ - bundle install --path vendor/bundle
17
+ - cache store
18
+ - bundle exec rake
data/README.md CHANGED
@@ -1,13 +1,9 @@
1
- # runtime_profiler - Runtime Profiler for Rails Applications
1
+ # runtime_profiler - Runtime Profiler for Rails Applications [![Build Status](https://wnuqui.semaphoreci.com/badges/runtime_profiler/branches/master.svg?style=shields)](https://wnuqui.semaphoreci.com/projects/runtime_profiler)
2
2
 
3
- `runtime_profiler` instruments api or a method of your Rails application using Rails' `ActiveSupport::Notifications`
3
+ `runtime_profiler` instruments API endpoints or methods in your Rails application using Rails' `ActiveSupport::Notifications`. Currently, it is intended to be used during development or test.
4
4
 
5
5
  It then aggregates and generates report to give you insights about specific calls in your Rails application.
6
6
 
7
- ## Note
8
-
9
- This is still a **work in progress**. However, this is a tool meant to be used in development so it is safe to use.
10
-
11
7
  ## Installation
12
8
 
13
9
  Add this line to your application's Gemfile:
@@ -25,9 +21,9 @@ And then execute:
25
21
 
26
22
  ## Profiling/Instrumenting
27
23
 
28
- To start profiling, make a test and use `RuntimeProfiler.profile!` method in the tests. The output of instrumentation will be generated under the `tmp` folder of your application.
24
+ To start profiling, you can make a test that targets a particular endpoint and use `RuntimeProfiler.profile!` method in the test. The output of instrumentation will be generated under the `tmp` folder of your application.
29
25
 
30
- Example:
26
+ Example of a test code wrap by `RuntimeProfiler.profile!` method:
31
27
  ```ruby
32
28
  it 'updates user' do
33
29
  RuntimeProfiler.profile!('updates user', [User]) {
@@ -38,19 +34,72 @@ it 'updates user' do
38
34
  end
39
35
  ```
40
36
 
41
- Run tests as usual and follow printed instructions after running tests.
37
+ Run the test as usual and follow printed instructions after running.
42
38
 
43
- ## Reporting
39
+ If you prefer writing just code snippet, then just wrap the snippet with `RuntimeProfiler.profile!` method:
40
+ ```ruby
41
+ RuntimeProfiler.profile!('UserMailer', [UserMailer]) {
42
+ user = User.last
43
+ UserMailer.with(user: user).weekly_summary.deliver_now
44
+ }
45
+ ```
44
46
 
45
- To see profiling/instrumenting report, please open the report in browser with JSON viewer report. Or you can run the following command:
47
+ **Note:** The code (tests or not) where `RuntimeProfiler.profile!` is used must be **free from any mocking** since your goal is to check bottlenecks.
48
+
49
+ ## Viewing Profiling Result
50
+
51
+ To see profiling/instrumenting report, you can open the report in browser with JSON viewer report. Or you can run the following command:
46
52
 
47
53
  ```bash
48
54
  bundle exec runtime_profiler view ~/the-rails-app/tmp/runtime-profiling-51079-1521371428.json
49
55
  ```
50
56
 
57
+ ### view options
58
+
59
+ Here are the command line options for `runtime_profiler view` command.
60
+
61
+ ```bash
62
+ $ bundle exec runtime_profiler view --help
63
+
64
+ NAME:
65
+
66
+ view
67
+
68
+ SYNOPSIS:
69
+
70
+ runtime_profiler view <profile.report.json> [options]
71
+
72
+ DESCRIPTION:
73
+
74
+ Display report in console given the JSON report file
75
+
76
+ OPTIONS:
77
+
78
+ --sort-by COLUMN
79
+ Sort by COLUMN. COLUMN can be "max_runtime", total_calls" or "total_runtime". Default is "max_runtime".
80
+
81
+ --details TYPE
82
+ TYPE can be "full" or "summary". Default is "summary"
83
+
84
+ --only-sqls
85
+ Show only SQL queries. Default is false.
86
+
87
+ --only-methods
88
+ Show only methods. Default is false.
89
+
90
+ --runtime-above RUNTIME
91
+ RUNTIME is integer or float value in ms.
92
+
93
+ --calls-above CALLS
94
+ CALLS is integer value.
95
+
96
+ --rounding ROUNDING
97
+ ROUNDING is integer value. Used in rounding runtimes. Default is 4.
98
+ ```
99
+
51
100
  ## Configurations
52
101
 
53
- All the configurable variables and their defaults are listed below:
102
+ All the configurable variables and their defaults are listed below. These configurations can be put in the `config/initializers` folder of your Rails application.
54
103
  ```ruby
55
104
  RuntimeProfiler.output_path = File.join(Rails.root.to_s, 'tmp')
56
105
  RuntimeProfiler.instrumented_constants = [User]
@@ -41,17 +41,5 @@ module RuntimeProfiler
41
41
 
42
42
  profiler.save_instrumentation_data
43
43
  end
44
-
45
- def runtime(label='for the block')
46
- result = nil
47
-
48
- elapsed_time = Benchmark.realtime { result = yield }
49
-
50
- puts
51
- puts '~~~~> ELAPSED TIME (%s): %s' % [label, elapsed_time * 1000]
52
- puts
53
-
54
- result
55
- end
56
44
  end
57
45
  end
@@ -16,28 +16,33 @@ module RuntimeProfiler
16
16
  c.syntax = 'runtime_profiler view <profile.report.json> [options]'
17
17
  c.description = 'Display report in console given the JSON report file'
18
18
 
19
- c.option '--sort-by COLUMN', String, 'Sort by COLUMN. COLUMN can either be "total_calls" or "total_runtime". Default is "total_calls".'
20
- c.option '--details TYPE', String, 'TYPE can be "full" or "summary".'
21
- c.option '--runtime-above RUNTIME', String, 'RUNTIME is numeric value in ms.'
22
- c.option '--only-sqls', String, 'Show only SQL(s).'
23
- c.option '--only-methods', String, 'Show only method(s).'
24
- c.option '--calls-above CALLS', String, 'CALLS is numeric value.'
19
+ c.option '--sort-by COLUMN', String, 'Sort by COLUMN. COLUMN can be "max_runtime", total_calls" or "total_runtime". Default is "max_runtime".'
20
+ c.option '--details TYPE', String, 'TYPE can be "full" or "summary". Default is "summary"'
21
+ c.option '--only-sqls', String, 'Show only SQL queries. Default is false.'
22
+ c.option '--only-methods', String, 'Show only methods. Default is false.'
23
+ c.option '--runtime-above RUNTIME', Float, 'RUNTIME is integer or float value in ms.'
24
+ c.option '--calls-above CALLS', Integer, 'CALLS is integer value.'
25
+ c.option '--rounding ROUNDING', Integer, 'ROUNDING is integer value. Used in rounding runtimes. Default is 4.'
25
26
 
26
27
  c.action do |args, options|
27
28
  default_options = {
28
- sort_by: 'total_calls',
29
+ sort_by: 'max_runtime',
29
30
  details: 'summary',
30
31
  runtime_above: 0,
31
32
  only_sqls: false,
32
33
  only_methods: false,
33
- calls_above: 1
34
+ calls_above: 0,
35
+ rounding: 4
34
36
  }
35
37
 
36
38
  options.default default_options
37
39
 
38
- report = RuntimeProfiler::TextReport.new(args.first, options)
39
-
40
- report.print
40
+ if args.first.nil?
41
+ say 'You need to supply <profile.report.json> as first argument of view.'
42
+ else
43
+ report = RuntimeProfiler::TextReport.new(args.first, options)
44
+ report.print
45
+ end
41
46
  end
42
47
  end
43
48
 
@@ -3,12 +3,15 @@ module RuntimeProfiler
3
3
  attr_reader :controller_data, :sql_data
4
4
 
5
5
  def initialize(controller_data: nil, sql_data: nil)
6
- @controller_data = controller_data
6
+ @controller_data = controller_data || {}
7
7
  @sql_data = sql_data
8
8
  end
9
9
 
10
10
  def persist!
11
- instrumented_api = [controller_data[:payload][:controller], controller_data[:payload][:action]].join('#')
11
+ instrumented_api = [
12
+ controller_data[:payload][:controller],
13
+ controller_data[:payload][:action]
14
+ ].join('#') if controller_data[:payload]
12
15
 
13
16
  instrumentation_data = {
14
17
  instrumentation: {
@@ -38,7 +41,8 @@ module RuntimeProfiler
38
41
  f.write JSON.dump(instrumentation_data)
39
42
  end
40
43
 
41
- puts '~~~~> [ Profiling RUNTIME ] Profiling now COMPLETE and JSON report written at ' + output_file.to_s
44
+ puts "\n"
45
+ puts '~~~~> [ Profiling RUNTIME ] Profiling now COMPLETE and JSON report is written at ' + output_file.to_s
42
46
  puts '~~~~> [ Profiling RUNTIME ]'
43
47
  puts '~~~~> [ Profiling RUNTIME ] You can do the following to view the JSON report in console:'
44
48
  puts '~~~~> [ Profiling RUNTIME ]'
@@ -1,6 +1,7 @@
1
1
  require 'hirb'
2
2
  require 'terminal-table'
3
3
  require 'active_support/core_ext/string'
4
+ require 'pry'
4
5
 
5
6
  module RuntimeProfiler
6
7
  class TextReport
@@ -8,17 +9,17 @@ module RuntimeProfiler
8
9
  DURATION_WIDTH = 22
9
10
  TOTAL_RUNTIME_WIDTH = 20
10
11
 
11
- SUMMARY_TEMPLATE = <<-EOT.strip_heredoc
12
+ FULL_DETAILS_TEMPLATE = <<-EOT.strip_heredoc
12
13
 
13
14
  \e[1mPROFILING REPORT\e[22m
14
15
  ----------------
15
16
 
16
- \e[1mRUNTIME\e[22m
17
+ \e[1mAPI RUNTIME\e[22m
17
18
  Total Runtime : %s ms
18
19
  Database Runtime : %s ms
19
20
  View Runtime : %s ms
20
21
 
21
- \e[1mMETHODS\e[22m
22
+ \e[1mMETHOD CALLS\e[22m
22
23
  SLOWEST : %s (%s ms)
23
24
  MOSTLY CALLED : %s (%s number of calls in %s ms)
24
25
 
@@ -39,48 +40,121 @@ module RuntimeProfiler
39
40
 
40
41
  EOT
41
42
 
43
+ METHODS_DETAILS_TEMPLATE = <<-EOT.strip_heredoc
44
+
45
+ \e[1mPROFILING REPORT\e[22m
46
+ ----------------
47
+
48
+ \e[1mAPI RUNTIME\e[22m
49
+ Total Runtime : %s ms
50
+ Database Runtime : %s ms
51
+ View Runtime : %s ms
52
+
53
+ \e[1mMETHOD CALLS\e[22m
54
+ SLOWEST : %s (%s ms)
55
+ MOSTLY CALLED : %s (%s number of calls in %s ms)
56
+
57
+ EOT
58
+
59
+ SQLS_DETAILS_TEMPLATE = <<-EOT.strip_heredoc
60
+
61
+ \e[1mPROFILING REPORT\e[22m
62
+ ----------------
63
+
64
+ \e[1mAPI RUNTIME\e[22m
65
+ Total Runtime : %s ms
66
+ Database Runtime : %s ms
67
+ View Runtime : %s ms
68
+
69
+ \e[1mSQL CALLS\e[22m
70
+ Total : %s
71
+ Total Unique : %s
72
+
73
+ \e[1mSLOWEST\e[22m
74
+ Total Runtime : %s ms
75
+ SQL : %s
76
+ Source : %s
77
+
78
+ \e[1mMOSTLY CALLED\e[22m
79
+ Total Calls : %s
80
+ Total Runtime : %s ms
81
+ SQL : %s
82
+ Sources : %s
83
+
84
+ EOT
85
+
42
86
  attr_accessor :data, :options
43
87
 
44
88
  def initialize(json_file, options)
45
89
  self.data = JSON.parse( File.read(json_file) )
46
- self.options = options
90
+ self.options = options
47
91
  end
48
92
 
49
93
  def print
50
94
  print_summary
51
95
 
52
- if self.options.details == 'full'
53
- print_instrumented_methods
54
- print_instrumented_sql_calls
96
+ if options.details == 'full'
97
+ if only_methods?
98
+ print_instrumented_methods
99
+ elsif only_sqls?
100
+ print_instrumented_sql_calls
101
+ else
102
+ print_instrumented_methods
103
+ print_instrumented_sql_calls
104
+ end
55
105
  end
56
106
  end
57
107
 
58
108
  private
59
109
 
110
+ def only_methods?
111
+ options.only_methods.present? && options.only_sqls.blank?
112
+ end
113
+
114
+ def only_sqls?
115
+ options.only_sqls.present? && options.only_methods.blank?
116
+ end
117
+
118
+ def rounding
119
+ options.rounding
120
+ end
121
+
60
122
  def print_summary
61
- summary = SUMMARY_TEMPLATE % summary_template_data
123
+ summary = if only_methods?
124
+ METHODS_DETAILS_TEMPLATE % details_template_data
125
+ elsif only_sqls?
126
+ SQLS_DETAILS_TEMPLATE % details_template_data
127
+ else
128
+ FULL_DETAILS_TEMPLATE % details_template_data
129
+ end
62
130
  puts summary
63
131
  end
64
132
 
65
133
  def print_instrumented_methods
66
134
  instrumented_methods = []
67
135
 
68
- self.data['instrumentation']['instrumented_methods'].each do |profiled_object_name, methods|
69
- instrumented_methods.concat methods.map { |method| method['method'] = [profiled_object_name, method['method']].join; method}
136
+ data['instrumentation']['instrumented_methods'].each do |profiled_object_name, methods|
137
+ _instrumented_methods = methods.map do |method|
138
+ method['method'] = [profiled_object_name, method['method']].join
139
+ method
140
+ end
141
+ instrumented_methods.concat(_instrumented_methods)
70
142
  end
71
143
 
144
+ instrumented_methods = runtime_above(instrumented_methods) if options.runtime_above.presence > 0
145
+ instrumented_methods = calls_above(instrumented_methods) if options.calls_above.presence > 0
72
146
  instrumented_methods = sort(instrumented_methods)
73
147
 
74
148
  table = Terminal::Table.new do |t|
75
- t.headings = ['Method', 'Total Runtime (ms)', 'Total Calls', 'Min', 'Max']
149
+ t.headings = ['Method', 'Total Runtime (ms)', 'Total Calls', 'Min (ms)', 'Max (ms)']
76
150
 
77
151
  instrumented_methods.each_with_index do |row, index|
78
152
  t.add_row [
79
153
  row['method'],
80
- row['total_runtime'],
154
+ row['total_runtime'].round(rounding),
81
155
  row['total_calls'],
82
- row['min'],
83
- row['max']
156
+ row['min'].round(rounding),
157
+ row['max'].round(rounding)
84
158
  ]
85
159
  t.add_separator if index < instrumented_methods.size - 1
86
160
  end
@@ -95,7 +169,11 @@ module RuntimeProfiler
95
169
  end
96
170
 
97
171
  def print_instrumented_sql_calls
98
- instrumented_sql_calls = sort(self.data['instrumentation']['instrumented_sql_calls'])
172
+ instrumented_sql_calls = data['instrumentation']['instrumented_sql_calls']
173
+
174
+ instrumented_sql_calls = runtime_above(instrumented_sql_calls) if options.runtime_above.presence > 0
175
+ instrumented_sql_calls = calls_above(instrumented_sql_calls) if options.calls_above.presence > 0
176
+ instrumented_sql_calls = sort(instrumented_sql_calls, false)
99
177
 
100
178
  table = Terminal::Table.new do |t|
101
179
  t.headings = ['Count', 'Total Runtime (ms)', 'Average Runtime (ms)', 'SQL Query', 'Source']
@@ -103,7 +181,8 @@ module RuntimeProfiler
103
181
  instrumented_sql_calls.each_with_index do |row, index|
104
182
  chopped_sql = wrap_text(row['sql'], sql_width)
105
183
  source_list = wrap_list(row['runtimes'].map { |runtime| runtime[1] }.uniq, sql_width - 15)
106
- average_runtime = row['average'].round(2)
184
+ average_runtime = row['average'].round(rounding)
185
+ total_runtime = row['total_runtime'].round(rounding)
107
186
  total_lines = if chopped_sql.length >= source_list.length
108
187
  chopped_sql.length
109
188
  else
@@ -113,14 +192,14 @@ module RuntimeProfiler
113
192
  (0...total_lines).each do |line|
114
193
  count = line == 0 ? row['total_calls'] : ''
115
194
  average = line == 0 ? average_runtime : ''
116
- total_runtime = line == 0 ? row['total_runtime'] : ''
195
+ total_runtime = line == 0 ? total_runtime : ''
117
196
  source = source_list.length > line ? source_list[line] : ''
118
197
  query = row['sql'].length > line ? chopped_sql[line] : ''
119
198
 
120
199
  t.add_row []
121
200
  t.add_row [count, total_runtime, average, query, source]
122
201
  end
123
-
202
+
124
203
  t.add_row []
125
204
  t.add_separator if index < instrumented_sql_calls.size - 1
126
205
  end
@@ -152,43 +231,72 @@ module RuntimeProfiler
152
231
  end
153
232
  end
154
233
 
155
- def sort(data)
156
- data.sort_by do |d|
157
- if self.options.sort_by == 'total_runtime'
158
- -d['total_runtime']
159
- else
160
- -d['total_calls']
234
+ def sort(data, methods=true)
235
+ if methods
236
+ data.sort_by do |d|
237
+ if options.sort_by == 'max_runtime'
238
+ -d['max']
239
+ elsif options.sort_by == 'total_runtime'
240
+ -d['total_runtime']
241
+ elsif options.sort_by == 'total_calls'
242
+ -d['total_calls']
243
+ end
161
244
  end
245
+ else
246
+ options.sort_by = 'total_runtime' if options.sort_by == 'max_runtime'
247
+ data.sort_by { |d| options.sort_by == 'total_runtime' ? -d['total_runtime'] : -d['total_calls'] }
162
248
  end
163
249
  end
164
250
 
165
- def summary_template_data
166
- summary = self.data['instrumentation']['summary']
251
+ def runtime_above(data)
252
+ data.select { |d| d['total_runtime'] > options.runtime_above }
253
+ end
167
254
 
168
- [
169
- summary['total_runtime'].round(2),
170
- summary['db_runtime'].round(2),
171
- summary['view_runtime'].round(2),
255
+ def calls_above(data)
256
+ data.select { |d| d['total_calls'] > options.calls_above }
257
+ end
258
+
259
+ def details_template_data
260
+ summary = data['instrumentation']['summary']
261
+
262
+ template_data = [
263
+ summary['total_runtime'] ? summary['total_runtime'].round(rounding) : 'n/a',
264
+ summary['db_runtime'] ? summary['db_runtime'].round(rounding) : 'n/a',
265
+ summary['view_runtime'] ? summary['view_runtime'].round(rounding) : 'n/a'
266
+ ]
172
267
 
268
+ methods_data = [
173
269
  summary['slowest_method']['method'],
174
- summary['slowest_method']['total_runtime'].round(2),
270
+ summary['slowest_method']['total_runtime'].round(rounding),
175
271
 
176
272
  summary['mostly_called_method']['method'],
177
273
  summary['mostly_called_method']['total_calls'],
178
- summary['mostly_called_method']['total_runtime'].round(2),
274
+ summary['mostly_called_method']['total_runtime'].round(rounding)
275
+ ]
179
276
 
277
+ sqls_data = [
180
278
  summary['total_sql_calls'],
181
279
  summary['total_unique_sql_calls'],
182
280
 
183
- summary['slowest_sql']['total_runtime'].round(2),
281
+ summary['slowest_sql']['total_runtime'].round(rounding),
184
282
  summary['slowest_sql']['sql'],
185
283
  summary['slowest_sql']['source'],
186
284
 
187
285
  summary['mostly_called_sql']['total_calls'],
188
- summary['mostly_called_sql']['total_runtime'].round(2),
286
+ summary['mostly_called_sql']['total_runtime'].round(rounding),
189
287
  summary['mostly_called_sql']['sql'],
190
288
  summary['mostly_called_sql']['runtimes'].map { |runtime| runtime[1] }.uniq
191
289
  ]
290
+
291
+ if only_methods?
292
+ template_data.concat(methods_data)
293
+ elsif only_sqls?
294
+ template_data.concat(sqls_data)
295
+ else
296
+ template_data
297
+ .concat(methods_data)
298
+ .concat(sqls_data)
299
+ end
192
300
  end
193
301
  end
194
302
  end
@@ -1,3 +1,3 @@
1
1
  module RuntimeProfiler
2
- VERSION = '0.1.4'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = ['runtime_profiler']
20
20
  spec.require_paths = ['lib']
21
21
 
22
- spec.add_development_dependency 'bundler', '~> 1.12'
22
+ spec.add_development_dependency 'bundler'
23
23
  spec.add_development_dependency 'rake', '>= 12.3.3'
24
24
  spec.add_development_dependency 'minitest', '~> 5.0'
25
25
  spec.add_development_dependency 'activesupport', '>= 3.0.0'
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: runtime_profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wilfrido T. Nuqui Jr.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-14 00:00:00.000000000 Z
11
+ date: 2020-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.12'
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.12'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -173,6 +173,7 @@ extensions: []
173
173
  extra_rdoc_files: []
174
174
  files:
175
175
  - ".gitignore"
176
+ - ".semaphore/semaphore.yml"
176
177
  - Gemfile
177
178
  - LICENSE.txt
178
179
  - README.md