newrelic_rpm 3.9.4.245 → 3.9.5.251

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +57 -0
  3. data/Guardfile +1 -0
  4. data/lib/new_relic/agent/agent.rb +3 -3
  5. data/lib/new_relic/agent/audit_logger.rb +5 -2
  6. data/lib/new_relic/agent/configuration/default_source.rb +11 -5
  7. data/lib/new_relic/agent/error_collector.rb +14 -1
  8. data/lib/new_relic/agent/hostname.rb +22 -1
  9. data/lib/new_relic/agent/instrumentation/middleware_tracing.rb +8 -2
  10. data/lib/new_relic/agent/instrumentation/queue_time.rb +9 -6
  11. data/lib/new_relic/agent/method_tracer.rb +51 -172
  12. data/lib/new_relic/agent/method_tracer_helpers.rb +90 -0
  13. data/lib/new_relic/agent/new_relic_service.rb +33 -11
  14. data/lib/new_relic/agent/new_relic_service/encoders.rb +9 -5
  15. data/lib/new_relic/agent/request_sampler.rb +20 -12
  16. data/lib/new_relic/agent/rules_engine.rb +31 -78
  17. data/lib/new_relic/agent/rules_engine/replacement_rule.rb +76 -0
  18. data/lib/new_relic/agent/rules_engine/segment_terms_rule.rb +48 -0
  19. data/lib/new_relic/agent/samplers/cpu_sampler.rb +1 -1
  20. data/lib/new_relic/agent/sql_sampler.rb +39 -10
  21. data/lib/new_relic/agent/stats_engine/metric_stats.rb +1 -0
  22. data/lib/new_relic/agent/stats_engine/stats_hash.rb +7 -13
  23. data/lib/new_relic/agent/system_info.rb +96 -10
  24. data/lib/new_relic/agent/threading/agent_thread.rb +4 -1
  25. data/lib/new_relic/agent/threading/backtrace_node.rb +67 -57
  26. data/lib/new_relic/agent/threading/thread_profile.rb +30 -15
  27. data/lib/new_relic/agent/transaction.rb +11 -4
  28. data/lib/new_relic/environment_report.rb +21 -20
  29. data/lib/new_relic/version.rb +1 -1
  30. data/test/agent_helper.rb +12 -0
  31. data/test/fixtures/cross_agent_tests/README.md +1 -0
  32. data/test/fixtures/cross_agent_tests/proc_cpuinfo/1pack_1core_1logical.txt +3 -0
  33. data/test/fixtures/cross_agent_tests/proc_cpuinfo/1pack_1core_2logical.txt +14 -0
  34. data/test/fixtures/cross_agent_tests/proc_cpuinfo/1pack_2core_2logical.txt +14 -0
  35. data/test/fixtures/cross_agent_tests/proc_cpuinfo/1pack_4core_4logical.txt +28 -0
  36. data/test/fixtures/{proc_cpuinfo.txt → cross_agent_tests/proc_cpuinfo/2pack_12core_24logical.txt} +0 -0
  37. data/test/fixtures/cross_agent_tests/proc_cpuinfo/2pack_20core_40logical.txt +999 -0
  38. data/test/fixtures/cross_agent_tests/proc_cpuinfo/2pack_2core_2logical.txt +51 -0
  39. data/test/fixtures/cross_agent_tests/proc_cpuinfo/2pack_2core_4logical.txt +28 -0
  40. data/test/fixtures/cross_agent_tests/proc_cpuinfo/2pack_4core_4logical.txt +28 -0
  41. data/test/fixtures/cross_agent_tests/proc_cpuinfo/4pack_4core_4logical.txt +103 -0
  42. data/test/fixtures/cross_agent_tests/proc_cpuinfo/8pack_8core_8logical.txt +199 -0
  43. data/test/fixtures/cross_agent_tests/proc_cpuinfo/README.md +24 -0
  44. data/test/fixtures/cross_agent_tests/proc_cpuinfo/Xpack_Xcore_2logical.txt +43 -0
  45. data/test/fixtures/cross_agent_tests/transaction_segment_terms.json +101 -0
  46. data/test/multiverse/lib/multiverse/suite.rb +1 -1
  47. data/test/multiverse/suites/agent_only/agent_run_id_handling_test.rb +40 -0
  48. data/test/multiverse/suites/agent_only/labels_test.rb +9 -14
  49. data/test/multiverse/suites/agent_only/marshaling_test.rb +4 -6
  50. data/test/multiverse/suites/agent_only/rename_rule_test.rb +41 -4
  51. data/test/multiverse/suites/agent_only/set_transaction_name_test.rb +11 -3
  52. data/test/multiverse/suites/config_file_loading/config_file_loading_test.rb +8 -8
  53. data/test/multiverse/suites/rack/example_app.rb +20 -0
  54. data/test/multiverse/suites/rack/http_response_code_test.rb +51 -0
  55. data/test/multiverse/suites/sidekiq/Envfile +13 -6
  56. data/test/multiverse/suites/sidekiq/sidekiq_server.rb +4 -3
  57. data/test/new_relic/agent/audit_logger_test.rb +27 -0
  58. data/test/new_relic/agent/error_collector_test.rb +26 -5
  59. data/test/new_relic/agent/hostname_test.rb +66 -14
  60. data/test/new_relic/agent/instrumentation/action_controller_subscriber_test.rb +8 -12
  61. data/test/new_relic/agent/method_tracer/instance_methods/trace_execution_scoped_test.rb +7 -45
  62. data/test/new_relic/agent/method_tracer_test.rb +52 -1
  63. data/test/new_relic/agent/new_relic_service_test.rb +76 -0
  64. data/test/new_relic/agent/request_sampler_test.rb +7 -0
  65. data/test/new_relic/agent/rules_engine_test.rb +87 -56
  66. data/test/new_relic/agent/sql_sampler_test.rb +50 -14
  67. data/test/new_relic/agent/stats_engine/metric_stats_test.rb +2 -2
  68. data/test/new_relic/agent/stats_engine/samplers_test.rb +1 -1
  69. data/test/new_relic/agent/{stats_hash_test.rb → stats_engine/stats_hash_test.rb} +1 -38
  70. data/test/new_relic/agent/system_info_test.rb +45 -0
  71. data/test/new_relic/agent/threading/agent_thread_test.rb +30 -0
  72. data/test/new_relic/agent/threading/backtrace_node_test.rb +27 -44
  73. data/test/new_relic/agent/threading/thread_profile_test.rb +35 -14
  74. data/test/new_relic/agent/transaction_test.rb +13 -10
  75. data/test/new_relic/environment_report_test.rb +7 -6
  76. data/test/new_relic/fake_collector.rb +10 -6
  77. data/test/new_relic/multiverse_helpers.rb +4 -11
  78. data/test/new_relic/rack/agent_hooks_test.rb +1 -1
  79. data/test/performance/lib/performance/baseline_compare_reporter.rb +24 -7
  80. data/test/performance/lib/performance/result.rb +3 -1
  81. data/test/performance/lib/performance/runner.rb +10 -0
  82. data/test/performance/lib/performance/timer.rb +6 -10
  83. data/test/performance/script/runner +18 -1
  84. data/test/performance/suites/queue_time.rb +21 -0
  85. data/test/performance/suites/stats_hash.rb +34 -0
  86. data/test/performance/suites/thread_profiling.rb +26 -0
  87. metadata +25 -4
  88. metadata.gz.sig +0 -0
@@ -0,0 +1,90 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+
5
+ module NewRelic
6
+ module Agent
7
+ module MethodTracerHelpers
8
+ extend self
9
+
10
+ # helper for logging errors to the newrelic_agent.log
11
+ # properly. Logs the error at error level
12
+ def log_errors(code_area)
13
+ yield
14
+ rescue => e
15
+ ::NewRelic::Agent.logger.error("Caught exception in #{code_area}.", e)
16
+ end
17
+
18
+ def trace_execution_scoped_header(state, t0)
19
+ log_errors(:trace_execution_scoped_header) do
20
+ stack = state.traced_method_stack
21
+ stack.push_frame(state, :method_tracer, t0)
22
+ end
23
+ end
24
+
25
+ def record_metrics(state, first_name, other_names, duration, exclusive, options)
26
+ record_scoped_metric = options.has_key?(:scoped_metric) ? options[:scoped_metric] : true
27
+ stat_engine = NewRelic::Agent.instance.stats_engine
28
+ if record_scoped_metric
29
+ stat_engine.record_scoped_and_unscoped_metrics(state, first_name, other_names, duration, exclusive)
30
+ else
31
+ metrics = [first_name].concat(other_names)
32
+ stat_engine.record_unscoped_metrics(state, metrics, duration, exclusive)
33
+ end
34
+ end
35
+
36
+ def trace_execution_scoped_footer(state, t0, first_name, metric_names, expected_frame, options, t1=Time.now.to_f)
37
+ log_errors(:trace_method_execution_footer) do
38
+ if expected_frame
39
+ stack = state.traced_method_stack
40
+ create_metrics = options.has_key?(:metric) ? options[:metric] : true
41
+ frame = stack.pop_frame(state, expected_frame, first_name, t1, create_metrics)
42
+ if create_metrics
43
+ duration = t1 - t0
44
+ exclusive = duration - frame.children_time
45
+
46
+ if duration < 1_000_000_000 # roughly 31 years
47
+ if duration < 0
48
+ ::NewRelic::Agent.logger.log_once(:warn, "metric_duration_negative:#{first_name}",
49
+ "Metric #{first_name} has negative duration: #{duration} s")
50
+ end
51
+
52
+ if exclusive < 0
53
+ ::NewRelic::Agent.logger.log_once(:warn, "metric_exclusive_negative:#{first_name}",
54
+ "Metric #{first_name} has negative exclusive time: duration = #{duration} s, child_time = #{frame.children_time}")
55
+ end
56
+
57
+ record_metrics(state, first_name, metric_names, duration, exclusive, options)
58
+ else
59
+ ::NewRelic::Agent.logger.log_once(:warn, "too_huge_metric:#{first_name}",
60
+ "Ignoring metric #{first_name} with unacceptably large duration: #{duration} s")
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ def trace_execution_scoped(metric_names, options={}) #THREAD_LOCAL_ACCESS
68
+ state = NewRelic::Agent::TransactionState.tl_get
69
+ return yield unless state.is_execution_traced?
70
+
71
+ metric_names = Array(metric_names)
72
+ first_name = metric_names.shift
73
+ return yield unless first_name
74
+
75
+ additional_metrics_callback = options[:additional_metrics_callback]
76
+ start_time = Time.now.to_f
77
+ expected_scope = trace_execution_scoped_header(state, start_time)
78
+
79
+ begin
80
+ result = yield
81
+ metric_names += Array(additional_metrics_callback.call) if additional_metrics_callback
82
+ result
83
+ ensure
84
+ trace_execution_scoped_footer(state, start_time, first_name, metric_names, expected_scope, options)
85
+ end
86
+ end
87
+
88
+ end
89
+ end
90
+ end
@@ -124,22 +124,26 @@ module NewRelic
124
124
  metric_data_array = build_metric_data_array(stats_hash)
125
125
  result = invoke_remote(
126
126
  :metric_data,
127
- [@agent_id, timeslice_start.to_f, timeslice_end.to_f, metric_data_array]
127
+ [@agent_id, timeslice_start.to_f, timeslice_end.to_f, metric_data_array],
128
+ :item_count => metric_data_array.size
128
129
  )
129
130
  fill_metric_id_cache(result)
130
131
  result
131
132
  end
132
133
 
133
134
  def error_data(unsent_errors)
134
- invoke_remote(:error_data, [@agent_id, unsent_errors])
135
+ invoke_remote(:error_data, [@agent_id, unsent_errors],
136
+ :item_count => unsent_errors.size)
135
137
  end
136
138
 
137
139
  def transaction_sample_data(traces)
138
- invoke_remote(:transaction_sample_data, [@agent_id, traces])
140
+ invoke_remote(:transaction_sample_data, [@agent_id, traces],
141
+ :item_count => traces.size)
139
142
  end
140
143
 
141
144
  def sql_trace_data(sql_traces)
142
- invoke_remote(:sql_trace_data, [sql_traces])
145
+ invoke_remote(:sql_trace_data, [sql_traces],
146
+ :item_count => sql_traces.size)
143
147
  end
144
148
 
145
149
  def profile_data(profile)
@@ -160,7 +164,8 @@ module NewRelic
160
164
 
161
165
  # Send fine-grained analytic data to the collector.
162
166
  def analytic_event_data(data)
163
- invoke_remote(:analytic_event_data, [@agent_id, data])
167
+ invoke_remote(:analytic_event_data, [@agent_id, data],
168
+ :item_count => data.size)
164
169
  end
165
170
 
166
171
  # We do not compress if content is smaller than 64kb. There are
@@ -332,7 +337,10 @@ module NewRelic
332
337
  :collector => @collector)
333
338
  @marshaller.load(decompress_response(response))
334
339
  ensure
335
- record_supportability_metrics(method, start_ts, serialize_finish_ts, size)
340
+ record_timing_supportability_metrics(method, start_ts, serialize_finish_ts)
341
+ if size
342
+ record_size_supportability_metrics(method, size, options[:item_count])
343
+ end
336
344
  end
337
345
 
338
346
  def handle_serialization_error(method, e)
@@ -344,7 +352,7 @@ module NewRelic
344
352
  raise error
345
353
  end
346
354
 
347
- def record_supportability_metrics(method, start_ts, serialize_finish_ts, size)
355
+ def record_timing_supportability_metrics(method, start_ts, serialize_finish_ts)
348
356
  serialize_time = serialize_finish_ts && (serialize_finish_ts - start_ts)
349
357
  duration = (Time.now - start_ts).to_f
350
358
  NewRelic::Agent.record_metric("Supportability/invoke_remote", duration)
@@ -353,10 +361,24 @@ module NewRelic
353
361
  NewRelic::Agent.record_metric("Supportability/invoke_remote_serialize", serialize_time)
354
362
  NewRelic::Agent.record_metric("Supportability/invoke_remote_serialize/#{method.to_s}", serialize_time)
355
363
  end
356
- if size
357
- NewRelic::Agent.record_metric("Supportability/invoke_remote_size", size)
358
- NewRelic::Agent.record_metric("Supportability/invoke_remote_size/#{method.to_s}", size)
359
- end
364
+ end
365
+
366
+ # For these metrics, we use the following fields:
367
+ # call_count => number of times this remote method was invoked
368
+ # total_call_time => total size in bytes of payloads across all invocations
369
+ # total_exclusive_time => total size in items (e.g. unique metrics, traces, events, etc) across all invocations
370
+ #
371
+ # The last field doesn't make sense for all methods (e.g. get_agent_commands),
372
+ # so we omit it for those methods that don't really take collections
373
+ # of items as arguments.
374
+ def record_size_supportability_metrics(method, size_bytes, item_count)
375
+ metrics = [
376
+ "Supportability/invoke_remote_size",
377
+ "Supportability/invoke_remote_size/#{method.to_s}"
378
+ ]
379
+ # we may not have an item count, in which case, just record 0 for the exclusive time
380
+ item_count ||= 0
381
+ NewRelic::Agent.agent.stats_engine.tl_record_unscoped_metrics(metrics, size_bytes, item_count)
360
382
  end
361
383
 
362
384
  # Raises an UnrecoverableServerException if the post_string is longer
@@ -10,21 +10,25 @@ module NewRelic
10
10
  class NewRelicService
11
11
  module Encoders
12
12
  module Identity
13
- def self.encode(data)
13
+ def self.encode(data, opts=nil)
14
14
  data
15
15
  end
16
16
  end
17
17
 
18
18
  module Compressed
19
- def self.encode(data)
19
+ def self.encode(data, opts=nil)
20
20
  Zlib::Deflate.deflate(data, Zlib::DEFAULT_COMPRESSION)
21
21
  end
22
22
  end
23
23
 
24
24
  module Base64CompressedJSON
25
- def self.encode(data)
26
- json = ::NewRelic::JSONWrapper.dump(data,
27
- :normalize => Agent.config[:normalize_json_string_encodings])
25
+ def self.encode(data, opts={})
26
+ normalize_encodings = if opts[:skip_normalization]
27
+ false
28
+ else
29
+ Agent.config[:normalize_json_string_encodings]
30
+ end
31
+ json = ::NewRelic::JSONWrapper.dump(data, :normalize => normalize_encodings)
28
32
  Base64.encode64(Compressed.encode(json))
29
33
  end
30
34
  end
@@ -13,20 +13,21 @@ class NewRelic::Agent::RequestSampler
13
13
  MonitorMixin
14
14
 
15
15
  # The type field of the sample
16
- SAMPLE_TYPE = 'Transaction'
16
+ SAMPLE_TYPE = 'Transaction'.freeze
17
17
 
18
18
  # Strings for static keys of the sample structure
19
- TYPE_KEY = 'type'
20
- TIMESTAMP_KEY = 'timestamp'
21
- NAME_KEY = 'name'
22
- DURATION_KEY = 'duration'
23
- GUID_KEY = 'nr.guid'
24
- REFERRING_TRANSACTION_GUID_KEY = 'nr.referringTransactionGuid'
25
- CAT_TRIP_ID_KEY = 'nr.tripId'
26
- CAT_PATH_HASH_KEY = 'nr.pathHash'
27
- CAT_REFERRING_PATH_HASH_KEY = 'nr.referringPathHash'
28
- CAT_ALTERNATE_PATH_HASHES_KEY = 'nr.alternatePathHashes'
29
- APDEX_PERF_ZONE_KEY = 'nr.apdexPerfZone'
19
+ TYPE_KEY = 'type'.freeze
20
+ TIMESTAMP_KEY = 'timestamp'.freeze
21
+ NAME_KEY = 'name'.freeze
22
+ DURATION_KEY = 'duration'.freeze
23
+ HTTP_RESPONSE_CODE_KEY = 'httpResponseCode'.freeze
24
+ GUID_KEY = 'nr.guid'.freeze
25
+ REFERRING_TRANSACTION_GUID_KEY = 'nr.referringTransactionGuid'.freeze
26
+ CAT_TRIP_ID_KEY = 'nr.tripId'.freeze
27
+ CAT_PATH_HASH_KEY = 'nr.pathHash'.freeze
28
+ CAT_REFERRING_PATH_HASH_KEY = 'nr.referringPathHash'.freeze
29
+ CAT_ALTERNATE_PATH_HASHES_KEY = 'nr.alternatePathHashes'.freeze
30
+ APDEX_PERF_ZONE_KEY = 'nr.apdexPerfZone'.freeze
30
31
 
31
32
  def initialize( event_listener )
32
33
  super()
@@ -185,10 +186,17 @@ class NewRelic::Agent::RequestSampler
185
186
  optionally_append(CAT_PATH_HASH_KEY, :cat_path_hash, sample, payload)
186
187
  optionally_append(CAT_REFERRING_PATH_HASH_KEY, :cat_referring_path_hash, sample, payload)
187
188
  optionally_append(APDEX_PERF_ZONE_KEY, :apdex_perf_zone, sample, payload)
189
+ append_http_response_code(sample, payload)
188
190
  append_cat_alternate_path_hashes(sample, payload)
189
191
  sample
190
192
  end
191
193
 
194
+ def append_http_response_code(sample, payload)
195
+ unless NewRelic::Agent.config[:disable_middleware_instrumentation]
196
+ optionally_append(HTTP_RESPONSE_CODE_KEY, :http_response_code, sample, payload)
197
+ end
198
+ end
199
+
192
200
  def append_cat_alternate_path_hashes(sample, payload)
193
201
  if payload.include?(:cat_alternate_path_hashes)
194
202
  sample[CAT_ALTERNATE_PATH_HASHES_KEY] = payload[:cat_alternate_path_hashes].sort.join(',')
@@ -2,103 +2,56 @@
2
2
  # This file is distributed under New Relic's license terms.
3
3
  # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
4
 
5
+ require 'new_relic/agent/rules_engine/replacement_rule'
6
+ require 'new_relic/agent/rules_engine/segment_terms_rule'
7
+
5
8
  module NewRelic
6
9
  module Agent
7
10
  class RulesEngine
11
+ SEGMENT_SEPARATOR = '/'.freeze
12
+ LEADING_SLASH_REGEX = %r{^/}.freeze
13
+
8
14
  include Enumerable
9
15
  extend Forwardable
10
16
 
11
17
  def_delegators :@rules, :size, :inspect, :each, :clear
12
18
 
13
- def self.from_specs(specs)
14
- rules = (specs || []).map { |spec| Rule.new(spec) }
19
+ def self.create_metric_rules(connect_response)
20
+ specs = connect_response['metric_name_rules'] || []
21
+ rules = specs.map { |spec| ReplacementRule.new(spec) }
15
22
  self.new(rules)
16
23
  end
17
24
 
18
- def initialize(rules=[])
19
- @rules = rules.sort
20
- end
21
-
22
- def << rule
23
- @rules << rule
24
- @rules.sort!
25
- @rules
26
- end
27
-
28
- def rename(original_string)
29
- @rules.inject(original_string) do |string,rule|
30
- if rule.each_segment
31
- segments = string.split('/')
32
-
33
- # If string looks like '/foo/bar', we want to
34
- # ignore the empty string preceding the first slash.
35
- add_preceding_slash = false
36
- if segments[0] == ''
37
- segments.shift
38
- add_preceding_slash = true
39
- end
25
+ def self.create_transaction_rules(connect_response)
26
+ txn_name_specs = connect_response['transaction_name_rules'] || []
27
+ segment_rule_specs = connect_response['transaction_segment_terms'] || []
40
28
 
41
- result, matched = rule.map_to_list(segments)
42
- result = result.join('/') if !result.nil?
43
- result = '/'+result if add_preceding_slash
44
- else
45
- result, matched = rule.apply(string)
46
- end
29
+ txn_name_rules = txn_name_specs.map { |s| ReplacementRule.new(s) }
30
+ segment_rules = segment_rule_specs.map { |s| SegmentTermsRule.new(s) }
47
31
 
48
- break result if (matched && rule.terminate_chain) || result.nil?
49
- result
50
- end
32
+ self.new(txn_name_rules, segment_rules)
51
33
  end
52
34
 
53
- class Rule
54
- attr_reader(:terminate_chain, :each_segment, :ignore, :replace_all, :eval_order,
55
- :match_expression, :replacement)
56
-
57
- def initialize(options)
58
- if !options['match_expression']
59
- raise ArgumentError.new('missing required match_expression')
60
- end
61
- if !options['replacement'] && !options['ignore']
62
- raise ArgumentError.new('must specify replacement when ignore is false')
63
- end
64
-
65
- @match_expression = Regexp.new(options['match_expression'], Regexp::IGNORECASE)
66
- @replacement = options['replacement']
67
- @ignore = options['ignore'] || false
68
- @eval_order = options['eval_order'] || 0
69
- @replace_all = options['replace_all'] || false
70
- @each_segment = options['each_segment'] || false
71
- @terminate_chain = options['terminate_chain'] || false
72
- end
73
-
74
- def apply(string)
75
- if @ignore
76
- if string.match @match_expression
77
- [nil, true]
78
- else
79
- [string, false]
80
- end
81
- else
82
- method = @replace_all ? :gsub : :sub
83
- result = string.send(method, @match_expression, @replacement)
84
- match_found = ($~ != nil)
85
- [result, match_found]
86
- end
87
- end
35
+ def initialize(rules=[], segment_term_rules=[])
36
+ @rules = rules.sort
37
+ @segment_term_rules = segment_term_rules
38
+ end
88
39
 
89
- def map_to_list(list)
90
- matched = false
91
- result = list.map do |string|
92
- str_result, str_match = apply(string)
93
- matched ||= str_match
94
- str_result
40
+ def apply_rules(rules, string)
41
+ rules.each do |rule|
42
+ if rule.matches?(string)
43
+ string = rule.apply(string)
44
+ break if rule.terminal?
95
45
  end
96
- [result, matched]
97
46
  end
47
+ string
48
+ end
98
49
 
99
- def <=>(other)
100
- eval_order <=> other.eval_order
101
- end
50
+ def rename(original_string)
51
+ renamed = apply_rules(@rules, original_string)
52
+ return nil unless renamed
53
+ renamed = apply_rules(@segment_term_rules, renamed)
54
+ renamed
102
55
  end
103
56
  end
104
57
  end
@@ -0,0 +1,76 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+
5
+ module NewRelic
6
+ module Agent
7
+ class RulesEngine
8
+ class ReplacementRule
9
+ attr_reader(:terminate_chain, :each_segment, :ignore, :replace_all, :eval_order,
10
+ :match_expression, :replacement)
11
+
12
+ def initialize(options)
13
+ if !options['match_expression']
14
+ raise ArgumentError.new('missing required match_expression')
15
+ end
16
+ if !options['replacement'] && !options['ignore']
17
+ raise ArgumentError.new('must specify replacement when ignore is false')
18
+ end
19
+
20
+ @match_expression = Regexp.new(options['match_expression'], Regexp::IGNORECASE)
21
+ @replacement = options['replacement']
22
+ @ignore = options['ignore'] || false
23
+ @eval_order = options['eval_order'] || 0
24
+ @replace_all = options['replace_all'] || false
25
+ @each_segment = options['each_segment'] || false
26
+ @terminate_chain = options['terminate_chain'] || false
27
+ end
28
+
29
+ def terminal?
30
+ @terminate_chain || @ignore
31
+ end
32
+
33
+ def matches?(string)
34
+ if @each_segment
35
+ string.split(SEGMENT_SEPARATOR).any? do |segment|
36
+ segment.match(@match_expression)
37
+ end
38
+ else
39
+ string.match @match_expression
40
+ end
41
+ end
42
+
43
+ def apply(string)
44
+ if @ignore
45
+ nil
46
+ elsif @each_segment
47
+ apply_to_each_segment(string)
48
+ else
49
+ apply_replacement(string)
50
+ end
51
+ end
52
+
53
+ def apply_replacement(string)
54
+ method = @replace_all ? :gsub : :sub
55
+ string.send(method, @match_expression, @replacement)
56
+ end
57
+
58
+ def apply_to_each_segment(string)
59
+ string = string.dup
60
+ leading_slash = string.slice!(LEADING_SLASH_REGEX)
61
+ segments = string.split(SEGMENT_SEPARATOR)
62
+
63
+ segments.map! do |segment|
64
+ apply_replacement(segment)
65
+ end
66
+
67
+ "#{leading_slash}#{segments.join(SEGMENT_SEPARATOR)}"
68
+ end
69
+
70
+ def <=>(other)
71
+ eval_order <=> other.eval_order
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end