newrelic_rpm 3.4.1 → 3.4.2.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of newrelic_rpm might be problematic. Click here for more details.

Files changed (81) hide show
  1. data/CHANGELOG +7 -0
  2. data/ReleaseNotes.md +638 -0
  3. data/lib/new_relic/agent/agent.rb +44 -56
  4. data/lib/new_relic/agent/beacon_configuration.rb +1 -1
  5. data/lib/new_relic/agent/browser_monitoring.rb +80 -42
  6. data/lib/new_relic/agent/configuration/defaults.rb +75 -0
  7. data/lib/new_relic/agent/configuration/environment_source.rb +42 -0
  8. data/lib/new_relic/agent/configuration/manager.rb +100 -0
  9. data/lib/new_relic/agent/configuration/server_source.rb +24 -0
  10. data/lib/new_relic/agent/configuration/yaml_source.rb +61 -0
  11. data/lib/new_relic/agent/configuration.rb +48 -0
  12. data/lib/new_relic/agent/error_collector.rb +10 -14
  13. data/lib/new_relic/agent/instrumentation/active_record.rb +1 -5
  14. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +20 -4
  15. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +1 -1
  16. data/lib/new_relic/agent/instrumentation/memcache.rb +2 -2
  17. data/lib/new_relic/agent/instrumentation/metric_frame.rb +1 -1
  18. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +3 -3
  19. data/lib/new_relic/agent/instrumentation/rails3/action_controller.rb +2 -2
  20. data/lib/new_relic/agent/instrumentation/resque.rb +1 -1
  21. data/lib/new_relic/agent/method_tracer.rb +1 -1
  22. data/lib/new_relic/agent/new_relic_service.rb +18 -9
  23. data/lib/new_relic/agent/pipe_channel_manager.rb +5 -6
  24. data/lib/new_relic/agent/samplers/delayed_job_sampler.rb +2 -5
  25. data/lib/new_relic/agent/sql_sampler.rb +11 -35
  26. data/lib/new_relic/agent/stats_engine/transactions.rb +2 -2
  27. data/lib/new_relic/agent/transaction_info.rb +24 -4
  28. data/lib/new_relic/agent/transaction_sample_builder.rb +2 -3
  29. data/lib/new_relic/agent/transaction_sampler.rb +9 -16
  30. data/lib/new_relic/agent.rb +4 -1
  31. data/lib/new_relic/collection_helper.rb +1 -1
  32. data/lib/new_relic/commands/deployments.rb +5 -3
  33. data/lib/new_relic/control/class_methods.rb +2 -3
  34. data/lib/new_relic/control/frameworks/rails.rb +8 -8
  35. data/lib/new_relic/control/frameworks/ruby.rb +2 -2
  36. data/lib/new_relic/control/instance_methods.rb +26 -32
  37. data/lib/new_relic/control/logging_methods.rb +5 -23
  38. data/lib/new_relic/control/server_methods.rb +11 -18
  39. data/lib/new_relic/control.rb +0 -1
  40. data/lib/new_relic/delayed_job_injection.rb +1 -1
  41. data/lib/new_relic/language_support.rb +8 -0
  42. data/lib/new_relic/noticed_error.rb +1 -1
  43. data/lib/new_relic/rack/browser_monitoring.rb +18 -6
  44. data/lib/new_relic/version.rb +2 -2
  45. data/newrelic.yml +0 -4
  46. data/newrelic_rpm.gemspec +16 -6
  47. data/test/config/newrelic.yml +3 -2
  48. data/test/new_relic/agent/agent/connect_test.rb +88 -83
  49. data/test/new_relic/agent/agent/start_test.rb +75 -80
  50. data/test/new_relic/agent/agent/start_worker_thread_test.rb +18 -18
  51. data/test/new_relic/agent/beacon_configuration_test.rb +13 -11
  52. data/test/new_relic/agent/browser_monitoring_test.rb +69 -14
  53. data/test/new_relic/agent/configuration/environment_source_test.rb +58 -0
  54. data/test/new_relic/agent/configuration/manager_test.rb +120 -0
  55. data/test/new_relic/agent/configuration/server_source_test.rb +28 -0
  56. data/test/new_relic/agent/configuration/yaml_source_test.rb +56 -0
  57. data/test/new_relic/agent/error_collector/notice_error_test.rb +63 -50
  58. data/test/new_relic/agent/error_collector_test.rb +10 -12
  59. data/test/new_relic/agent/new_relic_service_test.rb +11 -3
  60. data/test/new_relic/agent/pipe_channel_manager_test.rb +19 -16
  61. data/test/new_relic/agent/rpm_agent_test.rb +21 -19
  62. data/test/new_relic/agent/sql_sampler_test.rb +55 -56
  63. data/test/new_relic/agent/transaction_info_test.rb +45 -4
  64. data/test/new_relic/agent/transaction_sampler_test.rb +48 -44
  65. data/test/new_relic/agent_test.rb +68 -41
  66. data/test/new_relic/collection_helper_test.rb +7 -8
  67. data/test/new_relic/command/deployments_test.rb +12 -1
  68. data/test/new_relic/control/frameworks/rails_test.rb +26 -0
  69. data/test/new_relic/control/logging_methods_test.rb +77 -52
  70. data/test/new_relic/control_test.rb +103 -126
  71. data/test/new_relic/local_environment_test.rb +4 -6
  72. data/test/new_relic/rack/browser_monitoring_test.rb +4 -4
  73. data/test/new_relic/rack/developer_mode_test.rb +13 -7
  74. data/test/new_relic/transaction_sample_test.rb +8 -2
  75. data/test/script/build_test_gem.sh +9 -3
  76. data/test/script/ci.sh +13 -3
  77. data/test/test_helper.rb +9 -2
  78. data/ui/helpers/developer_mode_helper.rb +2 -7
  79. metadata +26 -11
  80. data/lib/new_relic/control/configuration.rb +0 -206
  81. data/test/new_relic/control/configuration_test.rb +0 -77
@@ -0,0 +1,75 @@
1
+ module NewRelic
2
+ module Agent
3
+ module Configuration
4
+ DEFAULTS = {
5
+ :config_path => File.join('config', 'newrelic.yml'),
6
+
7
+ :app_name => Proc.new { NewRelic::Control.instance.env },
8
+ :dispatcher => Proc.new { NewRelic::Control.instance.local_env.dispatcher },
9
+
10
+ :enabled => true,
11
+ :monitor_mode => Proc.new { self[:enabled] },
12
+ :agent_enabled => Proc.new do
13
+ self[:enabled] &&
14
+ (self[:developer_mode] || self[:monitor_mode] || self[:monitor_daemons]) &&
15
+ !!NewRelic::Control.instance.local_env.dispatcher
16
+ end,
17
+ :developer_mode => Proc.new { self[:developer] },
18
+ :developer => false,
19
+ :apdex_t => 0.5,
20
+ :monitor_daemons => false,
21
+ :multi_homed => false,
22
+ :high_security => false,
23
+
24
+ :host => 'collector.newrelic.com',
25
+ :api_host => 'rpm.newrelic.com',
26
+ :port => Proc.new { self[:ssl] ? 443 : 80 },
27
+ :api_port => Proc.new { self[:port] },
28
+ :ssl => false,
29
+ :verify_certificate => false,
30
+ :sync_startup => false,
31
+ :send_data_on_exit => true,
32
+ :post_size_limit => 2 * 1024 * 1024, # 2 megs
33
+ :timeout => 2 * 60, # 2 minutes
34
+ :force_send => false,
35
+ :send_environment_info => true,
36
+
37
+ :log_file_name => 'newrelic_agent.log',
38
+ :log_file_path => 'log/',
39
+ :log_level => 'info',
40
+
41
+ :disable_samplers => false,
42
+ :disable_resque => false,
43
+ :disable_dj => false,
44
+ :disable_view_instrumentation => false,
45
+ :disable_backtrace_cleanup => false,
46
+ :skip_ar_instrumentation => false,
47
+ :disable_activerecord_instrumentation => Proc.new { self[:skip_ar_instrumentation] },
48
+ :disable_memcache_instrumentation => false,
49
+ :disable_mobile_headers => true,
50
+
51
+ :capture_memcache_keys => false,
52
+ :textmate => false,
53
+
54
+ :'transaction_tracer.enabled' => true,
55
+ :'transaction_tracer.transaction_threshold' => Proc.new { self[:apdex_t] * 4 },
56
+ :'transaction_tracer.stack_trace_threshold' => 0.5,
57
+ :'transaction_tracer.explain_threshold' => 0.5,
58
+ :'transaction_tracer.explain_enabled' => true,
59
+ :'transaction_tracer.record_sql' => 'obfuscated',
60
+ :'transaction_tracer.limit_segments' => 4000,
61
+ :'transaction_tracer.random_sample' => false,
62
+
63
+ :'slow_sql.enabled' => Proc.new { self[:'transaction_tracer.enabled'] },
64
+ :'slow_sql.stack_trace_threshold' => Proc.new { self[:'transaction_tracer.stack_trace_threshold'] },
65
+ :'slow_sql.explain_threshold' => Proc.new { self[:'transaction_tracer.explain_threshold'] },
66
+ :'slow_sql.explain_enabled' => Proc.new { self[:'transaction_tracer.explain_enabled'] },
67
+ :'slow_sql.record_sql' => Proc.new { self[:'transaction_tracer.record_sql'] },
68
+
69
+ :'error_collector.enabled' => true,
70
+ :'error_collector.capture_source' => true,
71
+ :'error_collector.ignore_errors' => 'ActionController::RoutingError'
72
+ }.freeze
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,42 @@
1
+ module NewRelic
2
+ module Agent
3
+ module Configuration
4
+ class EnvironmentSource < DottedHash
5
+ def initialize
6
+ string_map = {
7
+ 'NRCONFIG' => :config_path,
8
+ 'NEW_RELIC_LICENSE_KEY' => :license_key,
9
+ 'NEWRELIC_LICENSE_KEY' => :license_key,
10
+ 'NEW_RELIC_APP_NAME' => :app_name,
11
+ 'NEWRELIC_APP_NAME' => :app_name,
12
+ 'NEW_RELIC_DISPATCHER' => :dispatcher,
13
+ 'NEWRELIC_DISPATCHER' => :dispatcher,
14
+ 'NEW_RELIC_FRAMEWORK' => :framework,
15
+ 'NEWRELIC_FRAMEWORK' => :framework
16
+ }.each do |key, val|
17
+ self[val] = ENV[key] if ENV[key]
18
+ end
19
+
20
+ boolean_map = {
21
+ 'NEWRELIC_ENABLE' => :agent_enabled
22
+ }.each do |key, val|
23
+ if ENV[key].to_s =~ /false|off|no/i
24
+ self[val] = false
25
+ elsif ENV[key] != nil
26
+ self[val] = true
27
+ end
28
+ end
29
+
30
+ if ENV['NEW_RELIC_LOG']
31
+ if ENV['NEW_RELIC_LOG'].upcase == 'STDOUT'
32
+ self[:log_file_path] = self[:log_file_name] = 'STDOUT'
33
+ else
34
+ self[:log_file_path] = File.dirname(ENV['NEW_RELIC_LOG'])
35
+ self[:log_file_name] = File.basename(ENV['NEW_RELIC_LOG'])
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,100 @@
1
+ require 'forwardable'
2
+ require 'new_relic/agent/configuration/defaults'
3
+ require 'new_relic/agent/configuration/yaml_source'
4
+ require 'new_relic/agent/configuration/server_source'
5
+ require 'new_relic/agent/configuration/environment_source'
6
+
7
+ module NewRelic
8
+ module Agent
9
+ module Configuration
10
+ class Manager
11
+ extend Forwardable
12
+ def_delegators :@cache, :[], :has_key?
13
+ attr_reader :config_stack # mainly for testing
14
+
15
+ def initialize
16
+ @config_stack = [ EnvironmentSource.new, DEFAULTS ]
17
+ @cache = Hash.new {|hash,key| hash[key] = self.fetch(key) }
18
+
19
+ # letting Control handle this for now
20
+ # yaml_config = YamlSource.new("#{NewRelic::Control.instance.root}/#{self['config_path']}",
21
+ # NewRelic::Control.instance.env)
22
+ # apply_config(yaml_config, 1) if yaml_config
23
+ end
24
+
25
+ def apply_config(source, level=0)
26
+ @config_stack.insert(level, source.freeze)
27
+ expire_cache
28
+ end
29
+
30
+ def remove_config(source=nil)
31
+ if block_given?
32
+ @config_stack.delete_if {|c| yield c }
33
+ else
34
+ @config_stack.delete(source)
35
+ end
36
+ expire_cache
37
+ end
38
+
39
+ def source(key)
40
+ @config_stack.each do |config|
41
+ if config.respond_to?(key.to_sym) || config.has_key?(key.to_sym)
42
+ return config
43
+ end
44
+ end
45
+ end
46
+
47
+ def fetch(key)
48
+ @config_stack.each do |config|
49
+ next unless config
50
+ accessor = key.to_sym
51
+ if config.respond_to?(accessor)
52
+ return config.send(accessor)
53
+ elsif config.has_key?(accessor)
54
+ if config[accessor].respond_to?(:call)
55
+ return instance_eval(&config[accessor])
56
+ else
57
+ return config[accessor]
58
+ end
59
+ end
60
+ end
61
+ nil
62
+ end
63
+
64
+ def flattened_config
65
+ @config_stack.reverse.inject({}) do |flat,layer|
66
+ thawed_layer = layer.dup
67
+ thawed_layer.each do |k,v|
68
+ begin
69
+ thawed_layer[k] = instance_eval(&v) if v.respond_to?(:call)
70
+ rescue => e
71
+ NewRelic::Control.instance.log.debug("#{e.class.name} : #{e.message} - when calling Proc for config key #{k}")
72
+ thawed_layer[k] = nil
73
+ end
74
+ thawed_layer.delete(:config)
75
+ end
76
+ flat.merge(thawed_layer)
77
+ end
78
+ end
79
+
80
+ def exclude_rails_config(hash, key)
81
+ if defined?(::Rails::Configuration) &&
82
+ hash[key].kind_of?(::Rails::Configuration)
83
+ hash.delete(key)
84
+ end
85
+ end
86
+
87
+ def app_names
88
+ case self[:app_name]
89
+ when Array then self[:app_name]
90
+ when String then self[:app_name].split(';')
91
+ end
92
+ end
93
+
94
+ def expire_cache
95
+ @cache = Hash.new {|hash,key| hash[key] = self.fetch(key) }
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,24 @@
1
+ module NewRelic
2
+ module Agent
3
+ module Configuration
4
+ class ServerSource < DottedHash
5
+ def initialize(hash)
6
+ string_map = [
7
+ ['collect_traces', 'transaction_tracer.enabled'],
8
+ ['collect_traces', 'slow_sql.enabled'],
9
+ ['collect_errors', 'error_collector.enabled']
10
+ ].each do |pair|
11
+ hash[pair[1]] = hash[pair[0]] if hash[pair[0]] != nil
12
+ end
13
+
14
+ if hash['transaction_tracer.transaction_threshold'] =~ /apdex_f/i
15
+ # when value is "apdex_f" remove the config and defer to default
16
+ hash.delete('transaction_tracer.transaction_threshold')
17
+ end
18
+
19
+ super
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,61 @@
1
+ require 'new_relic/agent/configuration'
2
+
3
+ module NewRelic
4
+ module Agent
5
+ module Configuration
6
+ class YamlSource < DottedHash
7
+ attr_accessor :file_path
8
+
9
+ def initialize(path, env)
10
+ config = {}
11
+ begin
12
+ @file_path = File.expand_path(path)
13
+ if !File.exists?(@file_path)
14
+ NewRelic::Control.instance.log.error("Unable to load configuration from #{path}")
15
+ return
16
+ end
17
+
18
+ file = File.read(@file_path)
19
+
20
+ # Next two are for populating the newrelic.yml via erb binding, necessary
21
+ # when using the default newrelic.yml file
22
+ generated_for_user = ''
23
+ license_key = ''
24
+
25
+ erb = ERB.new(file).result(binding)
26
+ config = merge!(YAML.load(erb)[env] || {})
27
+ rescue ScriptError, StandardError => e
28
+ NewRelic::Control.instance.log.warn("Unable to read configuration file: #{e}")
29
+ end
30
+
31
+ if config['transaction_tracer'] &&
32
+ config['transaction_tracer']['transaction_threshold'] =~ /apdex_f/i
33
+ # when value is "apdex_f" remove the config and defer to default
34
+ config['transaction_tracer'].delete('transaction_threshold')
35
+ end
36
+
37
+ booleanify_values(config, 'agent_enabled', 'enabled', 'monitor_daemons')
38
+
39
+ super(config)
40
+ end
41
+
42
+ protected
43
+
44
+ def booleanify_values(config, *keys)
45
+ # auto means defer ro default
46
+ keys.each do |option|
47
+ if config[option] == 'auto'
48
+ config.delete(option)
49
+ elsif !config[option].nil? && !is_boolean?(config[option])
50
+ config[option] = !!(config[option] =~ /yes|on|true/i)
51
+ end
52
+ end
53
+ end
54
+
55
+ def is_boolean?(value)
56
+ value == !!value
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,48 @@
1
+ require 'new_relic/agent/configuration/manager'
2
+
3
+ module NewRelic
4
+ module Agent
5
+ module Configuration
6
+ def self.manager
7
+ @@manager ||= Manager.new
8
+ end
9
+
10
+ # This can be mixed in with minimal impact to provide easy
11
+ # access to the config manager
12
+ module Instance
13
+ def config
14
+ Configuration.manager
15
+ end
16
+ end
17
+
18
+ class DottedHash < ::Hash
19
+ def initialize(hash)
20
+ self.merge!(dot_flattened(hash))
21
+ keys.each do |key|
22
+ self[(key.to_sym rescue key) || key] = delete(key)
23
+ end
24
+ end
25
+
26
+ def inspect
27
+ "#<#{self.class.name}:#{object_id} #{super}>"
28
+ end
29
+
30
+ protected
31
+ # turns {'a' => {'b' => 'c'}} into {'a.b' => 'c'}
32
+ def dot_flattened(nested_hash, names=[], result={})
33
+ nested_hash.each do |key, val|
34
+ next if val == nil
35
+ if val.respond_to?(:has_key?)
36
+ dot_flattened(val, names + [key], result)
37
+ else
38
+ result[(names + [key]).join('.')] = val
39
+ end
40
+ end
41
+ result
42
+ end
43
+ end
44
+
45
+ class ManualSource < DottedHash; end
46
+ end
47
+ end
48
+ end
@@ -25,23 +25,16 @@ module NewRelic
25
25
  # lookup of exception class names to ignore. Hash for fast access
26
26
  @ignore = {}
27
27
 
28
- config = NewRelic::Control.instance.fetch('error_collector', {})
28
+ @enabled = @config_enabled = Agent.config[:'error_collector.enabled']
29
+ @capture_source = Agent.config[:'error_collector.capture_source']
29
30
 
30
- @enabled = @config_enabled = config.fetch('enabled', true)
31
- @capture_source = config.fetch('capture_source', true)
32
-
33
- ignore_errors = config.fetch('ignore_errors', "")
31
+ ignore_errors = Agent.config[:'error_collector.ignore_errors']
34
32
  ignore_errors = ignore_errors.split(",") if ignore_errors.is_a? String
35
33
  ignore_errors.each { |error| error.strip! }
36
34
  ignore(ignore_errors)
37
35
  @lock = Mutex.new
38
36
  end
39
-
40
- # Helper method to get the NewRelic::Control.instance
41
- def control
42
- NewRelic::Control.instance
43
- end
44
-
37
+
45
38
  # Returns the error filter proc that is used to check if an
46
39
  # error should be reported. When given a block, resets the
47
40
  # filter to the provided block. The define_method() is used to
@@ -59,7 +52,10 @@ module NewRelic
59
52
  # errors is an array of Exception Class Names
60
53
  #
61
54
  def ignore(errors)
62
- errors.each { |error| @ignore[error] = true; log.debug("Ignoring errors of type '#{error}'") }
55
+ errors.each do |error|
56
+ @ignore[error] = true
57
+ log.debug("Ignoring errors of type '#{error}'")
58
+ end
63
59
  end
64
60
 
65
61
  # This module was extracted from the notice_error method - it is
@@ -118,7 +114,7 @@ module NewRelic
118
114
  {
119
115
  :request_uri => fetch_from_options(options, :uri, ''),
120
116
  :request_referer => fetch_from_options(options, :referer, ''),
121
- :rails_root => control.root
117
+ :rails_root => NewRelic::Control.instance.root
122
118
  }
123
119
  end
124
120
 
@@ -133,7 +129,7 @@ module NewRelic
133
129
  # returns nil
134
130
  def request_params_from_opts(options)
135
131
  value = options.delete(:request_params)
136
- if control.capture_params
132
+ if Agent.config[:capture_params]
137
133
  value
138
134
  else
139
135
  nil
@@ -100,11 +100,7 @@ DependencyDetection.defer do
100
100
  end
101
101
 
102
102
  depends_on do
103
- !NewRelic::Control.instance['skip_ar_instrumentation']
104
- end
105
-
106
- depends_on do
107
- !NewRelic::Control.instance['disable_activerecord_instrumentation']
103
+ !NewRelic::Agent.config[:disable_activerecord_instrumentation]
108
104
  end
109
105
 
110
106
  executes do
@@ -26,6 +26,7 @@ module NewRelic
26
26
  module ClassMethodsShim # :nodoc:
27
27
  def newrelic_ignore(*args); end
28
28
  def newrelic_ignore_apdex(*args); end
29
+ def newrelic_ignore_enduser(*args); end
29
30
  end
30
31
 
31
32
  module Shim # :nodoc:
@@ -51,6 +52,10 @@ module NewRelic
51
52
  newrelic_ignore_aspect('ignore_apdex', specifiers)
52
53
  end
53
54
 
55
+ def newrelic_ignore_enduser(specifiers={})
56
+ newrelic_ignore_aspect('ignore_enduser', specifiers)
57
+ end
58
+
54
59
  def newrelic_ignore_aspect(property, specifiers={}) # :nodoc:
55
60
  if specifiers.empty?
56
61
  self.newrelic_write_attr property, true
@@ -249,8 +254,9 @@ module NewRelic
249
254
  return perform_action_without_newrelic_trace(*args)
250
255
  end
251
256
  end
252
-
253
- return perform_action_with_newrelic_profile(args, &block) if NewRelic::Control.instance.profiling?
257
+
258
+ control = NewRelic::Control.instance
259
+ return perform_action_with_newrelic_profile(args, &block) if control.profiling?
254
260
 
255
261
  frame_data = _push_metric_frame(block_given? ? args : [])
256
262
  begin
@@ -258,11 +264,16 @@ module NewRelic
258
264
  frame_data.start_transaction
259
265
  begin
260
266
  NewRelic::Agent::BusyCalculator.dispatcher_start frame_data.start
261
- if block_given?
267
+ result = if block_given?
262
268
  yield
263
269
  else
264
270
  perform_action_without_newrelic_trace(*args)
265
271
  end
272
+ if defined?(request) && request && defined?(response) &&
273
+ response && !Agent.config[:disable_mobile_headers]
274
+ NewRelic::Agent::BrowserMonitoring.insert_mobile_response_header(request, response)
275
+ end
276
+ result
266
277
  rescue => e
267
278
  frame_data.notice_error(e)
268
279
  raise
@@ -273,8 +284,9 @@ module NewRelic
273
284
  # Look for a metric frame in the thread local and process it.
274
285
  # Clear the thread local when finished to ensure it only gets called once.
275
286
  frame_data.record_apdex unless ignore_apdex?
276
-
277
287
  frame_data.pop
288
+
289
+ NewRelic::Agent::TransactionInfo.get.ignore_end_user = true if ignore_enduser?
278
290
  end
279
291
  end
280
292
 
@@ -316,6 +328,10 @@ module NewRelic
316
328
  def ignore_apdex?
317
329
  _is_filtered?('ignore_apdex')
318
330
  end
331
+
332
+ def ignore_enduser?
333
+ _is_filtered?('ignore_enduser')
334
+ end
319
335
 
320
336
  private
321
337