rorvswild 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,5 @@
1
1
  module RorVsWild
2
2
  class RailsLoader
3
- @started = false
4
-
5
3
  def self.start_on_rails_initialization
6
4
  return if !defined?(Rails)
7
5
  Rails::Railtie.initializer "rorvswild.detect_config_file" do
@@ -10,13 +8,18 @@ module RorVsWild
10
8
  end
11
9
 
12
10
  def self.start
13
- return if @started
11
+ return if RorVsWild.agent
12
+
14
13
  if (path = Rails.root.join("config/rorvswild.yml")).exist?
15
14
  if config = RorVsWild::RailsLoader.load_config_file(path)[Rails.env]
16
15
  RorVsWild.start(config.symbolize_keys)
17
- @started = true
18
16
  end
19
17
  end
18
+
19
+ if !RorVsWild.agent && Rails.env.development?
20
+ require "rorvswild/local"
21
+ RorVsWild::Local.start
22
+ end
20
23
  end
21
24
 
22
25
  def self.load_config_file(path)
@@ -1,3 +1,3 @@
1
1
  module RorVsWild
2
- VERSION = "1.2.0".freeze
2
+ VERSION = "1.3.0".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rorvswild
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexis Bernard
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-23 00:00:00.000000000 Z
11
+ date: 2017-12-12 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Performances and quality insights for rails developers.
14
14
  email:
@@ -18,17 +18,24 @@ executables:
18
18
  extensions: []
19
19
  extra_rdoc_files: []
20
20
  files:
21
- - ".gitignore"
22
- - Gemfile
23
21
  - LICENSE.txt
24
22
  - README.md
25
- - Rakefile
26
23
  - bin/rorvswild-install
27
24
  - cacert.pem
28
25
  - lib/rorvswild.rb
29
26
  - lib/rorvswild/agent.rb
30
27
  - lib/rorvswild/client.rb
31
28
  - lib/rorvswild/installer.rb
29
+ - lib/rorvswild/local.rb
30
+ - lib/rorvswild/local/javascript/barber.js
31
+ - lib/rorvswild/local/javascript/local.js
32
+ - lib/rorvswild/local/javascript/mustache.js
33
+ - lib/rorvswild/local/javascript/prism.js
34
+ - lib/rorvswild/local/local.html
35
+ - lib/rorvswild/local/middleware.rb
36
+ - lib/rorvswild/local/queue.rb
37
+ - lib/rorvswild/local/stylesheet/local.css
38
+ - lib/rorvswild/local/stylesheet/prism.css
32
39
  - lib/rorvswild/location.rb
33
40
  - lib/rorvswild/plugin/action_controller.rb
34
41
  - lib/rorvswild/plugin/action_mailer.rb
@@ -47,25 +54,6 @@ files:
47
54
  - lib/rorvswild/rails_loader.rb
48
55
  - lib/rorvswild/section.rb
49
56
  - lib/rorvswild/version.rb
50
- - rorvswild.gemspec
51
- - test/helper.rb
52
- - test/measure_nested_sections_test.rb
53
- - test/plugin/action_controller_test.rb
54
- - test/plugin/action_mailer_test.rb
55
- - test/plugin/action_view_test.rb
56
- - test/plugin/active_job_test.rb
57
- - test/plugin/active_record_test.rb
58
- - test/plugin/delayed_job_test.rb
59
- - test/plugin/elasticsearch_test.rb
60
- - test/plugin/mongo_test.rb
61
- - test/plugin/net_http_test.rb
62
- - test/plugin/redis_test.rb
63
- - test/plugin/resque_test.rb
64
- - test/plugin/sidekiq_test.rb
65
- - test/queue_test.rb
66
- - test/rorvswild_test.rb
67
- - test/run.rb
68
- - test/section_test.rb
69
57
  homepage: https://www.rorvswild.com
70
58
  licenses:
71
59
  - MIT
@@ -90,23 +78,5 @@ rubygems_version: 2.4.8
90
78
  signing_key:
91
79
  specification_version: 4
92
80
  summary: Ruby on Rails app monitoring
93
- test_files:
94
- - test/helper.rb
95
- - test/measure_nested_sections_test.rb
96
- - test/plugin/action_controller_test.rb
97
- - test/plugin/action_mailer_test.rb
98
- - test/plugin/action_view_test.rb
99
- - test/plugin/active_job_test.rb
100
- - test/plugin/active_record_test.rb
101
- - test/plugin/delayed_job_test.rb
102
- - test/plugin/elasticsearch_test.rb
103
- - test/plugin/mongo_test.rb
104
- - test/plugin/net_http_test.rb
105
- - test/plugin/redis_test.rb
106
- - test/plugin/resque_test.rb
107
- - test/plugin/sidekiq_test.rb
108
- - test/queue_test.rb
109
- - test/rorvswild_test.rb
110
- - test/run.rb
111
- - test/section_test.rb
81
+ test_files: []
112
82
  has_rdoc:
data/.gitignore DELETED
@@ -1,22 +0,0 @@
1
- *.gem
2
- *.rbc
3
- .bundle
4
- .config
5
- .yardoc
6
- Gemfile.lock
7
- InstalledFiles
8
- _yardoc
9
- coverage
10
- doc/
11
- lib/bundler/man
12
- pkg
13
- rdoc
14
- spec/reports
15
- test/tmp
16
- test/version_tmp
17
- tmp
18
- *.bundle
19
- *.so
20
- *.o
21
- *.a
22
- mkmf.log
data/Gemfile DELETED
@@ -1,18 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in rorvswild.gemspec
4
- gemspec
5
-
6
- gem "mocha"
7
- gem "top_tests"
8
-
9
- gem "mongo"
10
- gem "redis"
11
- gem "elasticsearch"
12
-
13
- gem "actionpack"
14
- gem "activejob"
15
-
16
- gem "delayed_job"
17
- gem "sidekiq"
18
- gem "resque"
data/Rakefile DELETED
@@ -1,2 +0,0 @@
1
- require "bundler/gem_tasks"
2
-
data/rorvswild.gemspec DELETED
@@ -1,20 +0,0 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'rorvswild/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "rorvswild"
8
- spec.version = RorVsWild::VERSION
9
- spec.authors = ["Alexis Bernard"]
10
- spec.email = ["alexis@bernard.io"]
11
- spec.summary = "Ruby on Rails app monitoring"
12
- spec.description = "Performances and quality insights for rails developers."
13
- spec.homepage = "https://www.rorvswild.com"
14
- spec.license = "MIT"
15
-
16
- spec.files = `git ls-files -z`.split("\x0")
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
20
- end
data/test/helper.rb DELETED
@@ -1,20 +0,0 @@
1
- path = File.expand_path("#{File.dirname(__FILE__)}/../lib")
2
- $LOAD_PATH.unshift(path)
3
-
4
- require "rorvswild"
5
- require "minitest/autorun"
6
- require "mocha/mini_test"
7
- require "top_tests"
8
-
9
- module RorVsWildAgentHelper
10
- def agent
11
- @agent ||= initialize_agent(app_root: File.dirname(__FILE__))
12
- end
13
-
14
- def initialize_agent(options = {})
15
- agent ||= RorVsWild.start({logger: "/dev/null"}.merge(options))
16
- agent.stubs(:post_request)
17
- agent.stubs(:post_job)
18
- agent
19
- end
20
- end
@@ -1,37 +0,0 @@
1
- require File.expand_path("#{File.dirname(__FILE__)}/helper")
2
-
3
- class RorVsWild::MeasureNestedSectionsTest < Minitest::Test
4
- include RorVsWildAgentHelper
5
-
6
- def test_measure_section
7
- result = agent.measure_block("root") do
8
- agent.measure_block("parent") do
9
- sleep 0.01
10
- agent.measure_block("child") do
11
- sleep 0.02
12
- 42
13
- end
14
- end
15
- end
16
- assert_equal(42, result)
17
- sections = agent.data[:sections]
18
- parent, child = sections[1], sections[0]
19
- assert_equal("child", child.command)
20
- assert_equal("parent", parent.command)
21
- assert(child.self_runtime > 20)
22
- assert(parent.self_runtime > 10)
23
- assert(child.self_runtime > parent.self_runtime)
24
- assert_equal(child.total_runtime + parent.self_runtime, parent.total_runtime)
25
- end
26
-
27
- def test_measure_section_with_exception
28
- assert_raises(ZeroDivisionError) do
29
- agent.measure_block("root") do
30
- agent.measure_block("parent") do
31
- agent.measure_block("child") { 1 / 0 }
32
- end
33
- end
34
- end
35
- assert_equal(2, agent.data[:sections].size)
36
- end
37
- end
@@ -1,64 +0,0 @@
1
- require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
-
3
- require "action_controller"
4
-
5
- class RorVsWild::Plugin::ActionControllerTest < Minitest::Test
6
- include RorVsWildAgentHelper
7
-
8
- def test_callback
9
- agent.expects(:post_request)
10
- payload = {controller: "UsersController", action: "show"}
11
- ActiveSupport::Notifications.instrument("process_action.action_controller", payload) do
12
- sleep 0.01
13
- end
14
-
15
- data = agent.send(:data)
16
- assert_equal(0, data[:sections].size)
17
- assert_equal("UsersController#show", data[:name])
18
- assert(data[:runtime] > 10)
19
- end
20
-
21
- def test_callback_when_exception_is_raised
22
- agent.expects(:post_request)
23
- request = stub(filtered_parameters: {foo: "bar"}, filtered_env: {"HTTP_CONTENT_TYPE" => "HTML"})
24
- controller = stub(session: {id: "session"}, request: request)
25
- payload = {controller: "UsersController", action: "show"}
26
- assert_raises(ZeroDivisionError) do
27
- ActiveSupport::Notifications.instrument("process_action.action_controller", payload) do
28
- begin
29
- 1 / 0
30
- rescue => ex
31
- RorVsWild::Plugin::ActionController.after_exception(ex, controller)
32
- end
33
- end
34
- end
35
-
36
- data = agent.send(:data)
37
- assert_equal("UsersController#show", data[:name])
38
- assert_equal("ZeroDivisionError", data[:error][:exception])
39
- assert_equal({id: "session"}, data[:error][:session])
40
- assert_equal({foo: "bar"}, data[:error][:parameters])
41
- assert_equal({"Content-Type" => "HTML"}, data[:error][:environment_variables])
42
- end
43
-
44
- class SampleController
45
- def index
46
- end
47
- end
48
-
49
- def test_around_action
50
- controller = SampleController.new
51
- controller.stubs(action_name: "index", controller_name: "SampleController", method_for_action: "index")
52
- agent.measure_block("test") do
53
- RorVsWild::Plugin::ActionController.around_action(controller, controller.method(:index))
54
- end
55
- assert_equal(1, agent.data[:sections].size)
56
- assert_equal(__FILE__, agent.data[:sections][0].file)
57
- assert_equal("RorVsWild::Plugin::ActionControllerTest::SampleController#index", agent.data[:sections][0].command)
58
- end
59
-
60
- def test_format_header_name
61
- assert_equal("Content-Type", RorVsWild::Plugin::ActionController.format_header_name("HTTP_CONTENT_TYPE"))
62
- end
63
- end
64
-
@@ -1,27 +0,0 @@
1
- require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
-
3
- require "active_job"
4
-
5
- class RorVsWild::Plugin::ActiveMailerTest < Minitest::Test
6
- include RorVsWildAgentHelper
7
-
8
- def test_callback
9
- line = nil
10
- agent = initialize_agent(app_root: File.dirname(__FILE__))
11
- agent.measure_block("test") do
12
- ActiveSupport::Notifications.instrument("deliver.action_mailer", {mailer: "Mailer"}) do line = __LINE__
13
- sleep 0.01
14
- end
15
- end
16
-
17
- section = agent.data[:sections][0]
18
-
19
- assert_equal(1, agent.data[:sections].size)
20
- assert_equal("Mailer", section.command)
21
- assert_equal(line, section.line.to_i)
22
- assert_equal("mail", section.kind)
23
- assert(section.self_runtime > 10)
24
- assert_equal(1, section.calls)
25
- end
26
- end
27
-
@@ -1,40 +0,0 @@
1
- require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
-
3
- require "active_job"
4
-
5
- class RorVsWild::Plugin::ActionViewTest < Minitest::Test
6
- include RorVsWildAgentHelper
7
-
8
- def test_render_template_callback
9
- agent.measure_block("test") do
10
- ActiveSupport::Notifications.instrument("render_template.action_view", {identifier: "template.html.erb"}) do
11
- ActiveSupport::Notifications.instrument("render_partial.action_view", {identifier: "_partial.html.erb"}) do
12
- ActiveSupport::Notifications.instrument("render_partial.action_view", {identifier: "_sub_partial.html.erb"}) do
13
- sleep 0.03
14
- end
15
- sleep 0.02
16
- end
17
- sleep 0.01
18
- end
19
- end
20
-
21
- sections = agent.data[:sections]
22
- sub_partial, partial, template = sections[0], sections[1], sections[2]
23
- assert_equal(3, sections.size)
24
-
25
- assert_equal("view", sub_partial.kind)
26
- assert_equal("_sub_partial.html.erb", sub_partial.command)
27
-
28
- assert_equal("view", partial.kind)
29
- assert_equal("_partial.html.erb", partial.command)
30
-
31
- assert_equal("view", template.kind)
32
- assert_equal("template.html.erb", template.command)
33
-
34
- assert(sub_partial.self_runtime > partial.self_runtime)
35
- assert(partial.self_runtime > template.self_runtime)
36
- assert(partial.total_runtime < template.total_runtime)
37
- assert(sub_partial.total_runtime < partial.total_runtime)
38
- end
39
- end
40
-
@@ -1,32 +0,0 @@
1
- require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
-
3
- require "active_job"
4
-
5
- class RorVsWild::Plugin::ActiveJobTest < Minitest::Test
6
- include RorVsWildAgentHelper
7
-
8
- class SampleJob < ::ActiveJob::Base
9
- queue_as :default
10
-
11
- def perform(arg)
12
- raise "Exception" unless arg
13
- end
14
- end
15
-
16
- def test_callback
17
- ActiveJob::Base.logger = Logger.new("/dev/null")
18
- agent.expects(:post_job)
19
- SampleJob.perform_now(1)
20
- assert_equal("RorVsWild::Plugin::ActiveJobTest::SampleJob", agent.data[:name])
21
- end
22
-
23
- def test_callback_on_exception
24
- ActiveJob::Base.logger = Logger.new("/dev/null")
25
- agent.expects(:post_job)
26
- SampleJob.perform_now(false)
27
- rescue
28
- ensure
29
- assert_equal([false], agent.data[:error][:parameters])
30
- end
31
- end
32
-
@@ -1,39 +0,0 @@
1
- require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
-
3
- require "active_job"
4
-
5
- class RorVsWild::Plugin::ActiveRecordTest < Minitest::Test
6
- include RorVsWildAgentHelper
7
-
8
- def test_render_template_callback
9
- line1, line2 = nil
10
- agent = initialize_agent(app_root: File.dirname(__FILE__))
11
- agent.measure_block("test") do
12
- ActiveSupport::Notifications.instrument("sql.active_record", {sql: "SELECT COUNT(*) FROM users"}) do line1 = __LINE__
13
- sleep 0.01
14
- end
15
- 2.times do
16
- ActiveSupport::Notifications.instrument("sql.active_record", {sql: "SELECT * FROM users"}) do line2 = __LINE__
17
- sleep 0.02
18
- end
19
- end
20
- end
21
-
22
- sections = agent.data[:sections]
23
- sql1, sql2 = sections[0], sections[1]
24
- assert_equal(2, sections.size)
25
-
26
- assert_equal("sql", sql1.kind)
27
- assert_equal("SELECT COUNT(*) FROM users", sql1.command)
28
- assert_equal(line1, sql1.line.to_i)
29
- assert_equal(1, sql1.calls)
30
- assert(sql1.self_runtime > 10)
31
-
32
- assert_equal("sql", sql2.kind)
33
- assert_equal("SELECT * FROM users", sql2.command)
34
- assert_equal(line2, sql2.line.to_i)
35
- assert(sql2.self_runtime > 40)
36
- assert_equal(2, sql2.calls)
37
- end
38
- end
39
-
@@ -1,44 +0,0 @@
1
- require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
-
3
- require "delayed_job"
4
-
5
- class RorVsWild::Plugin::DelayedJobTest < Minitest::Test
6
- include RorVsWildAgentHelper
7
-
8
- class SampleJob
9
- def initialize(arg)
10
- @arg = arg
11
- end
12
-
13
- def perform
14
- raise "Exception" unless @arg
15
- end
16
- end
17
-
18
- Delayed::Worker.delay_jobs = false
19
-
20
- class SampleBackend
21
- include Delayed::Backend::Base
22
-
23
- attr_accessor :handler
24
-
25
- def initialize(options)
26
- @payload_object = options[:payload_object]
27
- end
28
- end
29
-
30
- def test_callback
31
- agent.expects(:post_job)
32
- SampleBackend.enqueue(SampleJob.new(true))
33
- assert_equal("RorVsWild::Plugin::DelayedJobTest::SampleJob", agent.data[:name])
34
- end
35
-
36
- def test_callback_on_exception
37
- agent.expects(:post_job)
38
- SampleBackend.enqueue(job = SampleJob.new(false))
39
- rescue
40
- ensure
41
- assert_equal(job, agent.data[:error][:parameters])
42
- end
43
- end
44
-
@@ -1,17 +0,0 @@
1
- require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
-
3
- require "elasticsearch"
4
-
5
- class RorVsWild::Plugin::ElasticsearchTest < Minitest::Test
6
- include RorVsWildAgentHelper
7
-
8
- def test_callback
9
- agent.measure_block("elastic") do
10
- ::Elasticsearch::Client.new.search(q: "test")
11
- end
12
- assert_equal(1, agent.data[:sections].size)
13
- assert_equal(1, agent.data[:sections][0].calls)
14
- assert_equal("elasticsearch", agent.data[:sections][0].kind)
15
- assert_equal('{"method":"GET","path":"_search","params":{"q":"test"},"body":null}', agent.data[:sections][0].command)
16
- end
17
- end
@@ -1,24 +0,0 @@
1
- require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
-
3
- require "mongo"
4
-
5
- class RorVsWild::Plugin::MongoTest < Minitest::Test
6
- include RorVsWildAgentHelper
7
-
8
- Mongo::Logger.logger.level = ::Logger::FATAL
9
-
10
- def test_callback
11
- mountains = [
12
- {name: "Mont Blanc", altitude: 4807},
13
- {name: "Mont Cervin", altitude: 4478},
14
- ]
15
- agent.measure_block("mongo") do
16
- agent = Mongo::Client.new('mongodb://127.0.0.1:27017/test')
17
- mountains.each { |m| agent[:mountains].insert_one(m) }
18
- end
19
- assert_equal(1, agent.data[:sections].size)
20
- assert_equal(2, agent.data[:sections][0].calls)
21
- assert_equal("mongo", agent.data[:sections][0].kind)
22
- assert_match('{"insert"=>"mountains", "documents"=>', agent.data[:sections][0].command)
23
- end
24
- end
@@ -1,30 +0,0 @@
1
- require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
-
3
- require "net/http"
4
-
5
- class RorVsWild::Plugin::NetHttpTest < Minitest::Test
6
- include RorVsWildAgentHelper
7
-
8
- def test_callback
9
- agent.measure_block("test") { Net::HTTP.get("ruby-lang.org", "/index.html") }
10
- assert_equal(1, agent.data[:sections].size)
11
- assert_equal(1, agent.data[:sections][0].calls)
12
- assert_equal("http", agent.data[:sections][0].kind)
13
- assert_match("GET http://ruby-lang.org/index.html", agent.data[:sections][0].command)
14
- end
15
-
16
- def test_callback_with_https
17
- agent.measure_block("test") { Net::HTTP.get(URI("https://www.ruby-lang.org/index.html")) }
18
- assert_match("GET https://www.ruby-lang.org/index.html", agent.data[:sections][0].command)
19
- assert_equal("http", agent.data[:sections][0].kind)
20
- end
21
-
22
- def test_nested_query_because_net_http_request_is_recursive_when_connection_is_not_started
23
- agent.measure_block("test") do
24
- uri = URI("http://www.ruby-lang.org/index.html")
25
- http = Net::HTTP.new(uri.host, uri.port)
26
- http.request(Net::HTTP::Get.new(uri.path))
27
- end
28
- assert_equal(1, agent.data[:sections][0].calls)
29
- end
30
- end
@@ -1,37 +0,0 @@
1
- require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
-
3
- require "redis"
4
-
5
- class RorVsWild::Plugin::RedisTest < Minitest::Test
6
- include RorVsWildAgentHelper
7
-
8
- def test_callback
9
- url = "redis://localhost:6379/1"
10
- agent.measure_code("::Redis.new(url: '#{url}').get('foo')")
11
- assert_equal(1, agent.data[:sections].size)
12
- assert_equal("redis", agent.data[:sections][0].kind)
13
- assert_equal("select 1\nget foo", agent.data[:sections][0].command)
14
- end
15
-
16
- def test_callback_when_pipelined
17
- agent.measure_block("pipeline") do
18
- (redis = ::Redis.new).pipelined do
19
- redis.get("foo")
20
- redis.set("foo", "bar")
21
- end
22
- end
23
- assert_equal(1, agent.data[:sections].size)
24
- assert_equal("redis", agent.data[:sections][0].kind)
25
- assert_equal("get foo\nset foo bar", agent.data[:sections][0].command)
26
- end
27
-
28
- def test_commands_to_string_hide_auth_password
29
- assert_equal("auth *****", RorVsWild::Plugin::Redis.commands_to_string([[:auth, "SECRET"]]))
30
- end
31
-
32
- def test_appendable_commands?
33
- assert(RorVsWild::Plugin::Redis.appendable_commands?([[:select, 1]]))
34
- assert(RorVsWild::Plugin::Redis.appendable_commands?([[:auth, "SECRET"]]))
35
- refute(RorVsWild::Plugin::Redis.appendable_commands?([[:get, "KEY"]]))
36
- end
37
- end
@@ -1,32 +0,0 @@
1
- require File.expand_path("#{File.dirname(__FILE__)}/../helper")
2
-
3
- require "resque"
4
-
5
- class RorVsWild::Plugin::ResqueTest < Minitest::Test
6
- include RorVsWildAgentHelper
7
-
8
- Resque.inline = true
9
-
10
- class SampleJob < Resque::Job
11
- @queue = :default
12
-
13
- def self.perform(arg)
14
- raise "Exception" unless arg
15
- end
16
- end
17
-
18
- def test_callback
19
- agent.expects(:post_job)
20
- Resque.enqueue(SampleJob, true)
21
- assert_equal("RorVsWild::Plugin::ResqueTest::SampleJob", agent.data[:name])
22
- end
23
-
24
- def test_callback_on_exception
25
- agent.expects(:post_job)
26
- Resque.enqueue(SampleJob, false)
27
- rescue
28
- ensure
29
- assert_equal([false], agent.data[:error][:parameters])
30
- end
31
- end
32
-