action_profiler 1.0.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.
data/LICENSE ADDED
@@ -0,0 +1,32 @@
1
+ Portions copyright 2004 David Heinemeier Hansson.
2
+
3
+ All original code copyright 2005 Eric Hodel, The Robot Co-op. All rights
4
+ reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions
8
+ are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright
11
+ notice, this list of conditions and the following disclaimer.
12
+ 2. Redistributions in binary form must reproduce the above copyright
13
+ notice, this list of conditions and the following disclaimer in the
14
+ documentation and/or other materials provided with the distribution.
15
+ 3. Neither the names of the authors nor the names of their contributors
16
+ may be used to endorse or promote products derived from this software
17
+ without specific prior written permission.
18
+ 4. Redistribution in Rails or any sub-projects of Rails is not allowed
19
+ until Rails runs without warnings with the ``-W2'' flag enabled.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
22
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
25
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
26
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
27
+ OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
30
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
31
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
+
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ Manifest.txt
3
+ README
4
+ Rakefile
5
+ bin/action_profiler
6
+ lib/action_profiler.rb
7
+ lib/action_profiler/path2class.rb
8
+ lib/action_profiler/prof_processor.rb
9
+ lib/action_profiler/profiled_processor.rb
10
+ lib/action_profiler/profiler_processor.rb
11
+ lib/action_profiler/test_processor.rb
12
+ lib/action_profiler/zenprofiler_processor.rb
data/README ADDED
@@ -0,0 +1,64 @@
1
+ = Action Profiler
2
+
3
+ Full Documentation:
4
+
5
+ http://rails-analyzer.rubyforge.org/action_profiler
6
+
7
+ Rubyforge Project:
8
+
9
+ http://rubyforge.org/projects/rails-analyzer
10
+
11
+ == About
12
+
13
+ Action Profiler allows you to profile a single Rails action to determine what
14
+ to optimize. You can use the Production Log Analyzer and action_grep to
15
+ determine which actions you should profile and what arguments to use.
16
+
17
+ Information on the Production Log Analyzer can be found at:
18
+
19
+ http://rails-analyzer.rubyforge.org/pl_analyze
20
+
21
+ === Profilers
22
+
23
+ action_profiler can use three profilers, Ruby's builtin profiler class,
24
+ Shugo Maeda's Prof or Ryan Davis' ZenProfile. For the last two profilers you
25
+ will need Ruby 1.8.3 or better.
26
+
27
+ === Running Action Profiler
28
+
29
+ Typically, action_profiler will be run from the root of your Rails
30
+ application:
31
+
32
+ $ action_profiler GamesController#index
33
+ Warmup...
34
+ Profiling...
35
+ [ profile output ]
36
+ $
37
+
38
+ If you need to run action_profiler from some other path, the -p command line
39
+ option can be used to specify the location of your Rails application.
40
+
41
+ action_profiler -p ~/Worx/X/CCR GamesController#index
42
+
43
+ Paramaters can be specified after the controller and action:
44
+
45
+ action_profiler GamesController#index ":id => 1"
46
+
47
+ If you need to make sure a page is working correctly you can specify -o and no
48
+ profiling will occur and the generated page will be printed out instead:
49
+
50
+ $ action_profiler -o GamesController#show ":id => 1"
51
+ <html>
52
+ [ lots of HTML output ]
53
+ $
54
+
55
+ == Gem Installation
56
+
57
+ gem install action_profiler
58
+
59
+ == Download
60
+
61
+ http://rubyforge.org/frs/?group_id=586
62
+
63
+ (Sorry, no manual installation script is available for the .tgz)
64
+
@@ -0,0 +1,61 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+ require 'rake/contrib/sshpublisher'
7
+
8
+ $VERBOSE = nil
9
+
10
+ spec = Gem::Specification.new do |s|
11
+ s.name = 'action_profiler'
12
+ s.version = '1.0.0'
13
+ s.summary = 'A profiler for Rails controllers'
14
+ s.author = 'Eric Hodel'
15
+ s.email = 'eric@robotcoop.com'
16
+
17
+ s.has_rdoc = true
18
+ s.files = File.read('Manifest.txt').split($/)
19
+ s.require_path = 'lib'
20
+ s.executables = ['action_profiler']
21
+ s.default_executable = 'action_profiler'
22
+ end
23
+
24
+ desc 'Run tests'
25
+ task :default => [ :test ]
26
+
27
+ Rake::TestTask.new('test') do |t|
28
+ t.libs << 'test'
29
+ t.pattern = 'test/test_*.rb'
30
+ t.verbose = true
31
+ end
32
+
33
+ desc 'Generate RDoc'
34
+ Rake::RDocTask.new :rdoc do |rd|
35
+ rd.rdoc_dir = 'doc'
36
+ rd.rdoc_files.add 'lib', 'README', 'LICENSE'
37
+ rd.main = 'README'
38
+ rd.options << '-d' if `which dot` =~ /\/dot/
39
+ end
40
+
41
+ desc 'Build Gem'
42
+ Rake::GemPackageTask.new spec do |pkg|
43
+ pkg.need_tar = true
44
+ end
45
+
46
+ desc 'Sends RDoc to RubyForge'
47
+ task :send_rdoc => [ :rerdoc ] do
48
+ publisher = Rake::SshDirPublisher.new('drbrain@rubyforge.org',
49
+ '/var/www/gforge-projects/rails-analyzer/action_profiler',
50
+ 'doc')
51
+ publisher.upload
52
+ end
53
+
54
+ desc 'Clean up'
55
+ task :clean => [ :clobber_rdoc, :clobber_package ]
56
+
57
+ desc 'Clean up'
58
+ task :clobber => [ :clean ]
59
+
60
+ # vim: syntax=Ruby
61
+
@@ -0,0 +1,6 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ require 'action_profiler'
4
+
5
+ ProfiledProcessor.process_args ARGV
6
+
@@ -0,0 +1,3 @@
1
+ # This project has lame names.
2
+ require 'action_profiler/profiled_processor'
3
+
@@ -0,0 +1,12 @@
1
+ class Object
2
+
3
+ ##
4
+ # Retrieves the class or module for the path +klassname+ (such as
5
+ # "Test::Unit::TestCase").
6
+
7
+ def path2class(klassname)
8
+ klassname.split('::').inject(Object) { |k,n| k.const_get n }
9
+ end
10
+
11
+ end
12
+
@@ -0,0 +1,39 @@
1
+ require 'prof'
2
+ require 'rubyprof_ext' # From Rails
3
+
4
+ Prof.clock_mode = Prof::GETTIMEOFDAY
5
+
6
+ ##
7
+ # A ProfiledProcessor that uses Shugo Maeda's Prof profiler.
8
+ #
9
+ # The Prof profiler requires Ruby 1.8.3 or better and can be found at
10
+ # http://shugo.net/archive/ruby-prof/
11
+ #
12
+ # The Prof profiler is configured to use gettimeofday(2). There is no way to
13
+ # change this setting.
14
+
15
+ class ProfProcessor < ProfiledProcessor
16
+
17
+ def initialize(*args) # :nodoc:
18
+ super
19
+ @profile_data = nil
20
+ end
21
+
22
+ def start_profile # :nodoc:
23
+ Prof.start
24
+ end
25
+
26
+ ##
27
+ # Prof returns profile data on Prof.stop, so save it for printing.
28
+
29
+ def stop_profile # :nodoc:
30
+ @profile_data = Prof.stop
31
+ end
32
+
33
+ def print_profile(io = STDERR) # :nodoc:
34
+ return unless @profile_data
35
+ Prof.print_profile @profile_data, io
36
+ end
37
+
38
+ end
39
+
@@ -0,0 +1,208 @@
1
+ require 'optparse'
2
+
3
+ require 'action_profiler/test_processor'
4
+
5
+ def debug(msg)
6
+ STDERR.puts msg if $AP_DEBUG
7
+ end
8
+
9
+ ##
10
+ # A Rails action processor that profiles an entire action.
11
+ #
12
+ # ProfiledProcessor is an abstract class. A subclasses must implement
13
+ # #start_profile, #stop_profile and #print_profile.
14
+
15
+ class ProfiledProcessor < TestProcessor
16
+
17
+ PROFILERS = ['ZenProfiler', 'Prof', 'Profiler']
18
+
19
+ ##
20
+ # Processes +args+ then runs a profile based on the arguments given.
21
+
22
+ def self.process_args(args = ARGV)
23
+ app_path = Dir.pwd
24
+ method = 'GET'
25
+ only_html = false
26
+ processor_klass = nil
27
+ times = 1
28
+
29
+ opts = OptionParser.new do |opts|
30
+ opts.banner = "Usage: #{File.basename $0} [options] method [params [session [flash]]]"
31
+
32
+ opts.separator ''
33
+ opts.separator 'method: controller and action to run "GamesController#index"'
34
+ opts.separator 'params, session, flash: Hash-style arguments ":id => 5"'
35
+ opts.separator ''
36
+
37
+ opts.on("-m", "--method=HTTP_METHOD",
38
+ "HTTP request method for this action",
39
+ "Default: #{method}") do |val|
40
+ method = val
41
+ end
42
+
43
+ opts.on("-o", "--[no-]only-html",
44
+ "Only output rendered page",
45
+ "Default: #{only_html}") do |val|
46
+ only_html = val
47
+ end
48
+
49
+ opts.on("-p", "--app-path=PATH",
50
+ "Path to Rails application root",
51
+ "Default: current directory") do |val|
52
+ unless File.directory? val then
53
+ raise OptionParser::InvalidArgument, "bad path: #{val}"
54
+ end
55
+
56
+ app_path = val
57
+ end
58
+
59
+ opts.on("-P", "--profiler=PROFILER",
60
+ "Profiler to use",
61
+ "Default: ZenProfiler, Prof then Profiler") do |val|
62
+ begin
63
+ processor_klass = load_processor val
64
+ rescue LoadError
65
+ raise OptionParser::InvalidArgument, "can't load #{val}_processor"
66
+ end
67
+ end
68
+
69
+ opts.on("-t", "--times=TIMES", Integer,
70
+ "Times to run the action under the profiler",
71
+ "Default: #{times}") do |val|
72
+ times = val
73
+ end
74
+
75
+ opts.separator ''
76
+ opts.on("-h", "--help", "Display this help") { STDERR.puts opts; exit 1 }
77
+ opts.on("-d", "--debug", "Enable debugging output") do |val|
78
+ $AP_DEBUG = val
79
+ end
80
+ opts.separator ''
81
+
82
+ opts.parse! args
83
+ end
84
+
85
+ processor_klass = load_default_processor if processor_klass.nil?
86
+
87
+ begin
88
+ Dir.chdir app_path
89
+ require 'config/environment'
90
+ require 'application' # HACK Rails can't find this by itself
91
+ rescue LoadError => e
92
+ debug "Application load error \"#{e.message}\""
93
+ raise OptionParser::InvalidArgument, "could not load application, check your path"
94
+ end
95
+
96
+ raise OptionParser::ParseError, "action not specified" if args.empty?
97
+ action = args.shift
98
+
99
+ raise OptionParser::ParseError, "too many arguments" if args.length > 3
100
+
101
+ begin
102
+ params, session, flash = args.map { |arg| eval "{#{arg}}" }
103
+ rescue Exception
104
+ raise OptionParser::ParseError, "invalid param/session/flash argument"
105
+ end
106
+
107
+ params ||= {}
108
+ session ||= {}
109
+ flash ||= {}
110
+
111
+ debug "Using #{processor_klass.inspect} processor"
112
+
113
+ pp = processor_klass.new action, method, params, session, flash, only_html
114
+ pp.profile times
115
+
116
+ rescue ArgumentError, OptionParser::ParseError => e
117
+ STDERR.puts e.message
118
+ debug "\t#{$!.backtrace.join("\n\t")}"
119
+ STDERR.puts
120
+ STDERR.puts opts.to_s
121
+ exit 1
122
+ end
123
+
124
+ ##
125
+ # Attempts to load the default profilers in order. Returns the first
126
+ # successfully found profiler class.
127
+
128
+ def self.load_default_processor
129
+ PROFILERS.each do |profiler|
130
+ begin
131
+ return load_processor(profiler)
132
+ rescue LoadError => e
133
+ end
134
+ end
135
+ raise "couldn't load any profilers, how strange, sorry about that"
136
+ end
137
+
138
+ ##
139
+ # Attempts to load a processor starting with +name+. Returns the loaded
140
+ # class if successful.
141
+
142
+ def self.load_processor(name)
143
+ debug "Loading #{name}Processor"
144
+ # HACK I have no fucking clue how or why Rails' require mucks shit up,
145
+ # nor can I reproduce it in a small testcase.
146
+ require__ "action_profiler/#{name.downcase}_processor"
147
+ return Object.path2class("#{name}Processor")
148
+ rescue LoadError => e
149
+ debug "Failed to load #{name}Processor: #{e.message}"
150
+ raise
151
+ end
152
+
153
+ ##
154
+ # If +only_html+ is true then only the rendered page will be displayed and
155
+ # no profiling will be performed. See TestProcessor#new for the rest.
156
+
157
+ def initialize(action, method, params, session, flash, only_html)
158
+ super action, method, params, session, flash
159
+ @only_html = only_html
160
+ end
161
+
162
+ ##
163
+ # Profiles the action, running it under the profiler +times+ times after
164
+ # three warmup actions.
165
+
166
+ def profile(times = 1)
167
+ if @only_html then
168
+ process
169
+ puts @response.body
170
+ return
171
+ end
172
+
173
+ STDERR.puts "Warmup..."
174
+ 3.times { process }
175
+
176
+ begin
177
+ STDERR.puts "Profiling..."
178
+ start_profile
179
+ times.times { process }
180
+ stop_profile
181
+ ensure
182
+ print_profile
183
+ end
184
+ end
185
+
186
+ ##
187
+ # Implemented by a subclass to start the profiler it uses.
188
+
189
+ def start_profile
190
+ raise NotImplementedError
191
+ end
192
+
193
+ ##
194
+ # Implemented by a subclass to stop the profiler it uses.
195
+
196
+ def stop_profile
197
+ raise NotImplementedError
198
+ end
199
+
200
+ ##
201
+ # Implemented by a subclass to print out the profile data to +io+.
202
+
203
+ def print_profile(io)
204
+ raise NotImplementedError
205
+ end
206
+
207
+ end
208
+
@@ -0,0 +1,24 @@
1
+ require 'profiler'
2
+
3
+ ##
4
+ # A ProfiledProcessor that uses Ruby's built-in Profiler__ class.
5
+ #
6
+ # ProfilerProcessor is very slow. You really want to upgrade to Ruby 1.8.3 or
7
+ # better and use ZenProfilerProcessor or ProfProcessor.
8
+
9
+ class ProfilerProcessor < ProfiledProcessor
10
+
11
+ def start_profile # :nodoc:
12
+ Profiler__.start_profile
13
+ end
14
+
15
+ def stop_profile # :nodoc:
16
+ Profiler__.stop_profile
17
+ end
18
+
19
+ def print_profile(io = STDERR) # :nodoc:
20
+ Profiler__.print_profile io
21
+ end
22
+
23
+ end
24
+
@@ -0,0 +1,130 @@
1
+ require 'rubygems'
2
+ require 'action_controller'
3
+
4
+ require 'action_profiler/path2class'
5
+
6
+ # :stopdoc:
7
+
8
+ # This exists because Rails coupled test processing to unit testing.
9
+
10
+ # Don't load assertions or deprecated assertions by faking entries in $".
11
+ gs = Gem::GemPathSearcher.new
12
+ path = gs.find('action_controller/test_process').full_gem_path
13
+ $" << File.join(path, 'lib', 'action_controller', 'assertions.rb')
14
+ $" << File.join(path, 'lib', 'action_controller', 'deprecated_assertions.rb')
15
+
16
+ # This lameness exists because Rails injects into Test::Unit::TestCase when
17
+ # it should use subclasses.
18
+
19
+ module Test; end
20
+ module Test::Unit; end
21
+ class Test::Unit::TestCase; end
22
+
23
+ # :startdoc:
24
+
25
+ require 'action_controller/test_process'
26
+
27
+ ##
28
+ # TestProcessor is a class that exercises a Rails controller action.
29
+ #
30
+ # TestProcessor is heavily based on ActionPack's
31
+ # lib/action_controller/test_process.rb
32
+ #
33
+ # The original can be found at: http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_controller/test_process.rb
34
+ #
35
+ # The methods #process and #build_request_uri are copyright (c) 2004 David
36
+ # Heinemeier Hansson and are used under the MIT License. All original code is
37
+ # subject to the LICENSE file included with Action Profiler.
38
+ #--
39
+ # Per the MIT license:
40
+ #
41
+ # Permission is hereby granted, free of charge, to any person obtaining
42
+ # a copy of this software and associated documentation files (the
43
+ # "Software"), to deal in the Software without restriction, including
44
+ # without limitation the rights to use, copy, modify, merge, publish,
45
+ # distribute, sublicense, and/or sell copies of the Software, and to
46
+ # permit persons to whom the Software is furnished to do so, subject to
47
+ # the following conditions:
48
+ #
49
+ # The above copyright notice and this permission notice shall be
50
+ # included in all copies or substantial portions of the Software.
51
+ #
52
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
53
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
54
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
55
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
56
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
57
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
58
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
59
+
60
+ class TestProcessor
61
+
62
+ ##
63
+ # Creates a new TestProcessor that will profile +action+ with +method+.
64
+ # +params+, +session+ and +flash+ are hashes for use in processing the
65
+ # request.
66
+ #
67
+ # +action+ is a String with the format "GamesController#index".
68
+ #
69
+ # +method+ is one of the HTTP request methods, get, post, etc.
70
+
71
+ def initialize(action, method, params = nil, session = nil, flash = nil)
72
+ unless action =~ /^([:\w]+Controller)#(\w+)$/ then
73
+ raise ArgumentError, "invalid action name"
74
+ end
75
+
76
+ @controller_name = $1
77
+ @action_name = $2
78
+ @method = method.downcase
79
+
80
+ @params = params
81
+ @session = session
82
+ @flash = flash
83
+
84
+ begin
85
+ controller_klass = Object.path2class @controller_name
86
+ rescue NameError
87
+ raise ArgumentError, "can't find controller #{@controller_name}"
88
+ end
89
+
90
+ @controller = controller_klass.new
91
+ @request = ActionController::TestRequest.new
92
+ @response = ActionController::TestResponse.new
93
+ ActionMailer::Base.delivery_method = :test
94
+ ActionMailer::Base.deliveries = []
95
+ end
96
+
97
+ ##
98
+ # Runs this action.
99
+
100
+ def process
101
+ @request.recycle!
102
+
103
+ @html_document = nil # Why? Who knows!
104
+ @request.env['REQUEST_METHOD'] = @method
105
+ @request.action = @action_name
106
+
107
+ @request.assign_parameters(@controller.class.controller_path, @action_name,
108
+ @params)
109
+
110
+ @request.session = ActionController::TestSession.new @session
111
+ @request.session['flash'] = ActionController::Flash::FlashHash.new
112
+ @request.session['flash'].update @flash
113
+
114
+ build_request_uri
115
+ @controller.process @request, @response
116
+ end
117
+
118
+ private
119
+
120
+ def build_request_uri
121
+ return if @request.env['REQUEST_URI']
122
+ options = @controller.send :rewrite_options, @params
123
+ options.update :only_path => true, :action => @action_name
124
+
125
+ url = ActionController::UrlRewriter.new @request, @params
126
+ @request.set_REQUEST_URI url.rewrite(options)
127
+ end
128
+
129
+ end
130
+
@@ -0,0 +1,24 @@
1
+ require 'zenprofile'
2
+
3
+ ##
4
+ # A ProfiledProcessor that uses Ryan Davis' ZenProfiler.
5
+ #
6
+ # The ZenProfiler profiler requires Ruby 1.8.3 or better and RubyInline.
7
+ # ZenProfiler can be found at http://rubyforge.org/frs/?group_id=712
8
+
9
+ class ZenProfilerProcessor < ProfiledProcessor
10
+
11
+ def start_profile # :nodoc:
12
+ ZenProfiler.start
13
+ end
14
+
15
+ def stop_profile # :nodoc:
16
+ ZenProfiler.stop
17
+ end
18
+
19
+ def print_profile(io = STDERR) # :nodoc:
20
+ ZenProfiler.print_profile io
21
+ end
22
+
23
+ end
24
+
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: action_profiler
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.0.0
7
+ date: 2005-11-15 00:00:00 -08:00
8
+ summary: A profiler for Rails controllers
9
+ require_paths:
10
+ - lib
11
+ email: eric@robotcoop.com
12
+ homepage:
13
+ rubyforge_project:
14
+ description:
15
+ autorequire:
16
+ default_executable: action_profiler
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ signing_key:
28
+ cert_chain:
29
+ authors:
30
+ - Eric Hodel
31
+ files:
32
+ - LICENSE
33
+ - Manifest.txt
34
+ - README
35
+ - Rakefile
36
+ - bin/action_profiler
37
+ - lib/action_profiler.rb
38
+ - lib/action_profiler/path2class.rb
39
+ - lib/action_profiler/prof_processor.rb
40
+ - lib/action_profiler/profiled_processor.rb
41
+ - lib/action_profiler/profiler_processor.rb
42
+ - lib/action_profiler/test_processor.rb
43
+ - lib/action_profiler/zenprofiler_processor.rb
44
+ test_files: []
45
+ rdoc_options: []
46
+ extra_rdoc_files: []
47
+ executables:
48
+ - action_profiler
49
+ extensions: []
50
+ requirements: []
51
+ dependencies: []