scout_apm 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +4 -0
  3. data/lib/scout_apm.rb +48 -23
  4. data/lib/scout_apm/agent.rb +93 -130
  5. data/lib/scout_apm/agent/reporting.rb +34 -63
  6. data/lib/scout_apm/app_server_load.rb +29 -0
  7. data/lib/scout_apm/background_worker.rb +6 -6
  8. data/lib/scout_apm/capacity.rb +48 -48
  9. data/lib/scout_apm/config.rb +5 -5
  10. data/lib/scout_apm/context.rb +3 -3
  11. data/lib/scout_apm/environment.rb +64 -100
  12. data/lib/scout_apm/framework_integrations/rails_2.rb +32 -0
  13. data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +33 -0
  14. data/lib/scout_apm/framework_integrations/ruby.rb +26 -0
  15. data/lib/scout_apm/framework_integrations/sinatra.rb +27 -0
  16. data/lib/scout_apm/instruments/active_record_instruments.rb +1 -1
  17. data/lib/scout_apm/instruments/mongoid_instruments.rb +1 -1
  18. data/lib/scout_apm/instruments/moped_instruments.rb +3 -3
  19. data/lib/scout_apm/instruments/net_http.rb +2 -2
  20. data/lib/scout_apm/instruments/process/process_cpu.rb +41 -20
  21. data/lib/scout_apm/instruments/process/process_memory.rb +45 -30
  22. data/lib/scout_apm/instruments/rails/action_controller_instruments.rb +20 -18
  23. data/lib/scout_apm/instruments/rails3_or_4/action_controller_instruments.rb +17 -14
  24. data/lib/scout_apm/layaway.rb +12 -12
  25. data/lib/scout_apm/layaway_file.rb +1 -1
  26. data/lib/scout_apm/metric_meta.rb +9 -9
  27. data/lib/scout_apm/metric_stats.rb +8 -8
  28. data/lib/scout_apm/reporter.rb +83 -0
  29. data/lib/scout_apm/serializers/app_server_load_serializer.rb +15 -0
  30. data/lib/scout_apm/serializers/directive_serializer.rb +15 -0
  31. data/lib/scout_apm/serializers/payload_serializer.rb +14 -0
  32. data/lib/scout_apm/server_integrations/null.rb +30 -0
  33. data/lib/scout_apm/server_integrations/passenger.rb +35 -0
  34. data/lib/scout_apm/server_integrations/puma.rb +30 -0
  35. data/lib/scout_apm/server_integrations/rainbows.rb +36 -0
  36. data/lib/scout_apm/server_integrations/thin.rb +41 -0
  37. data/lib/scout_apm/server_integrations/unicorn.rb +35 -0
  38. data/lib/scout_apm/server_integrations/webrick.rb +25 -0
  39. data/lib/scout_apm/slow_transaction.rb +3 -3
  40. data/lib/scout_apm/stack_item.rb +19 -17
  41. data/lib/scout_apm/store.rb +35 -35
  42. data/lib/scout_apm/tracer.rb +121 -110
  43. data/lib/scout_apm/utils/sql_sanitizer.rb +2 -2
  44. data/lib/scout_apm/version.rb +2 -1
  45. data/test/unit/environment_test.rb +5 -5
  46. metadata +18 -2
@@ -0,0 +1,25 @@
1
+ module ScoutApm
2
+ module ServerIntegrations
3
+ class Webrick
4
+ attr_reader :logger
5
+
6
+ def initialize(logger)
7
+ @logger = logger
8
+ end
9
+
10
+ def name
11
+ :webrick
12
+ end
13
+
14
+ def forking?; false; end
15
+
16
+ def present?
17
+ defined?(::WEBrick) && defined?(::WEBrick::VERSION)
18
+ end
19
+
20
+ # TODO: What does it mean to install on a non-forking env?
21
+ def install
22
+ end
23
+ end
24
+ end
25
+ end
@@ -3,7 +3,7 @@ class ScoutApm::SlowTransaction
3
3
  BACKTRACE_LIMIT = 5 # Max length of callers to display
4
4
  MAX_SIZE = 100 # Limits the size of the metric hash to prevent a metric explosion.
5
5
  attr_reader :metric_name, :total_call_time, :metrics, :meta, :uri, :context, :time
6
-
6
+
7
7
  # Given a call stack, generates a filtered backtrace that:
8
8
  # * Limits to the app/models, app/controllers, or app/views directories
9
9
  # * Limits to 5 total callers
@@ -18,7 +18,7 @@ class ScoutApm::SlowTransaction
18
18
  end
19
19
  stack
20
20
  end
21
-
21
+
22
22
  def initialize(uri,metric_name,total_call_time,metrics,context,time)
23
23
  @uri = uri
24
24
  @metric_name = metric_name
@@ -33,4 +33,4 @@ class ScoutApm::SlowTransaction
33
33
  @metrics = nil
34
34
  self
35
35
  end
36
- end
36
+ end
@@ -1,18 +1,20 @@
1
- class ScoutApm::StackItem
2
- attr_accessor :children_time
3
- attr_reader :metric_name, :start_time
4
-
5
- def initialize(metric_name)
6
- @metric_name = metric_name
7
- @start_time = Time.now
8
- @children_time = 0
9
- end
10
-
11
- def ==(o)
12
- self.eql?(o)
13
- end
1
+ module ScoutApm
2
+ class StackItem
3
+ attr_accessor :children_time
4
+ attr_reader :metric_name, :start_time
14
5
 
15
- def eql?(o)
16
- self.class == o.class && metric_name.eql?(o.metric_name)
17
- end
18
- end # class StackItem
6
+ def initialize(metric_name)
7
+ @metric_name = metric_name
8
+ @start_time = Time.now
9
+ @children_time = 0
10
+ end
11
+
12
+ def ==(o)
13
+ self.eql?(o)
14
+ end
15
+
16
+ def eql?(o)
17
+ self.class == o.class && metric_name.eql?(o.metric_name)
18
+ end
19
+ end # class StackItem
20
+ end
@@ -1,19 +1,19 @@
1
1
  # The store encapsolutes the logic that (1) saves instrumented data by Metric name to memory and (2) maintains a stack (just an Array)
2
- # of instrumented methods that are being called. It's accessed via +ScoutApm::Agent.instance.store+.
2
+ # of instrumented methods that are being called. It's accessed via +ScoutApm::Agent.instance.store+.
3
3
  class ScoutApm::Store
4
-
5
- # Limits the size of the metric hash to prevent a metric explosion.
4
+
5
+ # Limits the size of the metric hash to prevent a metric explosion.
6
6
  MAX_SIZE = 1000
7
7
 
8
- # Limit the number of slow transactions that we store metrics with to prevent writing too much data to the layaway file if there are are many processes and many slow slow_transactions.
8
+ # Limit the number of slow transactions that we store metrics with to prevent writing too much data to the layaway file if there are are many processes and many slow slow_transactions.
9
9
  MAX_SLOW_TRANSACTIONS_TO_STORE_METRICS = 10
10
-
10
+
11
11
  attr_accessor :metric_hash
12
12
  attr_accessor :transaction_hash
13
13
  attr_accessor :stack
14
14
  attr_accessor :slow_transactions # array of slow transaction slow_transactions
15
15
  attr_reader :slow_transaction_lock
16
-
16
+
17
17
  def initialize
18
18
  @metric_hash = Hash.new
19
19
  # Stores aggregate metrics for the current transaction. When the transaction is finished, metrics
@@ -24,7 +24,7 @@ class ScoutApm::Store
24
24
  @slow_transaction_lock = Mutex.new
25
25
  @slow_transactions = Array.new
26
26
  end
27
-
27
+
28
28
  # Called when the last stack item completes for the current transaction to clear
29
29
  # for the next run.
30
30
  def reset_transaction!
@@ -33,13 +33,13 @@ class ScoutApm::Store
33
33
  @transaction_hash = Hash.new
34
34
  @stack = Array.new
35
35
  end
36
-
36
+
37
37
  def ignore_transaction!
38
38
  Thread::current[:scout_apm_ignore_transaction] = true
39
39
  end
40
-
40
+
41
41
  # Called at the start of Tracer#instrument:
42
- # (1) Either finds an existing MetricStats object in the metric_hash or
42
+ # (1) Either finds an existing MetricStats object in the metric_hash or
43
43
  # initialize a new one. An existing MetricStats object is present if this +metric_name+ has already been instrumented.
44
44
  # (2) Adds a StackItem to the stack. This StackItem is returned and later used to validate the item popped off the stack
45
45
  # when an instrumented code block completes.
@@ -48,19 +48,19 @@ class ScoutApm::Store
48
48
  stack << item
49
49
  item
50
50
  end
51
-
51
+
52
52
  # Options:
53
53
  # * :scope - If specified, sets the sub-scope for the metric. We allow additional scope level. This is used
54
54
  # * uri - the request uri
55
55
  def stop_recording(sanity_check_item, options={})
56
56
  item = stack.pop
57
57
  stack_empty = stack.empty?
58
- # if ignoring the transaction, the item is popped but nothing happens.
58
+ # if ignoring the transaction, the item is popped but nothing happens.
59
59
  if Thread::current[:scout_apm_ignore_transaction]
60
60
  return
61
61
  end
62
- # unbalanced stack check - unreproducable cases have seen this occur. when it does, sets a Thread variable
63
- # so we ignore further recordings. +Store#reset_transaction!+ resets this.
62
+ # unbalanced stack check - unreproducable cases have seen this occur. when it does, sets a Thread variable
63
+ # so we ignore further recordings. +Store#reset_transaction!+ resets this.
64
64
  if item != sanity_check_item
65
65
  ScoutApm::Agent.instance.logger.warn "Scope [#{Thread::current[:scout_apm_scope_name]}] Popped off stack: #{item.inspect} Expected: #{sanity_check_item.inspect}. Aborting."
66
66
  ignore_transaction!
@@ -72,7 +72,7 @@ class ScoutApm::Store
72
72
  end
73
73
  meta = ScoutApm::MetricMeta.new(item.metric_name, :desc => options[:desc])
74
74
  meta.scope = nil if stack_empty
75
-
75
+
76
76
  # add backtrace for slow calls ... how is exclusive time handled?
77
77
  if duration > ScoutApm::SlowTransaction::BACKTRACE_THRESHOLD and !stack_empty
78
78
  meta.extra = {:backtrace => ScoutApm::SlowTransaction.backtrace_parser(caller)}
@@ -80,20 +80,20 @@ class ScoutApm::Store
80
80
  stat = transaction_hash[meta] || ScoutApm::MetricStats.new(!stack_empty)
81
81
  stat.update!(duration,duration-item.children_time)
82
82
  transaction_hash[meta] = stat if store_metric?(stack_empty)
83
-
83
+
84
84
  # Uses controllers as the entry point for a transaction. Otherwise, stats are ignored.
85
85
  if stack_empty and meta.metric_name.match(/\AController\//)
86
86
  aggs=aggregate_calls(transaction_hash.dup,meta)
87
- store_slow(options[:uri],transaction_hash.dup.merge(aggs),meta,stat)
88
- # deep duplicate
87
+ store_slow(options[:uri],transaction_hash.dup.merge(aggs),meta,stat)
88
+ # deep duplicate
89
89
  duplicate = aggs.dup
90
90
  duplicate.each_pair do |k,v|
91
91
  duplicate[k.dup] = v.dup
92
- end
93
- merge_metrics(duplicate.merge({meta.dup => stat.dup})) # aggregrates + controller
92
+ end
93
+ merge_metrics(duplicate.merge({meta.dup => stat.dup})) # aggregrates + controller
94
94
  end
95
95
  end
96
-
96
+
97
97
  # TODO - Move more logic to SlowTransaction
98
98
  #
99
99
  # Limits the size of the transaction hash to prevent a large transactions. The final item on the stack
@@ -101,19 +101,19 @@ class ScoutApm::Store
101
101
  def store_metric?(stack_empty)
102
102
  transaction_hash.size < ScoutApm::SlowTransaction::MAX_SIZE or stack_empty
103
103
  end
104
-
104
+
105
105
  # Returns the top-level category names used in the +metrics+ hash.
106
106
  def categories(metrics)
107
107
  cats = Set.new
108
108
  metrics.keys.each do |meta|
109
109
  next if meta.scope.nil? # ignore controller
110
- if match=meta.metric_name.match(/\A([\w|\d]+)\//)
110
+ if match=meta.metric_name.match(/\A([\w]+)\//)
111
111
  cats << match[1]
112
112
  end
113
113
  end # metrics.each
114
114
  cats
115
115
  end
116
-
116
+
117
117
  # Takes a metric_hash of calls and generates aggregates for ActiveRecord and View calls.
118
118
  def aggregate_calls(metrics,parent_meta)
119
119
  categories = categories(metrics)
@@ -124,30 +124,30 @@ class ScoutApm::Store
124
124
  agg_stats = ScoutApm::MetricStats.new
125
125
  metrics.each do |meta,stats|
126
126
  if meta.metric_name =~ /\A#{cat}\//
127
- agg_stats.combine!(stats)
127
+ agg_stats.combine!(stats)
128
128
  end
129
129
  end # metrics.each
130
130
  aggregates[agg_meta] = agg_stats unless agg_stats.call_count.zero?
131
- end # categories.each
131
+ end # categories.each
132
132
  aggregates
133
133
  end
134
-
134
+
135
135
  # Stores slow transactions. This will be sent to the server.
136
- def store_slow(uri,transaction_hash,parent_meta,parent_stat,options = {})
136
+ def store_slow(uri,transaction_hash,parent_meta,parent_stat,options = {})
137
137
  @slow_transaction_lock.synchronize do
138
138
  # tree map of all slow transactions
139
139
  if parent_stat.total_call_time >= 2
140
140
  @slow_transactions.push(ScoutApm::SlowTransaction.new(uri,parent_meta.metric_name,parent_stat.total_call_time,transaction_hash.dup,ScoutApm::Context.current,Thread::current[:scout_apm_trace_time]))
141
- ScoutApm::Agent.instance.logger.debug "Slow transaction sample added. [URI: #{uri}] [Context: #{ScoutApm::Context.current.to_hash}] Array Size: #{@slow_transactions.size}"
141
+ ScoutApm::Agent.instance.logger.debug "Slow transaction sample added. [URI: #{uri}] [Context: #{ScoutApm::Context.current.to_hash}] Array Size: #{@slow_transactions.size}"
142
142
  end
143
143
  end
144
144
  end
145
-
146
- # Finds or creates the metric w/the given name in the metric_hash, and updates the time. Primarily used to
145
+
146
+ # Finds or creates the metric w/the given name in the metric_hash, and updates the time. Primarily used to
147
147
  # record sampled metrics. For instrumented methods, #record and #stop_recording are used.
148
148
  #
149
149
  # Options:
150
- # :scope => If provided, overrides the default scope.
150
+ # :scope => If provided, overrides the default scope.
151
151
  # :exclusive_time => Sets the exclusive time for the method. If not provided, uses +call_time+.
152
152
  def track!(metric_name,call_time,options = {})
153
153
  meta = ScoutApm::MetricMeta.new(metric_name)
@@ -156,12 +156,12 @@ class ScoutApm::Store
156
156
  stat.update!(call_time,options[:exclusive_time] || call_time)
157
157
  metric_hash[meta] = stat
158
158
  end
159
-
159
+
160
160
  # Combines old and current data
161
161
  def merge_data(old_data)
162
162
  {:metrics => merge_metrics(old_data[:metrics]), :slow_transactions => merge_slow_transactions(old_data[:slow_transactions])}
163
163
  end
164
-
164
+
165
165
  # Merges old and current data, clears the current in-memory metric hash, and returns
166
166
  # the merged data
167
167
  def merge_data_and_clear(old_data)
@@ -198,4 +198,4 @@ class ScoutApm::Store
198
198
  end
199
199
  self.slow_transactions
200
200
  end
201
- end # class Store
201
+ end # class Store
@@ -1,123 +1,134 @@
1
- # Contains the methods that instrument blocks of code.
2
- #
1
+ # Contains the methods that instrument blocks of code.
2
+ #
3
3
  # When a code block is wrapped inside #instrument(metric_name):
4
4
  # * The #instrument method pushes a StackItem onto Store#stack
5
5
  # * When a code block is finished, #instrument pops the last item off the stack and verifies it's the StackItem
6
- # we created earlier.
6
+ # we created earlier.
7
7
  # * Once verified, the metrics for the recording session are merged into the in-memory Store#metric_hash. The current scope
8
8
  # is also set for the metric (if Thread::current[:scout_apm_scope_name] isn't nil).
9
- module ScoutApm::Tracer
10
- def self.included(klass)
11
- klass.extend ClassMethods
12
- end
13
-
14
- module ClassMethods
15
-
16
- # Use to trace a method call, possibly reporting slow transaction traces to Scout.
17
- # Options:
18
- # * uri - the request uri
19
- # * ip - the remote ip of the user. This is merged into the User context.
20
- def scout_apm_trace(metric_name, options = {}, &block)
21
- # TODO - wrap a lot of this into a Trace class, store that as a Thread var.
22
- ScoutApm::Agent.instance.store.reset_transaction!
23
- ScoutApm::Context.current.add_user(:ip => options[:ip]) if options[:ip]
24
- Thread::current[:scout_apm_trace_time] = Time.now.utc
25
- ScoutApm::Agent.instance.capacity.start_transaction!
26
- e = nil
27
- instrument(metric_name, options) do
28
- Thread::current[:scout_apm_scope_name] = metric_name
9
+ module ScoutApm
10
+ module Tracer
11
+ def self.included(klass)
12
+ klass.extend ClassMethods
13
+ end
14
+
15
+ module ClassMethods
16
+
17
+ # Use to trace a method call, possibly reporting slow transaction traces to Scout.
18
+ # Options:
19
+ # * uri - the request uri
20
+ # * ip - the remote ip of the user. This is merged into the User context.
21
+ def scout_apm_trace(metric_name, options = {}, &block)
22
+ # TODO - wrap a lot of this into a Trace class, store that as a Thread var.
23
+ ScoutApm::Agent.instance.store.reset_transaction!
24
+ ScoutApm::Context.current.add_user(:ip => options[:ip]) if options[:ip]
25
+ Thread::current[:scout_apm_trace_time] = Time.now.utc
26
+ ScoutApm::Agent.instance.capacity.start_transaction!
27
+ e = nil
28
+ instrument(metric_name, options) do
29
+ Thread::current[:scout_apm_scope_name] = metric_name
30
+ begin
31
+ yield
32
+ rescue Exception => e
33
+ ScoutApm::Agent.instance.store.track!("Errors/#{metric_name}",1, :scope => nil)
34
+ end
35
+ Thread::current[:scout_apm_scope_name] = nil
36
+ end
37
+ Thread::current[:scout_apm_trace_time] = nil
38
+ ScoutApm::Agent.instance.capacity.finish_transaction!
39
+ # The context is cleared after instrumentation (rather than before) as tracing controller-actions doesn't occur until the controller-action is called.
40
+ # It does not trace before filters, which is a likely spot to add context. This means that any context applied during before_filters would be cleared.
41
+ ScoutApm::Context.clear!
42
+ raise e if e
43
+ end
44
+
45
+ # Options:
46
+ # * :scope - If specified, sets the sub-scope for the metric. We allow additional scope level. This is used
47
+ # * uri - the request uri
48
+ # * :ignore_children - will not instrument any method calls beneath this call. Example use case: InfluxDB uses Net::HTTP, which is instrumented. However, we can provide more specific data if we know we're doing an influx call, so we'd rather just instrument the Influx call and ignore Net::HTTP.
49
+ # when rendering the transaction tree in the UI.
50
+ def instrument(metric_name, options={}, &block)
51
+ # don't instrument if (1) NOT inside a transaction and (2) NOT a Controller metric.
52
+ if !Thread::current[:scout_apm_scope_name] and metric_name !~ /\AController\//
53
+ return yield
54
+ elsif Thread::current[:scout_ignore_children]
55
+ return yield
56
+ end
57
+ if options.delete(:scope)
58
+ Thread::current[:scout_apm_sub_scope] = metric_name
59
+ end
60
+ if options[:ignore_children]
61
+ Thread::current[:scout_ignore_children] = true
62
+ end
63
+ stack_item = ScoutApm::Agent.instance.store.record(metric_name)
29
64
  begin
30
65
  yield
31
- rescue Exception => e
32
- ScoutApm::Agent.instance.store.track!("Errors/#{metric_name}",1, :scope => nil)
66
+ ensure
67
+ Thread::current[:scout_apm_sub_scope] = nil if Thread::current[:scout_apm_sub_scope] == metric_name
68
+ if options[:ignore_children]
69
+ Thread::current[:scout_ignore_children] = nil
70
+ end
71
+ ScoutApm::Agent.instance.store.stop_recording(stack_item,options)
33
72
  end
34
- Thread::current[:scout_apm_scope_name] = nil
35
73
  end
36
- Thread::current[:scout_apm_trace_time] = nil
37
- ScoutApm::Agent.instance.capacity.finish_transaction!
38
- # The context is cleared after instrumentation (rather than before) as tracing controller-actions doesn't occur until the controller-action is called.
39
- # It does not trace before filters, which is a likely spot to add context. This means that any context applied during before_filters would be cleared.
40
- ScoutApm::Context.clear!
41
- raise e if e
42
- end
43
-
44
- # Options:
45
- # * :scope - If specified, sets the sub-scope for the metric. We allow additional scope level. This is used
46
- # * uri - the request uri
47
- # when rendering the transaction tree in the UI.
48
- def instrument(metric_name, options={}, &block)
49
- # don't instrument if (1) NOT inside a transaction and (2) NOT a Controller metric.
50
- if !Thread::current[:scout_apm_scope_name] and metric_name !~ /\AController\//
51
- return yield
74
+
75
+ def instrument_method(method,options = {})
76
+ ScoutApm::Agent.instance.logger.info "Instrumenting #{method}"
77
+ metric_name = options[:metric_name] || default_metric_name(method)
78
+ return if !instrumentable?(method) or instrumented?(method,metric_name)
79
+ class_eval instrumented_method_string(method, {:metric_name => metric_name, :scope => options[:scope]}), __FILE__, __LINE__
80
+
81
+ alias_method _uninstrumented_method_name(method, metric_name), method
82
+ alias_method method, _instrumented_method_name(method, metric_name)
52
83
  end
53
- if options.delete(:scope)
54
- Thread::current[:scout_apm_sub_scope] = metric_name
84
+
85
+ private
86
+
87
+ def instrumented_method_string(method, options)
88
+ klass = (self === Module) ? "self" : "self.class"
89
+ "def #{_instrumented_method_name(method, options[:metric_name])}(*args, &block)
90
+ result = #{klass}.instrument(\"#{options[:metric_name]}\",{:scope => #{options[:scope] || false}}) do
91
+ #{_uninstrumented_method_name(method, options[:metric_name])}(*args, &block)
92
+ end
93
+ result
94
+ end"
55
95
  end
56
- stack_item = ScoutApm::Agent.instance.store.record(metric_name)
57
- begin
58
- yield
59
- ensure
60
- Thread::current[:scout_apm_sub_scope] = nil if Thread::current[:scout_apm_sub_scope] == metric_name
61
- ScoutApm::Agent.instance.store.stop_recording(stack_item,options)
96
+
97
+ # The method must exist to be instrumented.
98
+ def instrumentable?(method)
99
+ exists = method_defined?(method) || private_method_defined?(method)
100
+ ScoutApm::Agent.instance.logger.warn "The method [#{self.name}##{method}] does not exist and will not be instrumented" unless exists
101
+ exists
102
+ end
103
+
104
+ # +True+ if the method is already instrumented.
105
+ def instrumented?(method,metric_name)
106
+ instrumented = method_defined?(_instrumented_method_name(method, metric_name))
107
+ ScoutApm::Agent.instance.logger.warn "The method [#{self.name}##{method}] has already been instrumented" if instrumented
108
+ instrumented
109
+ end
110
+
111
+ def default_metric_name(method)
112
+ "Custom/#{self.name}/#{method.to_s}"
113
+ end
114
+
115
+ # given a method and a metric, this method returns the
116
+ # untraced alias of the method name
117
+ def _uninstrumented_method_name(method, metric_name)
118
+ "#{_sanitize_name(method)}_without_scout_instrument_#{_sanitize_name(metric_name)}"
119
+ end
120
+
121
+ # given a method and a metric, this method returns the traced
122
+ # alias of the method name
123
+ def _instrumented_method_name(method, metric_name)
124
+ "#{_sanitize_name(method)}_with_scout_instrument_#{_sanitize_name(metric_name)}"
125
+ end
126
+
127
+ # Method names like +any?+ or +replace!+ contain a trailing character that would break when
128
+ # eval'd as ? and ! aren't allowed inside method names.
129
+ def _sanitize_name(name)
130
+ name.to_s.tr_s('^a-zA-Z0-9', '_')
62
131
  end
63
132
  end
64
-
65
- def instrument_method(method,options = {})
66
- ScoutApm::Agent.instance.logger.info "Instrumenting #{method}"
67
- metric_name = options[:metric_name] || default_metric_name(method)
68
- return if !instrumentable?(method) or instrumented?(method,metric_name)
69
- class_eval instrumented_method_string(method, {:metric_name => metric_name, :scope => options[:scope]}), __FILE__, __LINE__
70
-
71
- alias_method _uninstrumented_method_name(method, metric_name), method
72
- alias_method method, _instrumented_method_name(method, metric_name)
73
- end
74
-
75
- private
76
-
77
- def instrumented_method_string(method, options)
78
- klass = (self === Module) ? "self" : "self.class"
79
- "def #{_instrumented_method_name(method, options[:metric_name])}(*args, &block)
80
- result = #{klass}.instrument(\"#{options[:metric_name]}\",{:scope => #{options[:scope] || false}}) do
81
- #{_uninstrumented_method_name(method, options[:metric_name])}(*args, &block)
82
- end
83
- result
84
- end"
85
- end
86
-
87
- # The method must exist to be instrumented.
88
- def instrumentable?(method)
89
- exists = method_defined?(method) || private_method_defined?(method)
90
- ScoutApm::Agent.instance.logger.warn "The method [#{self.name}##{method}] does not exist and will not be instrumented" unless exists
91
- exists
92
- end
93
-
94
- # +True+ if the method is already instrumented.
95
- def instrumented?(method,metric_name)
96
- instrumented = method_defined?(_instrumented_method_name(method, metric_name))
97
- ScoutApm::Agent.instance.logger.warn "The method [#{self.name}##{method}] has already been instrumented" if instrumented
98
- instrumented
99
- end
100
-
101
- def default_metric_name(method)
102
- "Custom/#{self.name}/#{method.to_s}"
103
- end
104
-
105
- # given a method and a metric, this method returns the
106
- # untraced alias of the method name
107
- def _uninstrumented_method_name(method, metric_name)
108
- "#{_sanitize_name(method)}_without_scout_instrument_#{_sanitize_name(metric_name)}"
109
- end
110
-
111
- # given a method and a metric, this method returns the traced
112
- # alias of the method name
113
- def _instrumented_method_name(method, metric_name)
114
- name = "#{_sanitize_name(method)}_with_scout_instrument_#{_sanitize_name(metric_name)}"
115
- end
116
-
117
- # Method names like +any?+ or +replace!+ contain a trailing character that would break when
118
- # eval'd as ? and ! aren't allowed inside method names.
119
- def _sanitize_name(name)
120
- name.to_s.tr_s('^a-zA-Z0-9', '_')
121
- end
122
- end # ClassMethods
123
- end # module Tracer
133
+ end
134
+ end