evenitron 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,23 +1,28 @@
1
- # Setting up with delayed_job
1
+ # Evenitron
2
2
 
3
- There are two options for setting up Evenitron to monitor your delayed_job tasks.
3
+ Evenitron is a service to help you to properly understand the performance of your delayed_job and resque jobs.
4
+
5
+ ## Getting started
6
+
7
+ In order to use the Evenitron service you will need a `system key` which is assigned to you when you sign up for an account. This key identifies you system to the Evenitron aggregators.
8
+
9
+ If you don't have an account you can request an invite to our private beta program here : http://app.evenitron.com/invite_requests/new
4
10
 
5
11
  Firstly, add the gem to your Gemfile:
6
12
 
7
13
  ```ruby
8
- gem 'evenitron-ruby', :git => 'git@github.com:evenitron/evenitron-ruby.git'
14
+ gem 'evenitron'
9
15
  ```
10
16
 
11
- *We'll use the git references if possible until things are public.*
17
+ On start you need to configure the gem with your system key. In Rails this would go in `config\initializers\evenitron.rb`.
12
18
 
13
- Then you need to configure it within your `boot.rb` for Rails (I have no idea if this is the right file but it works). You can either do this explicitly:
19
+ ### delayed_job setup
14
20
 
15
21
  ```ruby
16
22
  require 'evenitron/delayed_job'
17
23
 
18
24
  Evenitron::DelayedJob.configure do
19
- system_key 'benchmarking'
20
- # collector_url 'http://localhost:5000/'
25
+ system_key 'your_key'
21
26
  end
22
27
  ```
23
28
 
@@ -29,28 +34,13 @@ require 'evenitron/delayed_job'
29
34
  Evenitron::DelayedJob.configure
30
35
  ```
31
36
 
32
- You must specify your system key either through configuration or the `EVENITRON_SYSTEM_KEY` environment variable but you will normally rely on the default collector URL of `https://collector.evenitron.com/`.
33
-
34
- # Setting up with resque
35
-
36
- There are two options for setting up Evenitron to monitor your delayed_job tasks.
37
-
38
- Firstly, add the gem to your Gemfile:
39
-
40
- ```ruby
41
- gem 'evenitron-ruby', :git => 'git@github.com:evenitron/evenitron-ruby.git'
42
- ```
43
-
44
- *We'll use the git references if possible until things are public.*
45
-
46
- Then you need to configure it within your `boot.rb` for Rails (I have no idea if this is the right file but it works). You can either do this explicitly:
37
+ ### resque setup
47
38
 
48
39
  ```ruby
49
40
  require 'evenitron/resque'
50
41
 
51
42
  Evenitron::Resque.configure do
52
- system_key 'benchmarking'
53
- # collector_url 'http://localhost:5000/'
43
+ system_key 'your_key'
54
44
  end
55
45
  ```
56
46
 
@@ -62,4 +52,6 @@ require 'evenitron/resque'
62
52
  Evenitron::Resque.configure
63
53
  ```
64
54
 
55
+ ## Notes
56
+
65
57
  You must specify your system key either through configuration or the `EVENITRON_SYSTEM_KEY` environment variable but you will normally rely on the default collector URL of `https://collector.evenitron.com/`.
@@ -5,8 +5,117 @@ require 'securerandom'
5
5
  require 'logger'
6
6
  require 'json'
7
7
 
8
- module Evenitron ; end
8
+ module Evenitron
9
+ require 'evenitron/logger'
10
+ require 'evenitron/collector_client'
11
+ require 'evenitron/collector'
12
+
13
+ # Public: convenience method for configuring the gem
14
+ #
15
+ # Examples
16
+ #
17
+ # Evenitron.configure do |config|
18
+ # config.system_key = 'your system key'
19
+ # end
20
+ #
21
+ # Raises StandardError if no system_key passed or found in env variable
22
+ def self.configure
23
+ yield self if block_given?
24
+
25
+ logger.error "Evenitron system_key required" && return unless self.system_key
26
+
27
+ initialize_delayed_job if Evenitron.delayed_job_present?
28
+ initialize_resque if Evenitron.resque_present?
29
+
30
+ logger.info "Evenitron configured with #{self.system_key} #{self.collector_url}"
31
+ end
32
+
33
+ # Public: configured logger
34
+ #
35
+ def self.logger
36
+ @logger ||= default_logger
37
+ end
38
+
39
+ # Public: configured connection url for the gem to use
40
+ #
41
+ def self.collector_url
42
+ @collector_url ||= (ENV['EVENITRON_COLLECTOR_URL'] || "http://collector.evenitron.com")
43
+ end
44
+
45
+ # Public: configured system_key for the gem to use
46
+ #
47
+ def self.system_key
48
+ @system_key ||= ENV['EVENITRON_SYSTEM_KEY']
49
+ end
50
+
51
+ class << self
52
+ # Public: Writers for the config values
53
+ #
54
+ attr_writer :logger, :collector_url, :system_key
55
+ end
56
+
57
+ # Public: util method for encoding values for network transport.
58
+ #
59
+ # values - Enumerable of Strings
60
+ #
61
+ # Returns clean, Base64 encoded String
62
+ def self.encode(*values)
63
+ value = values.join ':'
64
+ cleanse_base64 Base64.encode64(value)
65
+ end
66
+
67
+ # Public: generates a unique id fo a transaction
68
+ #
69
+ # length - length of the key (default: 64)
70
+ #
71
+ # Returns String id
72
+ def self.generate_id(length = 64)
73
+ cleanse_base64 SecureRandom.base64(length)
74
+ end
75
+
76
+ private
77
+
78
+ # Private: constructs a default STDOUT logger set to INFO
79
+ #
80
+ def self.default_logger
81
+ logger = ::Logger.new(STDOUT)
82
+ logger.level = ::Logger::INFO
83
+ logger
84
+ end
85
+
86
+ # Private: strips space and = from Base64 strings
87
+ #
88
+ # value - String to cleanse
89
+ #
90
+ # Returns cleansed String
91
+ def self.cleanse_base64(value)
92
+ value.gsub /[\s=]/, ''
93
+ end
94
+
95
+
96
+ # Public: delayed_job present?
97
+ #
98
+ def self.delayed_job_present?
99
+ Module.const_defined? 'Delayed'
100
+ end
101
+
102
+ # Public: resque present?
103
+ #
104
+ def self.resque_present?
105
+ Module.const_defined? 'Resque'
106
+ end
107
+
108
+ def self.initialize_delayed_job
109
+ require 'evenitron/delayed_job'
110
+ Evenitron::DelayedJob.register_lifecycle_hooks
111
+ logger.info "Configured Evenitron delayed_job hooks"
112
+ end
113
+
114
+ def self.initialize_resque
115
+ require 'evenitron/resque'
116
+ logger.info "Configured Evenitron resque hooks"
117
+ end
118
+
119
+ end
120
+
9
121
 
10
- require_relative 'evenitron/evenitron'
11
- require_relative 'evenitron/logger'
12
- require_relative 'evenitron/collector_client'
@@ -0,0 +1,14 @@
1
+ # Public: common methods for collectors
2
+ module Evenitron
3
+
4
+ module Collector
5
+
6
+ # Public: lazy load client with the configured values
7
+ # Expects the host method to expose a component method
8
+ def client
9
+ @_client ||= CollectorClient.new Evenitron.system_key, Evenitron.collector_url, component
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -1,14 +1,21 @@
1
+ # Public: Handles communication with the Evenitron collector
2
+ #
1
3
  module Evenitron
2
4
 
3
5
  class CollectorClient
4
6
 
5
7
  include Evenitron::Logger
6
8
 
7
- DEFAULT_URL = "https://collector.evenitron.com/"
8
-
9
- def initialize(system_key, url = DEFAULT_URL, component = nil)
10
- @system_key = system_key
11
- @url = url
9
+ # Public: constructor
10
+ #
11
+ # system_key - String key identifying the system
12
+ # url - String url of the collector to push messages to
13
+ # component - optional String identifying the component generating the message,
14
+ # Used to construct UserAgent for requests
15
+ #
16
+ # Returns a new instance of CollectorClient
17
+ def initialize(system_key, url, component = nil)
18
+ @system_key, @url = system_key, url
12
19
  @user_agent = 'evenitron-ruby 0.1'
13
20
  @user_agent = "#{@user_agent} (#{component})" if component
14
21
  @simulate_requests = ENV['EVENITRON_SIMULATE_REQUESTS'] == 'true'
@@ -37,6 +44,11 @@ module Evenitron
37
44
  send message(stage, args)
38
45
  end
39
46
 
47
+ # Private: sends the messages to the collector
48
+ #
49
+ # msg - Hash comprising all args to send
50
+ #
51
+ # Returns nothing
40
52
  def send(msg)
41
53
  json = msg.to_json
42
54
 
@@ -59,6 +71,13 @@ module Evenitron
59
71
  nil
60
72
  end
61
73
 
74
+ # Private: construct a hahs comprising the message elements by combining
75
+ # the optional args, stage, version and timestamp
76
+ #
77
+ # stage - Symbol or String indicating the stage of the transaction
78
+ # args - Hash of additional arguments to send
79
+ #
80
+ # Returns Hash
62
81
  def message(stage, args)
63
82
  msg = args.dup
64
83
  msg[:stage] = stage
@@ -1,112 +1,53 @@
1
- require_relative '../evenitron'
2
1
  require 'delayed_job'
2
+ require_relative 'delayed_job/collector'
3
3
 
4
4
  module Evenitron
5
5
 
6
6
  module DelayedJob
7
7
 
8
- def self.configure(&block)
9
- dj = Evenitron::DelayedJobCollector.new
10
- dj.configure &block unless block.nil?
11
- dj.start!
12
- nil
8
+ # Public: lazy load DelayedJob specific collector
9
+ #
10
+ def self.collector
11
+ @_collector ||= Evenitron::DelayedJob::Collector.new
13
12
  end
14
13
 
15
- end
16
-
17
- class DelayedJobCollector
18
-
19
- include Evenitron::Logger
20
-
21
- def initialize
22
- @collector_url = ENV['EVENITRON_COLLECTOR_URL']
23
- @system_key = ENV['EVENITRON_SYSTEM_KEY']
24
- end
25
-
26
- def configure(&block)
27
- instance_eval &block
28
- end
29
-
30
- def system_key(key)
31
- @system_key = key
32
- end
33
-
34
- def api_key(key)
35
- @api_key = key
36
- end
37
-
38
- def collector_url(url)
39
- @collector_url = url
40
- end
41
-
42
- def start!
43
- log.info "Registering delayed_job evenitron collector with system key #{@system_key} to send messages to #{@collector_url}"
44
- @client = Evenitron::CollectorClient.new @system_key, @collector_url, 'dj'
45
- register_lifecycle_hooks
46
- end
47
-
48
- private
49
-
50
- def lifecycle
14
+ def self.lifecycle
51
15
  Delayed::Worker.lifecycle
52
16
  end
53
17
 
54
- def register_lifecycle_hooks
55
- log.info "Registering delayed_job job queued callback"
18
+ # Public: wire up DelayedJob with hooks for generating collector messages
19
+ #
20
+ def self.register_lifecycle_hooks
21
+ Evenitron.logger.info "Registering delayed_job job queued callback"
56
22
  lifecycle.around :enqueue do |job, &block|
57
23
  result = block.call(job)
58
- job_queued job
24
+ Evenitron::DelayedJob.collector.job_queued job
59
25
  result
60
26
  end
61
27
 
62
- log.info "Registering delayed_job job failed callback"
28
+ Evenitron.logger.info "Registering delayed_job job failed callback"
63
29
  lifecycle.before :failure do |worker, job|
64
- job_failed job
30
+ Evenitron::DelayedJob.collector.job_failed job
65
31
  end
66
32
 
67
- log.info "Registering delayed_job job requeued callback"
33
+ Evenitron.logger.info "Registering delayed_job job requeued callback"
68
34
  lifecycle.after :error do |worker, job|
69
- job_queued job unless job.failed? or job.frozen?
35
+ Evenitron::DelayedJob.collector.job_queued job unless job.failed? or job.frozen?
70
36
  end
71
37
 
72
- log.info "Registering delayed_job job started callback"
73
- log.info "Registering delayed_job job complete callback"
38
+ Evenitron.logger.info "Registering delayed_job job started callback"
39
+ Evenitron.logger.info "Registering delayed_job job complete callback"
74
40
  lifecycle.around :perform do |worker, job, &block|
75
- job_started job, worker
41
+ Evenitron::DelayedJob.collector.job_started job, worker
76
42
  result = block.call(worker, job)
77
- job_complete job if result
43
+ Evenitron::DelayedJob.collector.job_complete job if result
78
44
  result
79
45
  end
80
- end
81
46
 
82
- def job_queued(job)
83
- id = get_id(job)
84
- log.info "Sending job queued message for job #{job.id} (evenitron id #{id})"
85
- @client.job_queued :id => id, :queue => job.class.table_name, :task => job.name
86
47
  end
87
-
88
- def job_started(job, worker)
89
- id = get_id(job)
90
- log.info "Sending job started message for job #{job.id} (evenitron id #{id})"
91
- @client.job_started :id => id, :worker => worker.name, :task => job.name
92
- end
93
-
94
- def job_failed(job)
95
- id = get_id(job)
96
- log.info "Sending job failed message for job #{job.id} (evenitron id #{id})"
97
- @client.job_failed :id => id, :task => job.name
98
- end
99
-
100
- def job_complete(job)
101
- id = get_id(job)
102
- log.info "Sending job complete message for job #{job.id} (evenitron id #{id})"
103
- @client.job_complete :id => id, :task => job.name
104
- end
105
-
106
- def get_id(job)
107
- Evenitron.encode job.class.table_name, job.id
108
- end
109
-
48
+
49
+ register_lifecycle_hooks if Evenitron.delayed_job_enabled?
50
+
110
51
  end
111
52
 
112
53
  end
@@ -0,0 +1,37 @@
1
+ module Evenitron
2
+
3
+ module DelayedJob
4
+
5
+ class Collector
6
+
7
+ include Evenitron::Collector
8
+
9
+ def component
10
+ "dj"
11
+ end
12
+
13
+ def job_queued(job)
14
+ client.job_queued :id => get_id(job), :queue => job.class.table_name, :task => job.name
15
+ end
16
+
17
+ def job_started(job, worker)
18
+ client.job_started :id => get_id(job), :worker => worker.name, :task => job.name
19
+ end
20
+
21
+ def job_failed(job)
22
+ client.job_failed :id => get_id(job), :task => job.name
23
+ end
24
+
25
+ def job_complete(job)
26
+ client.job_complete :id => get_id(job), :task => job.name
27
+ end
28
+
29
+ def get_id(job)
30
+ Evenitron.encode job.class.table_name, job.id
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -1,149 +1,19 @@
1
- require_relative '../evenitron'
1
+ # Public: coordinates loading of hooks for resque monitoring
2
+ #
2
3
  require 'resque'
4
+ require_relative 'resque/resque'
5
+ require_relative 'resque/collector'
3
6
 
4
7
  module Evenitron
5
8
 
6
- class NullResqueCollector
7
-
8
- include Evenitron::Logger
9
-
10
- def job_queued(queue, item)
11
- log.warn "EVENITRON NOT CONFIGURED : Not sending job queued message for job #{item['evenitron_id']}"
12
- end
13
-
14
- def job_started(job)
15
- log.info "EVENITRON NOT CONFIGURED : Not sending job started message for job #{job.evenitron_id}"
16
- end
17
-
18
- def job_failed(job)
19
- log.info "EVENITRON NOT CONFIGURED : Not sending job failed message for job #{job.evenitron_id}"
20
- end
21
-
22
- def job_complete(job)
23
- log.info "EVENITRON NOT CONFIGURED : Not sending job complete message for job #{job.evenitron_id}"
24
- end
25
-
26
- end
27
-
28
9
  module Resque
29
10
 
30
- @@collector = Evenitron::NullResqueCollector.new
31
-
32
- def self.configure(&block)
33
- rc = Evenitron::ResqueCollector.new
34
- rc.configure &block unless block.nil?
35
- rc.start!
36
- @@collector = rc
37
- nil
38
- end
39
-
11
+ # Public: lazy load resque specific collector
12
+ #
40
13
  def self.collector
41
- @@collector
42
- end
43
-
44
- end
45
-
46
- class ResqueCollector
47
-
48
- include Evenitron::Logger
49
-
50
- def initialize
51
- @collector_url = ENV['EVENITRON_COLLECTOR_URL']
52
- @system_key = ENV['EVENITRON_SYSTEM_KEY']
53
- end
54
-
55
- def configure(&block)
56
- instance_eval &block
57
- end
58
-
59
- def system_key(key)
60
- @system_key = key
61
- end
62
-
63
- def api_key(key)
64
- @api_key = key
65
- end
66
-
67
- def collector_url(url)
68
- @collector_url = url
69
- end
70
-
71
- def start!
72
- log.info "Registering resque evenitron collector with system key #{@system_key} to send messages to #{@collector_url}"
73
- @client = Evenitron::CollectorClient.new @system_key, @collector_url, 'resque'
74
- end
75
-
76
- def job_queued(queue, item)
77
- id = get_id(item)
78
- log.info "Sending job queued message for job #{id}"
79
- @client.job_queued :id => id, :queue => queue, :task => item[:class]
80
- end
81
-
82
- def job_started(job)
83
- log.info "Sending job started message for job #{job.evenitron_id}"
84
- @client.job_started :id => job.evenitron_id, :worker => job.worker.to_s, :task => job.payload['class']
85
- end
86
-
87
- def job_failed(job)
88
- log.info "Sending job failed message for job #{job.evenitron_id}"
89
- @client.job_failed :id => job.evenitron_id, :task => job.payload['class']
90
- end
91
-
92
- def job_complete(job)
93
- log.info "Sending job complete message for job #{job.evenitron_id}"
94
- @client.job_complete :id => job.evenitron_id, :task => job.payload['class']
95
- end
96
-
97
- private
98
-
99
- def get_id(item)
100
- item['evenitron_id']
14
+ @_collector ||= Evenitron::Resque::Collector.new
101
15
  end
102
16
 
103
17
  end
104
18
 
105
19
  end
106
-
107
- module Resque
108
-
109
- class << self
110
-
111
- alias :base_push :push
112
-
113
- def push(queue, item)
114
- item['evenitron_id'] ||= Evenitron.generate_id
115
- base_push(queue, item)
116
- Evenitron::Resque.collector.job_queued queue, item
117
- end
118
-
119
- end
120
-
121
- class Job
122
-
123
- alias :base_perform :perform
124
-
125
- def perform
126
- begin
127
- Evenitron.logger.debug "Processing job #{evenitron_id}"
128
- Evenitron::Resque.collector.job_started self
129
-
130
- result = base_perform
131
- Evenitron::Resque.collector.job_complete self
132
-
133
- Evenitron.logger.debug "Processed job #{evenitron_id}, result was #{result}"
134
- result
135
- rescue => e
136
- Evenitron.logger.fatal "Exception occurred whilst processing job #{evenitron_id}"
137
- Evenitron.logger.debug e.inspect
138
- Evenitron::Resque.collector.job_failed self
139
- raise e
140
- end
141
- end
142
-
143
- def evenitron_id
144
- @payload['evenitron_id']
145
- end
146
-
147
- end
148
-
149
- end
@@ -0,0 +1,39 @@
1
+ module Evenitron
2
+
3
+ module Resque
4
+
5
+ class Collector
6
+
7
+ include Evenitron::Collector
8
+
9
+ def component
10
+ "resque"
11
+ end
12
+
13
+ def job_queued(queue, item)
14
+ client.job_queued :id => get_id(item), :queue => queue, :task => item[:class]
15
+ end
16
+
17
+ def job_started(job)
18
+ client.job_started :id => job.evenitron_id, :worker => job.worker.to_s, :task => job.payload['class']
19
+ end
20
+
21
+ def job_failed(job)
22
+ client.job_failed :id => job.evenitron_id, :task => job.payload['class']
23
+ end
24
+
25
+ def job_complete(job)
26
+ client.job_complete :id => job.evenitron_id, :task => job.payload['class']
27
+ end
28
+
29
+ private
30
+
31
+ def get_id(item)
32
+ item['evenitron_id']
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,52 @@
1
+ # Public: Wraps resque methods and classes with hooks to generate messages
2
+ # to the collector
3
+ #
4
+ module Resque
5
+
6
+ class << self
7
+
8
+ alias :base_push :push
9
+
10
+ # Private: wraps the push method to decorate the job with an evenitron
11
+ # task id. Then generates a job queued message
12
+ def push(queue, item)
13
+ item['evenitron_id'] ||= Evenitron.generate_id
14
+ base_push(queue, item)
15
+ Evenitron::Resque.collector.job_queued queue, item
16
+ end
17
+ Evenitron.logger.info "Registered resque job queued callback"
18
+
19
+ end
20
+
21
+ class Job
22
+
23
+ alias :base_perform :perform
24
+
25
+ # Private: wraps the perform method to generate job processing messages
26
+ #
27
+ def perform
28
+ begin
29
+ Evenitron::Resque.collector.job_started self
30
+
31
+ result = base_perform
32
+ Evenitron::Resque.collector.job_complete self
33
+ result
34
+ rescue => e
35
+ Evenitron.logger.error "Exception occurred whilst processing job #{evenitron_id}"
36
+ Evenitron.logger.debug e.inspect
37
+ Evenitron::Resque.collector.job_failed self
38
+ raise e
39
+ end
40
+ end
41
+
42
+ Evenitron.logger.info "Registered resque job started callback"
43
+ Evenitron.logger.info "Registered resque job completed callback"
44
+ Evenitron.logger.info "Registered resque job failed callback"
45
+
46
+ def evenitron_id
47
+ @payload['evenitron_id']
48
+ end
49
+
50
+ end
51
+
52
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evenitron
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-25 00:00:00.000000000Z
12
+ date: 2012-06-17 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rest-client
16
- requirement: &70228307216040 !ruby/object:Gem::Requirement
16
+ requirement: &70299044095340 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: 1.6.7
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70228307216040
24
+ version_requirements: *70299044095340
25
25
  description: Ruby client for Evenitron with hooks for delayed_job and resque
26
26
  email:
27
27
  - support@evenitron.com
@@ -29,10 +29,13 @@ executables: []
29
29
  extensions: []
30
30
  extra_rdoc_files: []
31
31
  files:
32
+ - lib/evenitron/collector.rb
32
33
  - lib/evenitron/collector_client.rb
34
+ - lib/evenitron/delayed_job/collector.rb
33
35
  - lib/evenitron/delayed_job.rb
34
- - lib/evenitron/evenitron.rb
35
36
  - lib/evenitron/logger.rb
37
+ - lib/evenitron/resque/collector.rb
38
+ - lib/evenitron/resque/resque.rb
36
39
  - lib/evenitron/resque.rb
37
40
  - lib/evenitron.rb
38
41
  - README.markdown
@@ -1,32 +0,0 @@
1
- module Evenitron
2
-
3
- def self.logger
4
- @@logger ||= default_logger
5
- end
6
-
7
- def self.logger=(logger)
8
- @@logger = logger
9
- end
10
-
11
- def self.encode(*values)
12
- value = values.join ':'
13
- cleanse_base64 Base64.encode64(value)
14
- end
15
-
16
- def self.generate_id(length = 64)
17
- cleanse_base64 SecureRandom.base64(length)
18
- end
19
-
20
- private
21
-
22
- def self.default_logger
23
- logger = ::Logger.new(STDOUT)
24
- logger.level = ::Logger::INFO
25
- logger
26
- end
27
-
28
- def self.cleanse_base64(value)
29
- value.gsub /[\s=]/, ''
30
- end
31
-
32
- end