formatted-metrics 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -39,6 +39,18 @@ Metrics.instrument 'sidekiq.queue', source: 'background' do
39
39
  yield
40
40
  end
41
41
  # => 'source=app.background measure.sidekiq.queue=500ms'
42
+
43
+ Metrics.group 'sidekiq' do
44
+ instrument 'queues.process', 100, units: 'jobs'
45
+ instrument 'workers.busy', 10, units: 'workers'
46
+
47
+ instrument 'queue.time', source: 'worker.1' do
48
+ sleep 1
49
+ end
50
+ end
51
+
52
+ # => 'source=app measure.sidekiq.queues.processed=100jobs measure.sidekiq.workers.busy=10workers'
53
+ # => 'source=app.worker.1 measure.sidekiq.queue.time=1000.00ms'
42
54
  ```
43
55
 
44
56
  ## TODO
@@ -18,8 +18,6 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency 'activesupport', '>= 3.1'
22
-
23
21
  spec.add_development_dependency 'bundler', '~> 1.3'
24
22
  spec.add_development_dependency 'rake'
25
23
  spec.add_development_dependency 'rspec', '~> 2.14'
@@ -21,11 +21,11 @@ module Metrics
21
21
  end
22
22
 
23
23
  def source
24
- @source ||= ENV['METRICS_SOURCE'] || ENV['APP_NAME'] || `hostname`.chomp.underscore
24
+ @source ||= ENV['METRICS_SOURCE'] || ENV['APP_NAME'] || `hostname`.chomp
25
25
  end
26
26
 
27
27
  def formatter
28
- @formatter ||= Metrics::Formatter
28
+ @formatter ||= Metrics::Formatters::L2Met
29
29
  end
30
30
  end
31
31
  end
@@ -0,0 +1,31 @@
1
+ module Metrics::Formatters
2
+ class Base
3
+ attr_reader :instrumenters
4
+
5
+ def self.write(*instrumenters)
6
+ new(*instrumenters).write
7
+ end
8
+
9
+ def initialize(*instrumenters)
10
+ @instrumenters = instrumenters.flatten
11
+ end
12
+
13
+ def write
14
+ lines.each do |line|
15
+ configuration.logger.info line
16
+ end
17
+ instrumenters
18
+ end
19
+
20
+ def lines
21
+ raise NotImplementedError
22
+ end
23
+
24
+ private
25
+
26
+ def configuration
27
+ Metrics.configuration
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ module Metrics::Formatters
2
+ class L2Met < Base
3
+ def lines
4
+ groups.map do |source, instrumenters|
5
+ [
6
+ "source=#{full_source(source)}",
7
+ instrumenters.map { |instrumenter| "measure.#{instrumenter.metric}=#{instrumenter.value}#{instrumenter.units}" }.join(' ')
8
+ ].flatten.join(' ')
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ # Internal: We group the metrics by their source so that we can separate
15
+ # the lines.
16
+ def groups
17
+ instrumenters.group_by(&:source)
18
+ end
19
+
20
+ def full_source(source=nil)
21
+ if source
22
+ [configuration.source, source].join('.')
23
+ else
24
+ configuration.source
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ module Metrics
2
+ # Public: Starts a new Grouping context, which allows for multiple
3
+ # instruments to output on a single line.
4
+ class Grouping
5
+ attr_reader :namespace, :instrumenters
6
+
7
+ def self.instrument(*args, &block)
8
+ new(*args, &block).instrumenters
9
+ end
10
+
11
+ def initialize(namespace = nil, &block)
12
+ @instrumenters = []
13
+ @namespace = namespace
14
+ instance_eval &block
15
+ end
16
+
17
+ def instrument(metric, *args, &block)
18
+ metric = "#{namespace}.#{metric}" if namespace
19
+ instrumenters << Instrumenter.instrument(metric, *args, &block)
20
+ end
21
+ end
22
+ end
@@ -1,35 +1,38 @@
1
- require 'active_support/core_ext/module/delegation'
2
-
3
1
  module Metrics
4
2
 
5
- # Internal: Responsible for handling an ActiveSupport::Notifications message.
3
+ # Internal: Responsible for taking a list or an Array of
4
+ # Metrics::Instrumenters and passing them to the formatter.
6
5
  class Handler
7
- attr_reader :args
6
+ attr_reader :instrumenters
7
+
8
+ def self.handle(*instrumenters)
9
+ new(*instrumenters).handle
10
+ end
8
11
 
9
- def initialize(*args)
10
- @args = args
12
+ def initialize(*instrumenters)
13
+ @instrumenters = instrumenters.flatten
11
14
  end
12
15
 
16
+ # Public: Writes all of the instrumenters to STDOUT using the formatter.
17
+ #
18
+ # Returns an Array of Metrics::Instrumenters that were written to STDOUT.
13
19
  def handle
14
- log if trackable?
20
+ write instrumenters
15
21
  end
16
22
 
17
23
  private
18
24
 
19
- delegate :configuration, to: :'Metrics'
20
-
21
- def trackable?
22
- event.payload[:measure]
25
+ def configuration
26
+ Metrics.configuration
23
27
  end
24
28
 
25
- def event
26
- @event ||= ActiveSupport::Notifications::Event.new(*args)
29
+ def write(*args, &block)
30
+ formatter.write(*args, &block)
27
31
  end
28
32
 
29
- def log
30
- configuration.logger.info configuration.formatter.new(event).to_s
33
+ def formatter
34
+ configuration.formatter
31
35
  end
32
-
36
+
33
37
  end
34
-
35
38
  end
@@ -1,8 +1,9 @@
1
- require 'active_support/concern'
2
-
3
1
  module Metrics
4
2
  module Instrumentable
5
- extend ActiveSupport::Concern
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
6
7
 
7
8
  # Public: See Metrics.instrument.
8
9
  #
@@ -0,0 +1,64 @@
1
+ module Metrics
2
+ # Public: Responsible for sampling a measurement of something.
3
+ #
4
+ # metric - The name of the metric to measure (e.g. rack.request.time)
5
+ #
6
+ # Returns a new Metrics::Instrumenter.
7
+ class Instrumenter
8
+ TIME_UNITS = 'ms'.freeze
9
+
10
+ attr_reader :metric
11
+
12
+ def self.instrument(*args, &block)
13
+ instrument = new(*args, &block)
14
+ instrument.value
15
+ instrument
16
+ end
17
+
18
+ def initialize(metric, *args, &block)
19
+ @metric = metric
20
+ @options = extract_options!(args)
21
+ @block = block
22
+ @value = args.first if args.length > 0
23
+ end
24
+
25
+ # Public: Runs the instrumenter.
26
+ #
27
+ # Returns the run time if a block was supplied.
28
+ # Returns the value if the
29
+ def value
30
+ timing? ? time : @value
31
+ end
32
+
33
+ def units
34
+ timing? ? TIME_UNITS : options[:units]
35
+ end
36
+
37
+ def source
38
+ options[:source]
39
+ end
40
+
41
+ private
42
+ attr_reader :options, :block
43
+
44
+ def timing?
45
+ !block.nil?
46
+ end
47
+
48
+ def time
49
+ @time ||= begin
50
+ start = Time.now
51
+ block.call
52
+ (Time.now - start) * 1000.0
53
+ end
54
+ end
55
+
56
+ def extract_options!(options)
57
+ if options.last.is_a?(Hash)
58
+ options.pop
59
+ else
60
+ {}
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,3 +1,3 @@
1
1
  module FormattedMetrics
2
- VERSION = '0.1.2'
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/metrics.rb CHANGED
@@ -1,24 +1,21 @@
1
- require 'active_support/notifications'
2
- require 'active_support/core_ext/array/extract_options'
3
- require 'active_support/dependencies/autoload'
4
-
5
1
  require 'metrics/railtie' if defined?(Rails)
6
2
 
7
3
  module Metrics
8
- extend ActiveSupport::Autoload
4
+ autoload :Configuration, 'metrics/configuration'
5
+ autoload :Instrumentable, 'metrics/instrumentable'
6
+ autoload :Instrumenter, 'metrics/instrumenter'
7
+ autoload :Grouping, 'metrics/grouping'
8
+ autoload :Handler, 'metrics/handler'
9
9
 
10
- autoload :Configuration
11
- autoload :Handler
12
- autoload :Formatter
13
- autoload :Instrumentable
10
+ module Formatters
11
+ autoload :Base, 'metrics/formatters/base'
12
+ autoload :L2Met, 'metrics/formatters/l2met'
13
+ end
14
14
 
15
15
  class << self
16
16
 
17
17
  # Public: Instrument a metric.
18
18
  #
19
- # metric - The name of the metric (e.g. rack.request)
20
- # source - A source to append to the default source.
21
- #
22
19
  # Example
23
20
  #
24
21
  # # Instrument the duration of an event.
@@ -35,35 +32,28 @@ module Metrics
35
32
  # end
36
33
  #
37
34
  # Returns nothing.
38
- def instrument(metric, *args, &block)
39
- options = args.extract_options!
40
-
41
- measure = if args.empty?
42
- block_given? ? true : 1
43
- else
44
- args.first
45
- end
46
-
47
- ActiveSupport::Notifications.instrument(
48
- metric,
49
- options.merge(measure: measure, source: options[:source]),
50
- &block
51
- )
35
+ def instrument(*args, &block)
36
+ Handler.handle(Instrumenter.instrument(*args, &block))
52
37
  end
53
38
 
54
- # Public: Subscribe to all ActiveSupport::Notifications events. Only events
55
- # that have a payload with a :measure key that is truthy will be processed
56
- # and logged to stdout.
39
+ # Public: Group multiple instruments.
57
40
  #
58
41
  # Example
59
42
  #
60
- # Metrics.setup
43
+ # Metrics.group 'sidekiq' do
44
+ # instrument 'request.time' do
45
+ # begin
46
+ # @app.call(env)
47
+ # rescue Exception => e
48
+ # instrument 'exceptions', 1
49
+ # raise
50
+ # end
51
+ # end
52
+ # end
61
53
  #
62
54
  # Returns nothing.
63
- def subscribe
64
- ActiveSupport::Notifications.subscribe /.*/ do |*args|
65
- Metrics::Handler.new(*args).handle
66
- end
55
+ def group(*args, &block)
56
+ Handler.handle(Grouping.instrument(*args, &block))
67
57
  end
68
58
 
69
59
  def configuration
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Metrics::Formatters::L2Met do
4
+ let(:formatter) { described_class.new *instrumenters }
5
+
6
+ before do
7
+ Metrics.configuration.stub source: 'app'
8
+ end
9
+
10
+ def instrumenter(values)
11
+ double Metrics::Instrumenter, { source: nil }.merge(values)
12
+ end
13
+
14
+ describe '.lines' do
15
+ subject(:format) { formatter.lines }
16
+
17
+ context 'with a single instrumenter' do
18
+ let(:instrumenters) { [ instrumenter(metric: 'rack.request.time', value: 10, units: 'ms') ] }
19
+ it { should eq ['source=app measure.rack.request.time=10ms'] }
20
+ end
21
+
22
+ context 'with multiple instrumenters' do
23
+ let(:instrumenters) do
24
+ [ instrumenter(metric: 'rack.request.time', value: 10, units: 'ms'),
25
+ instrumenter(metric: 'jobs.busy', value: 10, units: 'jobs') ]
26
+ end
27
+
28
+ it { should eq ['source=app measure.rack.request.time=10ms measure.jobs.busy=10jobs'] }
29
+ end
30
+
31
+ context 'with multiple metrics from different sources' do
32
+ let(:instrumenters) do
33
+ [ instrumenter(metric: 'rack.request.time', value: 10, units: 'ms'),
34
+ instrumenter(metric: 'jobs.queued', value: 15, units: 'jobs', source: 'foo'),
35
+ instrumenter(metric: 'jobs.busy', value: 10, units: 'jobs', source: 'foo') ]
36
+ end
37
+
38
+ it { should eq ['source=app measure.rack.request.time=10ms', 'source=app.foo measure.jobs.queued=15jobs measure.jobs.busy=10jobs'] }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Metrics::Grouping do
4
+ describe '.instrumenters' do
5
+ it 'builds instruments' do
6
+ group = described_class.new do
7
+ instrument 'jobs.busy', 10
8
+ instrument 'rack.request.time', 500, units: 'ms'
9
+ end
10
+
11
+ expect(group.instrumenters).to have(2).instrumenters
12
+ end
13
+
14
+ it 'allows a namespace to be provided' do
15
+ group = described_class.new 'rack' do
16
+ instrument 'request.time', 500, units: 'ms'
17
+ end
18
+
19
+ expect(group.instrumenters.first.metric).to eq 'rack.request.time'
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe Metrics::Instrumenter do
4
+ let(:instrumenter) { described_class.new('rack.request') { } }
5
+
6
+ describe '.value' do
7
+ subject { instrumenter.value }
8
+
9
+ context 'with a value' do
10
+ let(:instrumenter) { described_class.new('rack.request', 500) }
11
+ it { should eq 500 }
12
+ end
13
+
14
+ context 'with a block' do
15
+ before { Time.should_receive(:now).and_return(Time.at(1), Time.at(3)) }
16
+ it { should eq 2000.0 }
17
+ end
18
+ end
19
+
20
+ describe '.units' do
21
+ subject { instrumenter.units }
22
+
23
+ context 'with a block' do
24
+ it { should eq 'ms' }
25
+ end
26
+
27
+ context 'when specified' do
28
+ let(:instrumenter) { described_class.new('jobs.busy', 10, units: 'jobs') }
29
+ it { should eq 'jobs' }
30
+ end
31
+ end
32
+
33
+ describe '.source' do
34
+ subject { instrumenter.source }
35
+
36
+ context 'by default' do
37
+ it { should be_nil }
38
+ end
39
+
40
+ context 'when specified' do
41
+ let(:instrumenter) { described_class.new('jobs.busy', 10, units: 'jobs', source: 'sidekiq') }
42
+ it { should eq 'sidekiq' }
43
+ end
44
+ end
45
+ end
data/spec/metrics_spec.rb CHANGED
@@ -1,42 +1,21 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Metrics do
4
- let(:instrumenter) { ActiveSupport::Notifications }
5
-
6
4
  describe '#instrument' do
7
- context 'with a block' do
8
- it 'instruments the duration' do
9
- instrumenter.should_receive(:instrument).with('rack.request', measure: true, source: nil)
10
- Metrics.instrument 'rack.request' do
11
- 'foo'
12
- end
13
- end
14
-
15
- it 'instruments the duration with a source' do
16
- instrumenter.should_receive(:instrument).with('rack.request', measure: true, source: 'foo')
17
- Metrics.instrument 'rack.request', source: 'foo' do
18
- 'do something long'
19
- end
20
- end
21
- end
22
-
23
- context 'with a measurement' do
24
- it 'instruments the measurement' do
25
- instrumenter.should_receive(:instrument).with('rack.request', measure: 10, source: nil)
26
- Metrics.instrument 'rack.request', 10
27
- end
28
-
29
- it 'instruments the measurement with a source' do
30
- instrumenter.should_receive(:instrument).with('rack.request', measure: 10, source: 'foo')
31
- Metrics.instrument 'rack.request', 10, source: 'foo'
32
- end
5
+ it 'delegates to Metrics::Instrumenter' do
6
+ instrumenter = double Metrics::Instrumenter
7
+ Metrics::Instrumenter.should_receive(:instrument).with('rack.request', 10).and_return(instrumenter)
8
+ Metrics::Handler.should_receive(:handle).with(instrumenter)
9
+ Metrics.instrument 'rack.request', 10
33
10
  end
11
+ end
34
12
 
35
- context 'with empty no block and no measurement' do
36
- it 'instruments with a measurement of 1' do
37
- instrumenter.should_receive(:instrument).with('exception', measure: 1, source: nil)
38
- Metrics.instrument 'exception'
39
- end
13
+ describe '#group' do
14
+ it 'delegates to Metrics::Grouping' do
15
+ grouping = double Metrics::Grouping
16
+ Metrics::Grouping.should_receive(:instrument).and_return(grouping)
17
+ Metrics::Handler.should_receive(:handle).with(grouping)
18
+ Metrics.group { instrument 'rack.request', 10 }
40
19
  end
41
20
  end
42
21
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: formatted-metrics
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,24 +9,8 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-08-06 00:00:00.000000000 Z
12
+ date: 2013-08-07 00:00:00.000000000 Z
13
13
  dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: activesupport
16
- requirement: !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '3.1'
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ! '>='
28
- - !ruby/object:Gem::Version
29
- version: '3.1'
30
14
  - !ruby/object:Gem::Dependency
31
15
  name: bundler
32
16
  requirement: !ruby/object:Gem::Requirement
@@ -93,14 +77,18 @@ files:
93
77
  - lib/formatted-metrics.rb
94
78
  - lib/metrics.rb
95
79
  - lib/metrics/configuration.rb
96
- - lib/metrics/formatter.rb
80
+ - lib/metrics/formatters/base.rb
81
+ - lib/metrics/formatters/l2met.rb
82
+ - lib/metrics/grouping.rb
97
83
  - lib/metrics/handler.rb
98
84
  - lib/metrics/instrumentable.rb
85
+ - lib/metrics/instrumenter.rb
99
86
  - lib/metrics/railtie.rb
100
87
  - lib/metrics/version.rb
101
- - spec/metrics/formatter_spec.rb
102
- - spec/metrics/handler_spec.rb
88
+ - spec/metrics/formatters/l2met_spec.rb
89
+ - spec/metrics/grouping_spec.rb
103
90
  - spec/metrics/instrumentable_spec.rb
91
+ - spec/metrics/instrumenter_spec.rb
104
92
  - spec/metrics_spec.rb
105
93
  - spec/spec_helper.rb
106
94
  homepage: http://github.com/remind101/formatted-metrics
@@ -118,7 +106,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
118
106
  version: '0'
119
107
  segments:
120
108
  - 0
121
- hash: 1246572699261836643
109
+ hash: -1552981004150797655
122
110
  required_rubygems_version: !ruby/object:Gem::Requirement
123
111
  none: false
124
112
  requirements:
@@ -127,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
115
  version: '0'
128
116
  segments:
129
117
  - 0
130
- hash: 1246572699261836643
118
+ hash: -1552981004150797655
131
119
  requirements: []
132
120
  rubyforge_project:
133
121
  rubygems_version: 1.8.23
@@ -135,8 +123,9 @@ signing_key:
135
123
  specification_version: 3
136
124
  summary: Easily output formatted metrics to stdout
137
125
  test_files:
138
- - spec/metrics/formatter_spec.rb
139
- - spec/metrics/handler_spec.rb
126
+ - spec/metrics/formatters/l2met_spec.rb
127
+ - spec/metrics/grouping_spec.rb
140
128
  - spec/metrics/instrumentable_spec.rb
129
+ - spec/metrics/instrumenter_spec.rb
141
130
  - spec/metrics_spec.rb
142
131
  - spec/spec_helper.rb
@@ -1,53 +0,0 @@
1
- require 'active_support/core_ext/module/delegation'
2
- require 'active_support/core_ext/string/inflections'
3
-
4
- module Metrics
5
-
6
- # Internal: Responsible for taking an event and formatting it to be consumed
7
- # by l2met.
8
- #
9
- # Example
10
- #
11
- # Formatter.new(event).to_s
12
- # # => "source=my-app measure.rack.request=50ms"
13
- #
14
- # Returns a Metrics::Formatter.to_s
15
- class Formatter
16
- DEFAULT_UNITS = 'ms'.freeze
17
-
18
- # An object that conforms to the same public interface as
19
- # ActiveSupport::Notifications::Event
20
- attr_reader :event
21
-
22
- def initialize(event)
23
- @event = event
24
- end
25
-
26
- def to_s
27
- "source=#{source} measure.#{event_name}=#{value}"
28
- end
29
-
30
- private
31
-
32
- delegate :name, :payload, :duration, to: :event, prefix: true
33
- delegate :configuration, to: :'Metrics'
34
-
35
- def value
36
- case measurement = event_payload[:measure]
37
- when true
38
- [event_duration, DEFAULT_UNITS].join('')
39
- else
40
- if units = event_payload[:units]
41
- [measurement, units].join('')
42
- else
43
- measurement
44
- end
45
- end
46
- end
47
-
48
- def source
49
- [configuration.source, Array(event_payload[:source])].flatten.join('.')
50
- end
51
- end
52
-
53
- end
@@ -1,36 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Metrics::Formatter do
4
- let(:event ) { double ActiveSupport::Notifications::Event, name: 'rack.request', payload: { measure: true } }
5
- let(:formatter) { described_class.new event }
6
-
7
- before do
8
- formatter.stub_chain :configuration, source: 'app'
9
- end
10
-
11
- describe '.to_s' do
12
- subject { formatter.to_s }
13
-
14
- context 'with a duration' do
15
- before { event.stub duration: 10 }
16
- it { should eq 'source=app measure.rack.request=10ms' }
17
- end
18
-
19
- context 'with a measurement' do
20
- context 'with units' do
21
- before { event.stub payload: { measure: 1, units: 's' } }
22
- it { should eq 'source=app measure.rack.request=1s' }
23
- end
24
-
25
- context 'without units' do
26
- before { event.stub payload: { measure: 50 } }
27
- it { should eq 'source=app measure.rack.request=50' }
28
- end
29
- end
30
-
31
- context 'with a source' do
32
- before { event.stub name: 'workers.busy', payload: { measure: 10, source: 'sidekiq' } }
33
- it { should eq 'source=app.sidekiq measure.workers.busy=10' }
34
- end
35
- end
36
- end
@@ -1,38 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Metrics::Handler do
4
- let(:args ) { ['event args'] * 5 }
5
- let(:handler ) { described_class.new *args }
6
-
7
- let(:event ) { double ActiveSupport::Notifications::Event }
8
- let(:formatter) { double Metrics.configuration.formatter }
9
-
10
- describe '.handle' do
11
- before do
12
- ActiveSupport::Notifications::Event.stub new: event
13
- Metrics.configuration.formatter.stub new: formatter
14
- end
15
-
16
- context 'when the event is trackable' do
17
- before do
18
- event.stub payload: { measure: true }
19
- end
20
-
21
- it 'should handle the event' do
22
- Metrics.configuration.logger.should_receive(:info).with(formatter.to_s)
23
- handler.handle
24
- end
25
- end
26
-
27
- context 'when the event is not trackable' do
28
- before do
29
- event.stub payload: { }
30
- end
31
-
32
- it 'should not handle the event' do
33
- Metrics.configuration.logger.should_receive(:info).never
34
- handler.handle
35
- end
36
- end
37
- end
38
- end