newrelic_rpm 3.5.7.59 → 3.5.8.64.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. data.tar.gz.sig +3 -2
  2. data/CHANGELOG +34 -3
  3. data/LICENSE +23 -0
  4. data/lib/new_relic/agent.rb +50 -3
  5. data/lib/new_relic/agent/agent.rb +40 -60
  6. data/lib/new_relic/agent/configuration/defaults.rb +9 -3
  7. data/lib/new_relic/agent/configuration/server_source.rb +4 -0
  8. data/lib/new_relic/agent/cross_app_monitor.rb +230 -0
  9. data/lib/new_relic/agent/cross_app_tracing.rb +274 -0
  10. data/lib/new_relic/agent/database.rb +28 -10
  11. data/lib/new_relic/agent/error_collector.rb +5 -0
  12. data/lib/new_relic/agent/event_listener.rb +4 -0
  13. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +53 -34
  14. data/lib/new_relic/agent/instrumentation/metric_frame.rb +16 -3
  15. data/lib/new_relic/agent/instrumentation/net.rb +13 -11
  16. data/lib/new_relic/agent/instrumentation/resque.rb +10 -10
  17. data/lib/new_relic/agent/instrumentation/sinatra.rb +19 -9
  18. data/lib/new_relic/agent/new_relic_service.rb +63 -9
  19. data/lib/new_relic/agent/pipe_service.rb +8 -12
  20. data/lib/new_relic/agent/rules_engine.rb +72 -0
  21. data/lib/new_relic/agent/shim_agent.rb +0 -1
  22. data/lib/new_relic/agent/sql_sampler.rb +3 -2
  23. data/lib/new_relic/agent/stats.rb +149 -0
  24. data/lib/new_relic/agent/stats_engine.rb +9 -0
  25. data/lib/new_relic/agent/stats_engine/gc_profiler.rb +1 -24
  26. data/lib/new_relic/agent/stats_engine/metric_stats.rb +84 -185
  27. data/lib/new_relic/agent/stats_engine/stats_hash.rb +58 -0
  28. data/lib/new_relic/agent/stats_engine/transactions.rb +10 -2
  29. data/lib/new_relic/agent/transaction_info.rb +31 -6
  30. data/lib/new_relic/agent/transaction_sample_builder.rb +19 -8
  31. data/lib/new_relic/agent/transaction_sampler.rb +17 -10
  32. data/lib/new_relic/helper.rb +32 -0
  33. data/lib/new_relic/local_environment.rb +24 -32
  34. data/lib/new_relic/okjson.rb +599 -0
  35. data/lib/new_relic/transaction_sample.rb +2 -1
  36. data/lib/new_relic/transaction_sample/segment.rb +2 -1
  37. data/lib/new_relic/version.rb +1 -1
  38. data/newrelic.yml +27 -41
  39. data/test/multiverse/suites/agent_only/audit_log_test.rb +2 -4
  40. data/test/multiverse/suites/agent_only/config/newrelic.yml +1 -2
  41. data/test/multiverse/suites/agent_only/{cross_process_test.rb → cross_application_tracing_test.rb} +3 -3
  42. data/test/multiverse/suites/agent_only/key_transactions_test.rb +66 -0
  43. data/test/multiverse/suites/agent_only/marshaling_test.rb +9 -22
  44. data/test/multiverse/suites/agent_only/rename_rule_test.rb +57 -0
  45. data/test/multiverse/suites/agent_only/start_up_test.rb +1 -1
  46. data/test/multiverse/suites/agent_only/thread_profiling_test.rb +17 -6
  47. data/test/multiverse/suites/rails/error_tracing_test.rb +20 -8
  48. data/test/multiverse/suites/resque/instrumentation_test.rb +2 -2
  49. data/test/multiverse/suites/sinatra/Envfile +2 -0
  50. data/test/multiverse/suites/sinatra/config/newrelic.yml +1 -0
  51. data/test/multiverse/suites/sinatra/sinatra_metric_explosion_test.rb +5 -5
  52. data/test/multiverse/suites/sinatra/sinatra_test.rb +75 -4
  53. data/test/new_relic/agent/agent/connect_test.rb +45 -1
  54. data/test/new_relic/agent/agent/start_worker_thread_test.rb +0 -3
  55. data/test/new_relic/agent/agent_test.rb +20 -40
  56. data/test/new_relic/agent/agent_test_controller_test.rb +24 -19
  57. data/test/new_relic/agent/busy_calculator_test.rb +1 -1
  58. data/test/new_relic/agent/configuration/server_source_test.rb +8 -3
  59. data/test/new_relic/agent/cross_app_monitor_test.rb +237 -0
  60. data/test/new_relic/agent/database_test.rb +60 -16
  61. data/test/new_relic/agent/error_collector_test.rb +28 -4
  62. data/test/new_relic/agent/event_listener_test.rb +23 -2
  63. data/test/new_relic/agent/instrumentation/controller_instrumentation_test.rb +53 -0
  64. data/test/new_relic/agent/instrumentation/metric_frame_test.rb +95 -0
  65. data/test/new_relic/agent/instrumentation/net_instrumentation_test.rb +414 -59
  66. data/test/new_relic/agent/instrumentation/task_instrumentation_test.rb +2 -5
  67. data/test/new_relic/agent/method_tracer_test.rb +4 -2
  68. data/test/new_relic/agent/new_relic_service_test.rb +108 -6
  69. data/test/new_relic/agent/pipe_channel_manager_test.rb +1 -1
  70. data/test/new_relic/agent/pipe_service_test.rb +9 -9
  71. data/test/new_relic/agent/rpm_agent_test.rb +0 -11
  72. data/test/new_relic/agent/rules_engine_test.rb +82 -0
  73. data/test/new_relic/agent/shim_agent_test.rb +0 -4
  74. data/test/new_relic/agent/sql_sampler_test.rb +7 -0
  75. data/test/new_relic/agent/stats_engine/gc_profiler_test.rb +85 -0
  76. data/test/new_relic/agent/stats_engine/metric_stats_test.rb +110 -23
  77. data/test/new_relic/agent/stats_engine_test.rb +1 -46
  78. data/test/new_relic/agent/stats_hash_test.rb +93 -0
  79. data/test/new_relic/agent/stats_test.rb +197 -0
  80. data/test/new_relic/agent/transaction_info_test.rb +63 -11
  81. data/test/new_relic/agent/transaction_sample_builder_test.rb +10 -3
  82. data/test/new_relic/agent/transaction_sampler_test.rb +92 -80
  83. data/test/new_relic/agent_test.rb +35 -5
  84. data/test/new_relic/control_test.rb +1 -1
  85. data/test/new_relic/fake_collector.rb +87 -9
  86. data/test/new_relic/helper_test.rb +24 -0
  87. data/test/new_relic/metric_data_test.rb +11 -11
  88. data/test/new_relic/metric_spec_test.rb +1 -1
  89. data/test/script/ci.sh +1 -1
  90. data/test/test_contexts.rb +0 -1
  91. data/test/test_helper.rb +21 -3
  92. metadata +32 -16
  93. metadata.gz.sig +0 -0
  94. data/lib/new_relic/agent/cross_process_monitoring.rb +0 -187
  95. data/lib/new_relic/stats.rb +0 -337
  96. data/test/new_relic/agent/cross_process_monitoring_test.rb +0 -190
  97. data/test/new_relic/agent/stats_engine/metric_stats/harvest_test.rb +0 -133
  98. data/test/new_relic/fakes_sending_data.rb +0 -30
  99. data/test/new_relic/stats_test.rb +0 -421
@@ -300,17 +300,30 @@ module NewRelic
300
300
  end
301
301
 
302
302
  def self.record_apdex(current_metric, action_duration, total_duration, is_error)
303
- summary_stat = agent.stats_engine.get_custom_stats("Apdex", NewRelic::ApdexStats)
304
- controller_stat = agent.stats_engine.get_custom_stats(current_metric.apdex_metric_path, NewRelic::ApdexStats)
303
+ summary_stat = agent.stats_engine.lookup_stats("Apdex") ||
304
+ initialize_apdex("Apdex")
305
305
  update_apdex(summary_stat, total_duration, is_error)
306
+
307
+ controller_stat = agent.stats_engine.lookup_stats(current_metric.apdex_metric_path) ||
308
+ initialize_apdex(current_metric.apdex_metric_path)
306
309
  update_apdex(controller_stat, action_duration, is_error)
307
310
  end
308
311
 
312
+ # Apdex min and max values should be initialized to the
313
+ # current apdex_t
314
+ def self.initialize_apdex(metric_name)
315
+ stats = agent.stats_engine.get_stats_no_scope(metric_name)
316
+ apdex_t = TransactionInfo.get.apdex_t
317
+ stats.min_call_time = apdex_t
318
+ stats.max_call_time = apdex_t
319
+ return stats
320
+ end
321
+
309
322
  # Record an apdex value for the given stat. when `failed`
310
323
  # the apdex should be recorded as a failure regardless of duration.
311
324
  def self.update_apdex(stat, duration, failed)
312
325
  duration = duration.to_f
313
- apdex_t = Agent.config[:apdex_t]
326
+ apdex_t = TransactionInfo.get.apdex_t
314
327
  case
315
328
  when failed
316
329
  stat.record_apdex_f
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  DependencyDetection.defer do
2
4
  @name = :net
3
5
 
@@ -7,23 +9,23 @@ DependencyDetection.defer do
7
9
 
8
10
  executes do
9
11
  ::NewRelic::Agent.logger.info 'Installing Net instrumentation'
12
+ require 'new_relic/agent/cross_app_tracing'
10
13
  end
11
-
14
+
12
15
  executes do
13
- Net::HTTP.class_eval do
14
- def request_with_newrelic_trace(*args, &block)
15
- metrics = ["External/#{@address}/Net::HTTP/#{args[0].method}", "External/#{@address}/all", "External/all"]
16
- if NewRelic::Agent::Instrumentation::MetricFrame.recording_web_transaction?
17
- metrics << "External/allWeb"
18
- else
19
- metrics << "External/allOther"
20
- end
21
- self.class.trace_execution_scoped metrics do
22
- request_without_newrelic_trace(*args, &block)
16
+ class Net::HTTP
17
+
18
+ # Instrument outgoing HTTP requests and fire associated events back
19
+ # into the Agent.
20
+ def request_with_newrelic_trace(request, *args, &block)
21
+ NewRelic::Agent::CrossAppTracing.trace_http_request( self, request ) do
22
+ request_without_newrelic_trace( request, *args, &block )
23
23
  end
24
24
  end
25
+
25
26
  alias request_without_newrelic_trace request
26
27
  alias request request_with_newrelic_trace
28
+
27
29
  end
28
30
  end
29
31
  end
@@ -1,6 +1,6 @@
1
1
  DependencyDetection.defer do
2
2
  @name = :resque
3
-
3
+
4
4
  depends_on do
5
5
  defined?(::Resque::Job) && !NewRelic::Agent.config[:disable_resque] &&
6
6
  !NewRelic::LanguageSupport.using_version?('1.9.1')
@@ -9,19 +9,19 @@ DependencyDetection.defer do
9
9
  executes do
10
10
  ::NewRelic::Agent.logger.info 'Installing Resque instrumentation'
11
11
  end
12
-
12
+
13
13
  executes do
14
14
  # == Resque Instrumentation
15
15
  #
16
16
  # Installs a hook to ensure the agent starts manually when the worker
17
17
  # starts and also adds the tracer to the process method which executes
18
18
  # in the forked task.
19
-
19
+
20
20
  module Resque
21
21
  module Plugins
22
22
  module NewRelicInstrumentation
23
23
  include NewRelic::Agent::Instrumentation::ControllerInstrumentation
24
-
24
+
25
25
  def around_perform_with_monitoring(*args)
26
26
  begin
27
27
  perform_action_with_newrelic_trace(:name => 'perform',
@@ -36,7 +36,7 @@ DependencyDetection.defer do
36
36
  end
37
37
  end
38
38
  end
39
-
39
+
40
40
  module NewRelic
41
41
  module Agent
42
42
  module Instrumentation
@@ -51,13 +51,13 @@ DependencyDetection.defer do
51
51
  end
52
52
  end
53
53
  end
54
-
54
+
55
55
  ::Resque::Job.class_eval do
56
56
  def self.new(*args)
57
57
  super(*args).extend NewRelic::Agent::Instrumentation::ResqueInstrumentationInstaller
58
58
  end
59
59
  end
60
-
60
+
61
61
  if NewRelic::LanguageSupport.can_fork?
62
62
  ::Resque.before_first_fork do
63
63
  NewRelic::Agent.manual_start(:dispatcher => :resque,
@@ -65,17 +65,17 @@ DependencyDetection.defer do
65
65
  :start_channel_listener => true,
66
66
  :report_instance_busy => false)
67
67
  end
68
-
68
+
69
69
  ::Resque.before_fork do |job|
70
70
  NewRelic::Agent.register_report_channel(job.object_id)
71
71
  end
72
-
72
+
73
73
  ::Resque.after_fork do |job|
74
74
  NewRelic::Agent.after_fork(:report_to_channel => job.object_id)
75
75
  end
76
76
  end
77
77
  end
78
- end
78
+ end
79
79
 
80
80
  # call this now so it is memoized before potentially forking worker processes
81
81
  NewRelic::LanguageSupport.can_fork?
@@ -37,26 +37,33 @@ module NewRelic
37
37
  include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
38
38
 
39
39
  def dispatch_with_newrelic
40
+ # We're trying to determine the transaction name via Sinatra's
41
+ # process_route, but calling it here misses Sinatra's normal error handling.
42
+ #
43
+ # Relies on transaction_name to always safely return a value for us
40
44
  txn_name = NewRelic.transaction_name(self.class.routes, @request) do |pattern, keys, conditions|
41
- process_route(pattern, keys, conditions) do
45
+ result = process_route(pattern, keys, conditions) do
42
46
  pattern.source
43
47
  end
48
+ result if result.class == String
44
49
  end
45
50
 
46
51
  perform_action_with_newrelic_trace(:category => :sinatra,
47
52
  :name => txn_name,
48
53
  :params => @request.params) do
49
- result = dispatch_without_newrelic
50
-
51
- # Will only see an error raised if :show_exceptions is true, but
52
- # will always see them in the env hash if they occur
53
- had_error = env.has_key?('sinatra.error')
54
- ::NewRelic::Agent.notice_error(env['sinatra.error']) if had_error
55
-
56
- result
54
+ dispatch_and_notice_errors_with_newrelic
57
55
  end
58
56
  end
59
57
 
58
+ def dispatch_and_notice_errors_with_newrelic
59
+ dispatch_without_newrelic
60
+ ensure
61
+ # Will only see an error raised if :show_exceptions is true, but
62
+ # will always see them in the env hash if they occur
63
+ had_error = env.has_key?('sinatra.error')
64
+ ::NewRelic::Agent.notice_error(env['sinatra.error']) if had_error
65
+ end
66
+
60
67
  # Define Request Header accessor for Sinatra
61
68
  def newrelic_request_headers
62
69
  request.env
@@ -89,6 +96,9 @@ module NewRelic
89
96
  end
90
97
 
91
98
  name
99
+ rescue => e
100
+ ::NewRelic::Agent.logger.debug("#{e.class} : #{e.message} - Error encountered trying to identify Sinatra transaction name")
101
+ '(unknown)'
92
102
  end
93
103
  end
94
104
  end
@@ -7,7 +7,8 @@ module NewRelic
7
7
  # Specifies the version of the agent's communication protocol with
8
8
  # the NewRelic hosted site.
9
9
 
10
- PROTOCOL_VERSION = 10
10
+ PROTOCOL_VERSION = 12
11
+ # 1f147a42: v10 (tag 3.5.3.17)
11
12
  # cf0d1ff1: v9 (tag 3.5.0)
12
13
  # 14105: v8 (tag 2.10.3)
13
14
  # (no v7)
@@ -18,12 +19,13 @@ module NewRelic
18
19
  # 534: v2 (shows up in 2.1.0, our first tag)
19
20
 
20
21
  attr_accessor :request_timeout, :agent_id
21
- attr_reader :collector, :marshaller
22
+ attr_reader :collector, :marshaller, :metric_id_cache
22
23
 
23
24
  def initialize(license_key=nil, collector=control.server)
24
25
  @license_key = license_key || Agent.config[:license_key]
25
26
  @collector = collector
26
27
  @request_timeout = Agent.config[:timeout]
28
+ @metric_id_cache = {}
27
29
 
28
30
  @audit_logger = ::NewRelic::Agent::AuditLogger.new(Agent.config)
29
31
  Agent.config.register_callback(:'audit_log.enabled') do |enabled|
@@ -70,9 +72,47 @@ module NewRelic
70
72
  invoke_remote(:shutdown, @agent_id, time.to_i) if @agent_id
71
73
  end
72
74
 
75
+ def reset_metric_id_cache
76
+ @metric_id_cache = {}
77
+ end
78
+
79
+ # takes an array of arrays of spec and id, adds it into the
80
+ # metric cache so we can save the collector some work by
81
+ # sending integers instead of strings the next time around
82
+ def fill_metric_id_cache(pairs_of_specs_and_ids)
83
+ Array(pairs_of_specs_and_ids).each do |metric_spec_hash, metric_id|
84
+ metric_spec = MetricSpec.new(metric_spec_hash['name'],
85
+ metric_spec_hash['scope'])
86
+ metric_id_cache[metric_spec] = metric_id
87
+ end
88
+ end
89
+
90
+ # The collector wants to recieve metric data in a format that's different
91
+ # from how we store it internally, so this method handles the translation.
92
+ # It also handles translating metric names to IDs using our metric ID cache.
93
+ def build_metric_data_array(stats_hash)
94
+ metric_data_array = []
95
+ stats_hash.each do |metric_spec, stats|
96
+ # Omit empty stats as an optimization
97
+ unless stats.is_reset?
98
+ metric_id = metric_id_cache[metric_spec]
99
+ metric_data = if metric_id
100
+ NewRelic::MetricData.new(nil, stats, metric_id)
101
+ else
102
+ NewRelic::MetricData.new(metric_spec, stats, nil)
103
+ end
104
+ metric_data_array << metric_data
105
+ end
106
+ end
107
+ metric_data_array
108
+ end
109
+
73
110
  def metric_data(last_harvest_time, now, unsent_timeslice_data)
74
- invoke_remote(:metric_data, @agent_id, last_harvest_time, now,
75
- unsent_timeslice_data)
111
+ metric_data_array = build_metric_data_array(unsent_timeslice_data)
112
+ result = invoke_remote(:metric_data, @agent_id, last_harvest_time, now,
113
+ metric_data_array)
114
+ fill_metric_id_cache(result)
115
+ result
76
116
  end
77
117
 
78
118
  def error_data(unsent_errors)
@@ -207,7 +247,14 @@ module NewRelic
207
247
  def invoke_remote(method, *args)
208
248
  now = Time.now
209
249
 
210
- data = @marshaller.dump(args)
250
+ data = nil
251
+ begin
252
+ data = @marshaller.dump(args)
253
+ rescue JsonError
254
+ @marshaller = PrubyMarshaller.new
255
+ retry
256
+ end
257
+
211
258
  data, encoding = compress_request_if_needed(data)
212
259
 
213
260
  uri = remote_method_uri(method, @marshaller.format)
@@ -341,6 +388,12 @@ module NewRelic
341
388
  end
342
389
  end
343
390
 
391
+ # Used to wrap errors reported to agent by the collector
392
+ class CollectorError < StandardError; end
393
+
394
+ # Used to wrap any problem with the JSON marshaller
395
+ class JsonError < StandardError; end
396
+
344
397
  class Marshaller
345
398
  def parsed_error(error)
346
399
  error_class = error['error_type'].split('::') \
@@ -426,13 +479,16 @@ module NewRelic
426
479
 
427
480
  def dump(ruby, opts={})
428
481
  JSON.dump(prepare(ruby, opts))
482
+ rescue => e
483
+ ::NewRelic::Agent.logger.debug "#{e.class.name} : #{e.message} encountered dumping agent data: #{ruby}"
484
+ raise JsonError.new(e)
429
485
  end
430
486
 
431
487
  def load(data)
432
488
  return unless data && data != ''
433
489
  return_value(JSON.load(data))
434
- rescue
435
- ::NewRelic::Agent.logger.debug "Error encountered loading collector response: #{data}"
490
+ rescue => e
491
+ ::NewRelic::Agent.logger.debug "#{e.class.name} : #{e.message} encountered loading collector response: #{data}"
436
492
  raise
437
493
  end
438
494
 
@@ -452,8 +508,6 @@ module NewRelic
452
508
  true # for some definitions of 'human'
453
509
  end
454
510
  end
455
-
456
- class CollectorError < StandardError; end
457
511
  end
458
512
  end
459
513
  end
@@ -3,13 +3,13 @@ module NewRelic
3
3
  class PipeService
4
4
  attr_reader :channel_id, :buffer
5
5
  attr_accessor :request_timeout, :agent_id, :collector
6
-
6
+
7
7
  def initialize(channel_id)
8
8
  @channel_id = channel_id
9
9
  @collector = NewRelic::Control::Server.new(:name => 'parent',
10
10
  :port => 0)
11
11
  end
12
-
12
+
13
13
  def connect(config)
14
14
  nil
15
15
  end
@@ -19,7 +19,7 @@ module NewRelic
19
19
  end
20
20
 
21
21
  def metric_data(last_harvest_time, now, unsent_timeslice_data)
22
- write_to_pipe(:stats => hash_from_metric_data(unsent_timeslice_data))
22
+ write_to_pipe(:stats => unsent_timeslice_data)
23
23
  {}
24
24
  end
25
25
 
@@ -34,7 +34,7 @@ module NewRelic
34
34
  def sql_trace_data(sql)
35
35
  write_to_pipe(:sql_traces => sql) if sql
36
36
  end
37
-
37
+
38
38
  def shutdown(time)
39
39
  write_to_pipe('EOF')
40
40
  NewRelic::Agent::PipeChannelManager.channels[@channel_id].close
@@ -46,17 +46,13 @@ module NewRelic
46
46
  def session
47
47
  yield
48
48
  end
49
-
50
- private
51
49
 
52
- def hash_from_metric_data(metric_data)
53
- metric_hash = {}
54
- metric_data.each do |metric_entry|
55
- metric_hash[metric_entry.metric_spec] = metric_entry
56
- end
57
- metric_hash
50
+ def reset_metric_id_cache
51
+ # we don't cache metric IDs, so nothing to do
58
52
  end
59
53
 
54
+ private
55
+
60
56
  def write_to_pipe(data)
61
57
  NewRelic::Agent::PipeChannelManager.channels[@channel_id].write(data)
62
58
  rescue => e
@@ -0,0 +1,72 @@
1
+ module NewRelic
2
+ module Agent
3
+ class RulesEngine
4
+ include Enumerable
5
+ extend Forwardable
6
+
7
+ def_delegators :@rules, :size, :<<, :inspect, :each
8
+
9
+ attr_accessor :rules
10
+
11
+ def initialize(rules=[])
12
+ @rules = rules
13
+ end
14
+
15
+ def rename(original_string)
16
+ @rules.sort.inject(original_string) do |string,rule|
17
+ if rule.each_segment
18
+ result, matched = rule.map_to_list(string.split('/'))
19
+ result = result.join('/')
20
+ else
21
+ result, matched = rule.apply(string)
22
+ end
23
+
24
+ break result if matched && rule.terminate_chain
25
+ result
26
+ end
27
+ end
28
+
29
+ class Rule
30
+ attr_reader(:terminate_chain, :each_segment, :ignore, :replace_all, :eval_order,
31
+ :match_expression, :replacement)
32
+
33
+ def initialize(options)
34
+ if !options['match_expression']
35
+ raise ArgumentError.new('missing required match_expression')
36
+ end
37
+ if !options['replacement'] && !options['ignore']
38
+ raise ArgumentError.new('must specify replacement when ignore is false')
39
+ end
40
+
41
+ @match_expression = Regexp.new(options['match_expression'])
42
+ @replacement = options['replacement']
43
+ @ignore = options['ignore'] || false
44
+ @eval_order = options['eval_order'] || 0
45
+ @replace_all = options['replace_all'] || false
46
+ @each_segment = options['each_segment'] || false
47
+ @terminate_chain = options['terminate_chain'] || false
48
+ end
49
+
50
+ def apply(string)
51
+ method = @replace_all ? :gsub : :sub
52
+ result = string.send(method, @match_expression, @replacement)
53
+ [result, result != string]
54
+ end
55
+
56
+ def map_to_list(list)
57
+ matched = false
58
+ result = list.map do |string|
59
+ str_result, str_match = apply(string)
60
+ matched ||= str_match
61
+ str_result
62
+ end
63
+ [result, matched]
64
+ end
65
+
66
+ def <=>(other)
67
+ eval_order <=> other.eval_order
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end