stackify-ruby-apm 1.10.2 → 1.13.2

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/stackify_apm/agent.rb +0 -2
  3. data/lib/stackify_apm/config.rb +36 -5
  4. data/lib/stackify_apm/context.rb +4 -1
  5. data/lib/stackify_apm/context/prefix.rb +30 -0
  6. data/lib/stackify_apm/context/request/headers.rb +30 -0
  7. data/lib/stackify_apm/context_builder.rb +1 -0
  8. data/lib/stackify_apm/helper/database_helper.rb +40 -1
  9. data/lib/stackify_apm/instrumenter_helper.rb +90 -4
  10. data/lib/stackify_apm/logger/logger_high_version.rb +5 -1
  11. data/lib/stackify_apm/logger/logger_lower_version.rb +5 -1
  12. data/lib/stackify_apm/middleware.rb +21 -3
  13. data/lib/stackify_apm/normalizers/active_record.rb +16 -8
  14. data/lib/stackify_apm/root_info.rb +7 -1
  15. data/lib/stackify_apm/serializers/transactions.rb +5 -0
  16. data/lib/stackify_apm/span/context.rb +42 -1
  17. data/lib/stackify_apm/spies.rb +4 -2
  18. data/lib/stackify_apm/spies/action_dispatch.rb +6 -1
  19. data/lib/stackify_apm/spies/curb.rb +41 -20
  20. data/lib/stackify_apm/spies/curb/easy.rb +220 -92
  21. data/lib/stackify_apm/spies/curb/multi.rb +26 -12
  22. data/lib/stackify_apm/spies/custom_instrumenter.rb +25 -4
  23. data/lib/stackify_apm/spies/delayed_job.rb +49 -0
  24. data/lib/stackify_apm/spies/dynamo_db.rb +50 -0
  25. data/lib/stackify_apm/spies/faraday.rb +87 -0
  26. data/lib/stackify_apm/spies/httparty.rb +45 -24
  27. data/lib/stackify_apm/spies/httpclient.rb +41 -20
  28. data/lib/stackify_apm/spies/httprb.rb +39 -18
  29. data/lib/stackify_apm/spies/log4r.rb +60 -0
  30. data/lib/stackify_apm/spies/logger.rb +117 -0
  31. data/lib/stackify_apm/spies/logging.rb +66 -0
  32. data/lib/stackify_apm/spies/net_http.rb +38 -20
  33. data/lib/stackify_apm/spies/redis.rb +36 -30
  34. data/lib/stackify_apm/spies/sequel.rb +28 -11
  35. data/lib/stackify_apm/spies/sinatra_activerecord/mysql_adapter.rb +27 -25
  36. data/lib/stackify_apm/spies/sinatra_activerecord/postgresql_adapter.rb +27 -24
  37. data/lib/stackify_apm/spies/sinatra_activerecord/sqlite_adapter.rb +18 -8
  38. data/lib/stackify_apm/spies/stackify_logger.rb +28 -16
  39. data/lib/stackify_apm/spies/sucker_punch.rb +39 -0
  40. data/lib/stackify_apm/spies/yell.rb +65 -0
  41. data/lib/stackify_apm/util.rb +10 -9
  42. data/lib/stackify_apm/version.rb +1 -1
  43. data/stackify-ruby-apm.gemspec +1 -0
  44. metadata +26 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ace5e2981f0588711fce71484c2192d09647f9387693082b55c907ed421da82
4
- data.tar.gz: 1bff561f0a9e316cf487bba4168c89a7bd6defb4f87c0dd1eacdf648ebee2d3c
3
+ metadata.gz: c1ee1b9b91d78729820e74b7fbc014310af4037390de6c0982cb0169cba50a8a
4
+ data.tar.gz: 3a7dfe377099f033779e85d591e8ec973039ee4a4b59f20175e57afdd967819a
5
5
  SHA512:
6
- metadata.gz: 540d32c55572249886f87bd3c339cd158d9f85d0a0556323698b25046decae29eab319669ec5b077121cb48046c08fca6eb0ad8223d3488fabb54fe0183c6a0a
7
- data.tar.gz: ae9f59d8c40f09c71833d67cb836b7ebb278934b336160f2375763416479fa5050fdbbbfbc2b7843630dc21b005caebeb9372df835c93e254c9f507d1ce7cc29
6
+ metadata.gz: 143b1fba17994c286ad71dc020d731247079b28a75fda11ee742ed8d49c023557a731e16c0ae8715153d6f5c98dbbb7dbbdaa10a225d4ad8f5f0750aedb7a739
7
+ data.tar.gz: 28be6a1c40fa6dab5fc5a35f2c56e99984c579d4e5f84e5481ffa6e5c9b9fa9c615a0425c1af81d5a3d1d7d4088df1b4f777250fde816b48a2049f320a1df1c4
@@ -101,8 +101,6 @@ module StackifyRubyAPM
101
101
  info '[Agent] start()'
102
102
  info '[Agent] transport type: ' + @config.transport
103
103
  spies_name = ''
104
- # If the rake task is detected as being ran then we don't load the spies
105
- StackifyRubyAPM::Util.apm_disabled_in_rake
106
104
  return false unless @config.instrument
107
105
  config.enabled_spies.each do |lib|
108
106
  spies_name = spies_name + ', ' + lib.inspect.to_s
@@ -26,7 +26,7 @@ module StackifyRubyAPM
26
26
  environment_name: ENV['RAILS_ENV'] || ENV['RACK_ENV'],
27
27
  already_instrumented_flag: false,
28
28
  rum_auto_injection: false,
29
- rum_enabled: false,
29
+ rum_enabled: true,
30
30
  rum_cookie_path: '/',
31
31
  rum_cookie_name: '.Stackify.Rum',
32
32
  transport: StackifyRubyAPM::TRACE_LOG,
@@ -70,7 +70,8 @@ module StackifyRubyAPM
70
70
  transport_http_endpoint: 'https://localhost:10601',
71
71
 
72
72
  queue: true,
73
- lambda_handler: ''
73
+ lambda_handler: '',
74
+ prefix_enabled: false
74
75
  }.freeze
75
76
 
76
77
  ENV_TO_KEY = {
@@ -97,7 +98,8 @@ module StackifyRubyAPM
97
98
  'STACKIFY_FLUSH_INTERVAL' => 'flush_interval_seconds',
98
99
  'STACKIFY_DISABLED_SPIES' => [:list, 'disabled_spies'],
99
100
  'STACKIFY_QUEUE' => [:bool, 'queue'],
100
- 'STACKIFY_LAMBDA_HANDLER' => 'lambda_handler'
101
+ 'STACKIFY_LAMBDA_HANDLER' => 'lambda_handler',
102
+ 'STACKIFY_PREFIX_ENABLED' => [:bool, 'prefix_enabled']
101
103
  }.freeze
102
104
 
103
105
  def initialize(options = {})
@@ -105,6 +107,7 @@ module StackifyRubyAPM
105
107
  set_from_args(options)
106
108
  set_from_config_file
107
109
  set_from_env
110
+ set_prefix_paths if @prefix_enabled
108
111
  yield self if block_given?
109
112
  debug_logger
110
113
  StackifyRubyAPM::Util.host_os == 'WINDOWS' ? load_stackify_props_windows : load_stackify_props_linux
@@ -170,6 +173,7 @@ module StackifyRubyAPM
170
173
 
171
174
  attr_accessor :queue
172
175
  attr_accessor :lambda_handler
176
+ attr_accessor :prefix_enabled
173
177
 
174
178
  attr_reader :client_id
175
179
  attr_reader :device_id
@@ -210,6 +214,20 @@ module StackifyRubyAPM
210
214
  httparty
211
215
  stackify_logger
212
216
  sidekiq
217
+ delayed_job
218
+ faraday
219
+ sucker_punch
220
+ dynamo_db
221
+ ]
222
+ end
223
+
224
+ def prefix_spies
225
+ return [] unless @prefix_enabled
226
+ %w[
227
+ logger
228
+ logging
229
+ log4r
230
+ yell
213
231
  ]
214
232
  end
215
233
 
@@ -232,7 +250,7 @@ module StackifyRubyAPM
232
250
  available_spies
233
251
  end
234
252
 
235
- new_available_spies - disabled_spies
253
+ new_available_spies + prefix_spies - disabled_spies
236
254
  end
237
255
 
238
256
  # Default Transport
@@ -253,7 +271,11 @@ module StackifyRubyAPM
253
271
 
254
272
  def assign(options)
255
273
  options.each do |key, value|
256
- send("#{key}=", value)
274
+ begin
275
+ send("#{key}=", value)
276
+ rescue Exception => e
277
+ info "[Config] Key: '#{key}' doesn't exist."
278
+ end
257
279
  end
258
280
  end
259
281
 
@@ -294,6 +316,14 @@ module StackifyRubyAPM
294
316
  assign(YAML.load_file(config_file) || {})
295
317
  end
296
318
 
319
+ # set log paths to prefix path if prefix_enabled is true
320
+ def set_prefix_paths
321
+ all_user_profile = ENV['ALLUSERSPROFILE'] || "C:\\ProgramData"
322
+
323
+ @log_path = StackifyRubyAPM::Util.host_os == 'WINDOWS' ? "#{all_user_profile}\\Stackify\\Agent\\debug\\stackify-ruby-apm-1.log" : "/usr/local/prefix/debug/stackify-ruby-apm-1.log"
324
+ @log_trace_path = StackifyRubyAPM::Util.host_os == 'WINDOWS' ? "#{all_user_profile}\\Stackify\\Agent\\log\\" : '/usr/local/prefix/log/'
325
+ end
326
+
297
327
  # rubocop:disable Naming/AccessorMethodName
298
328
  def set_rails(app)
299
329
  self.application_name ||= format_name(application_name || app.class.parent_name).strip
@@ -381,6 +411,7 @@ module StackifyRubyAPM
381
411
  info '[Config] environment_name must be String type.' unless @environment_name.is_a?(String) && defined?(@environment_name)
382
412
  info '[Config] transport must be String type.' unless @transport.is_a?(String) && defined?(@transport)
383
413
  info '[Config] Transport should be one of these values: [agent_socket, default, agent_http]. Should be a String.' if defined?(@transport) && !%w[agent_socket default agent_http].include?(@transport.downcase)
414
+ info '[Config] prefix_enabled must be Boolean type: true/false.' unless [TrueClass, FalseClass].include?(@prefix_enabled.class) && defined?(@prefix_enabled)
384
415
  end
385
416
  # rubocop:enable Metrics/CyclomaticComplexity
386
417
  # rubocop:enable Metrics/PerceivedComplexity
@@ -2,7 +2,9 @@
2
2
 
3
3
  # This class initializes the parameters and variables for the context of Transaction/Span
4
4
 
5
+ require 'stackify_apm/context/prefix'
5
6
  require 'stackify_apm/context/request'
7
+ require 'stackify_apm/context/request/headers'
6
8
  require 'stackify_apm/context/request/socket'
7
9
  require 'stackify_apm/context/request/url'
8
10
  require 'stackify_apm/context/response'
@@ -12,12 +14,13 @@ module StackifyRubyAPM
12
14
  class Context
13
15
  include NaivelyHashable
14
16
 
15
- attr_accessor :request, :response, :aws
17
+ attr_accessor :request, :response, :aws, :category, :prefix
16
18
  attr_reader :custom, :tags
17
19
 
18
20
  def initialize
19
21
  @custom = {}
20
22
  @tags = {}
23
+ @prefix = Context::Prefix.new
21
24
  end
22
25
 
23
26
  # add aws context to context instance
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StackifyRubyAPM
4
+ class Context
5
+ # @api private
6
+ class Prefix
7
+ include NaivelyHashable
8
+
9
+ attr_accessor :response_body, :request_body
10
+ attr_reader :response_headers, :request_headers
11
+
12
+ def request_headers=(headers)
13
+ @request_headers = to_json_list(headers)
14
+ end
15
+
16
+ def response_headers=(headers)
17
+ @response_headers = to_json_list(headers)
18
+ end
19
+
20
+ private
21
+ def to_json_list(header)
22
+ list_header = []
23
+ header.each do |key, value|
24
+ list_header << {key => value}
25
+ end
26
+ list_header.to_json
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StackifyRubyAPM
4
+ # @api private
5
+ class Context
6
+ # @api private
7
+ class Request
8
+ # @api private
9
+ class Headers
10
+ include NaivelyHashable
11
+
12
+ def initialize(req)
13
+ @values = build_headers req
14
+ end
15
+
16
+ attr_reader :values
17
+
18
+ def build_headers req
19
+ env = req.env
20
+ headers = Hash[*env.select {|k,v| k.start_with? 'HTTP_'}
21
+ .collect {|k,v| [k.sub(/^HTTP_/, ''), v]}
22
+ .collect {|k,v| [k.split('_').collect(&:capitalize).join('-'), v]}
23
+ .sort
24
+ .flatten]
25
+ return headers
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -27,6 +27,7 @@ module StackifyRubyAPM
27
27
  request.method = req.request_method
28
28
  request.url = Context::Request::Url.new(req).to_h
29
29
  request.body = get_body(req)
30
+ request.headers = Context::Request::Headers.new(req).values
30
31
 
31
32
  context
32
33
  end
@@ -18,10 +18,49 @@ module DatabaseHelper
18
18
  elsif driver.include? 'db2'
19
19
  'db2'
20
20
  elsif driver.include? 'sqlite'
21
- 'generic'
21
+ 'sqlite'
22
22
  end
23
23
  end
24
24
  # rubocop:enable Metrics/CyclomaticComplexity
25
25
  # rubocop:enable Metrics/PerceivedComplexity
26
+
27
+ # Check the prepared statement by placeholder if its valid
28
+ # placeholder - contains the prepared statement pattern.
29
+ # Placeholder for mysql, sqlite, jdbc oracle, db2 is ?, example "SELECT * FROM posts WHERE author = ? and id = ?"
30
+ # Placeholder for postgres is $1, $2,...$n, example "SELECT * FROM posts WHERE author = $1 and id = $2"
31
+ # statement - contains the db properties such as SQL, PROVIDER, etc.
32
+ # So if there's payload value we append PREFIX_SQL_PARAMETERS to the existing object properties.
33
+ # payload - contains the payload[:binds] which is the payload value/data of the placeholder.
34
+ # Example payload: {:sql=>"SELECT * FROM posts WHERE author = ? and id = ?", :db_adapter=>"mysql2::client", :binds=>["J.K. Rowling", 1]}
35
+ def check_prepared_stmt_by_placeholder(placeholder, statement, payload)
36
+ sqlParam = []
37
+ unless payload[:binds].nil?
38
+ payload[:binds].each_with_index do |record, idx|
39
+ if record && defined?(record.value)
40
+ StackifyRubyAPM::Util.pushToAryIndex(sqlParam, idx, record.value[0..999])
41
+ elsif ((record && record.instance_of?(Array)) && defined?(record[1]))
42
+ StackifyRubyAPM::Util.pushToAryIndex(sqlParam, idx, record[1][0..999])
43
+ else
44
+ StackifyRubyAPM::Util.pushToAryIndex(sqlParam, idx, record[0..999])
45
+ end
46
+ end
47
+ statement[:PREFIX_SQL_PARAMETERS] = sqlParam[0..99].to_json if sqlParam.count > 0
48
+ statement[:PREFIX_SQL_PARAMETER_COUNT] = sqlParam.length.to_s if sqlParam.count > 0
49
+ true
50
+ else
51
+ false
52
+ end
53
+ end
54
+
55
+ # Common DB span properties shared to sequel, activerecord, etc.
56
+ def get_common_db_properties
57
+ {
58
+ CATEGORY: 'Database',
59
+ SUBCATEGORY: 'Execute',
60
+ COMPONENT_CATEGORY: 'DB Query',
61
+ COMPONENT_DETAIL: 'Execute SQL Query',
62
+ PROVIDER: 'generic'
63
+ }
64
+ end
26
65
  end
27
66
  include DatabaseHelper
@@ -90,9 +90,8 @@ module StackifyRubyAPM
90
90
  )
91
91
  end
92
92
 
93
- req = #{current_method_without}(*args, &block)
94
93
  StackifyRubyAPM.span name, type, context: ctx do
95
- req
94
+ #{current_method_without}(*args, &block)
96
95
  end
97
96
  else
98
97
  return #{current_method_without}(*args, &block)
@@ -169,9 +168,9 @@ module StackifyRubyAPM
169
168
  CATEGORY: 'Ruby'
170
169
  )
171
170
  end
172
- req = _self_without_apm_#{current_method}(*args, &block)
171
+
173
172
  StackifyRubyAPM.span name, type, context: ctx do
174
- req
173
+ _self_without_apm_#{current_method}(*args, &block)
175
174
  end
176
175
  else
177
176
  return _self_without_apm_#{current_method}(*args, &block)
@@ -190,6 +189,93 @@ module StackifyRubyAPM
190
189
  @custom_class_info[current_class.to_s]['controller'] = true if class_path && class_path.include?('controllers')
191
190
  @custom_class_info[current_class.to_s]['model'] = true if class_path && class_path.include?('models')
192
191
  end
192
+
193
+ # Monkey patch the single ruby module file.
194
+ #
195
+ # tracked_func - trackedFunction variable in stackify.json
196
+ # current_module - module variable in stackify.json
197
+ # class_path - file_path variable in stackify.json
198
+ # options - other options such as trackedFunctionName
199
+ def self.patched_module(tracked_func, current_module, class_path, **options)
200
+ current_method = options[:current_method] || nil
201
+ tracked_function_name = options[:tracked_function_name] || nil
202
+ transaction = options[:is_transaction] || nil
203
+ mod_spy = "#{current_module}Spy"
204
+
205
+ unless @custom_instrumented[current_module.to_s]
206
+ @custom_instrumented[current_module.to_s] = {}
207
+ end
208
+
209
+ unless @custom_instrumented[current_module.to_s][current_method.to_s]
210
+ @custom_instrumented[current_module.to_s][current_method.to_s] = false
211
+ end
212
+
213
+ unless @custom_class_info[current_module.to_s]
214
+ @custom_class_info[current_module.to_s] = {}
215
+ end
216
+
217
+ unless @custom_class_info[current_module.to_s]['controller']
218
+ @custom_class_info[current_module.to_s]['controller'] = false
219
+ end
220
+
221
+ unless @custom_class_info[current_module.to_s]['model']
222
+ @custom_class_info[current_module.to_s]['model'] = false
223
+ end
224
+
225
+ return unless @custom_instrumented[current_module.to_s][current_method.to_s] == false
226
+
227
+ eval <<-RUBY
228
+ module #{mod_spy}
229
+ def self.install
230
+ #{current_module}.class_eval do
231
+ class<< self
232
+ alias_method "#{current_method}_without_apm", "#{current_method}"
233
+
234
+ def #{current_method}(*args, &block)
235
+ if StackifyRubyAPM.current_transaction.nil? && #{!transaction.nil?}
236
+ t = StackifyRubyAPM.transaction("custom.#{current_module}.#{current_method}", TRACETYPE)
237
+ begin
238
+ req = #{current_method}_without_apm(*args, &block)
239
+ rescue Exception => e
240
+ StackifyRubyAPM.report(e)
241
+ raise e
242
+ ensure
243
+ t.submit
244
+ end
245
+ return req
246
+ elsif StackifyRubyAPM.current_transaction
247
+ name = "Custom Instrument"
248
+ type = "#{current_module}##{current_method}"
249
+ ctx = if "#{tracked_func}" == 'true'
250
+ Span::Context.new(
251
+ CATEGORY: 'Ruby',
252
+ TRACKED_FUNC: "#{tracked_function_name}"
253
+ )
254
+ else
255
+ Span::Context.new(
256
+ CATEGORY: 'Ruby'
257
+ )
258
+ end
259
+
260
+ StackifyRubyAPM.span name, type, context: ctx do
261
+ #{current_method}_without_apm(*args, &block)
262
+ end
263
+ else
264
+ return #{current_method}_without_apm(*args, &block)
265
+ end
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end
271
+
272
+ StackifyRubyAPM::Spies.register current_module.to_s, class_path.to_s, #{mod_spy}, true, "#{current_method}", "#{tracked_func}", "#{tracked_function_name}"
273
+ RUBY
274
+ @custom_instrumented[current_module.to_s][current_method.to_s] = true
275
+ @custom_class_info[current_module.to_s]['controller'] = true if class_path && class_path.include?('controllers')
276
+ @custom_class_info[current_module.to_s]['model'] = true if class_path && class_path.include?('models')
277
+ end
278
+
193
279
  # rubocop:enable Metrics/CyclomaticComplexity
194
280
  # rubocop:enable Metrics/PerceivedComplexity
195
281
  # rubocop:enable Lint/UselessAssignment
@@ -29,7 +29,11 @@ module StackifyRubyAPM
29
29
  temp_filename = logdev.gsub('-1.log', '')
30
30
  new_logdev = temp_filename + '-1.log'
31
31
  end
32
- @logdev = LogDevice.new(new_logdev, shift_size: shift_size)
32
+ begin
33
+ @logdev = LogDevice.new(new_logdev, shift_size: shift_size)
34
+ rescue StandardError => e
35
+ puts "Stackify Profiler unable to access [" + logdev + "]"
36
+ end
33
37
  end
34
38
  end
35
39
  require 'stackify_apm/logger/log_device'
@@ -20,7 +20,11 @@ module StackifyRubyAPM
20
20
  temp_filename = logdev.gsub('-1.log', '')
21
21
  new_logdev = temp_filename + '-1.log'
22
22
  end
23
- @logdev = LogDevice.new(new_logdev, shift_size: shift_size)
23
+ begin
24
+ @logdev = LogDevice.new(new_logdev, shift_size: shift_size)
25
+ rescue StandardError => e
26
+ puts "Stackify Profiler unable to access [" + logdev + "]"
27
+ end
24
28
  end
25
29
  end
26
30
 
@@ -39,7 +39,8 @@ module StackifyRubyAPM
39
39
  # rubocop:disable Metrics/PerceivedComplexity
40
40
  def call(env)
41
41
  begin
42
- transaction = build_transaction(env) if running?
42
+ context = StackifyRubyAPM.build_context(env)
43
+ transaction = build_transaction(env, context) if running?
43
44
  resp = @app.call env
44
45
 
45
46
  @rack_status = resp[0].to_i
@@ -47,6 +48,8 @@ module StackifyRubyAPM
47
48
  @rack_body = resp[2]
48
49
  @configuration = config
49
50
 
51
+ build_prefix_context(transaction, context)
52
+
50
53
  if okay_to_modify?
51
54
  @configuration.already_instrumented_flag = true
52
55
  if @configuration.rum_enabled.is_a?(TrueClass)
@@ -92,8 +95,9 @@ module StackifyRubyAPM
92
95
 
93
96
  # Start of transaction building with params: name, type, context
94
97
  #
95
- def build_transaction(env)
96
- StackifyRubyAPM.transaction 'Rack', 'WEBAPP', context: StackifyRubyAPM.build_context(env)
98
+ def build_transaction(env, context=nil)
99
+ context = context || StackifyRubyAPM.build_context(env)
100
+ StackifyRubyAPM.transaction 'Rack', 'WEBAPP', context: context
97
101
  end
98
102
 
99
103
  def running?
@@ -129,5 +133,19 @@ module StackifyRubyAPM
129
133
  def xhr?
130
134
  @rack_headers['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'
131
135
  end
136
+
137
+ # Add prefix instrument data to transaction context if enabled
138
+ def build_prefix_context(transaction, context)
139
+ return nil unless @configuration.prefix_enabled.is_a?(TrueClass)
140
+
141
+ source = nil
142
+ body = @rack_body
143
+ body.each { |fragment| source ? (source << fragment.to_s) : (source = fragment.to_s) }
144
+
145
+ transaction.context.prefix.request_body = context && context.request && context.request.body || ""
146
+ transaction.context.prefix.request_headers = context && context.request && context.request.headers || Hash.new
147
+ transaction.context.prefix.response_body = source || ""
148
+ transaction.context.prefix.response_headers = @rack_headers || Hash.new
149
+ end
132
150
  end
133
151
  end