scout_apm 1.1.0.pre1 → 1.2.0.pre1

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +6 -0
  3. data/lib/scout_apm/agent/reporting.rb +67 -77
  4. data/lib/scout_apm/agent.rb +56 -9
  5. data/lib/scout_apm/background_job_integrations/delayed_job.rb +19 -0
  6. data/lib/scout_apm/background_job_integrations/sidekiq.rb +60 -0
  7. data/lib/scout_apm/bucket_name_splitter.rb +2 -2
  8. data/lib/scout_apm/capacity.rb +2 -1
  9. data/lib/scout_apm/context.rb +1 -5
  10. data/lib/scout_apm/environment.rb +16 -1
  11. data/lib/scout_apm/instruments/action_controller_rails_2.rb +13 -3
  12. data/lib/scout_apm/instruments/action_controller_rails_3.rb +20 -20
  13. data/lib/scout_apm/instruments/active_record.rb +5 -8
  14. data/lib/scout_apm/instruments/delayed_job.rb +56 -0
  15. data/lib/scout_apm/instruments/middleware.rb +44 -0
  16. data/lib/scout_apm/instruments/mongoid.rb +1 -1
  17. data/lib/scout_apm/instruments/moped.rb +2 -2
  18. data/lib/scout_apm/instruments/net_http.rb +1 -2
  19. data/lib/scout_apm/instruments/process/process_cpu.rb +5 -1
  20. data/lib/scout_apm/instruments/process/process_memory.rb +5 -1
  21. data/lib/scout_apm/instruments/sinatra.rb +14 -2
  22. data/lib/scout_apm/layaway.rb +33 -79
  23. data/lib/scout_apm/layaway_file.rb +2 -1
  24. data/lib/scout_apm/layer.rb +115 -0
  25. data/lib/scout_apm/layer_converter.rb +196 -0
  26. data/lib/scout_apm/metric_meta.rb +24 -4
  27. data/lib/scout_apm/metric_stats.rb +14 -4
  28. data/lib/scout_apm/request_manager.rb +26 -0
  29. data/lib/scout_apm/request_queue_time.rb +54 -0
  30. data/lib/scout_apm/serializers/payload_serializer.rb +8 -1
  31. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +1 -0
  32. data/lib/scout_apm/slow_transaction.rb +3 -0
  33. data/lib/scout_apm/store.rb +122 -190
  34. data/lib/scout_apm/tracer.rb +54 -83
  35. data/lib/scout_apm/tracked_request.rb +168 -0
  36. data/lib/scout_apm/version.rb +1 -1
  37. data/lib/scout_apm.rb +18 -5
  38. metadata +11 -2
@@ -1,11 +1,11 @@
1
- # Contains the methods that instrument blocks of code.
1
+ # Provides helpers to wrap sections of code in instrumentation
2
2
  #
3
- # When a code block is wrapped inside #instrument(metric_name):
4
- # * The #instrument method pushes a StackItem onto Store#stack
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.
7
- # * Once verified, the metrics for the recording session are merged into the in-memory Store#metric_hash. The current scope
8
- # is also set for the metric (if Thread::current[:scout_apm_scope_name] isn't nil).
3
+ # The manual approach is to wrap your code in a call like:
4
+ # `instrument("View", "users/index") do ... end`
5
+ #
6
+ # The monkey-patching approach does this for you:
7
+ # `instrument_method(:my_render, :type => "View", :name => "users/index)`
8
+
9
9
  module ScoutApm
10
10
  module Tracer
11
11
  def self.included(klass)
@@ -13,92 +13,67 @@ module ScoutApm
13
13
  end
14
14
 
15
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
- ScoutApm::Agent.instance.capacity.start_transaction!
26
- e = nil
27
- instrument(metric_name, options) do
28
- Thread::current[:scout_apm_scope_name] = metric_name
29
- begin
30
- yield
31
- rescue Exception => e
32
- ScoutApm::Agent.instance.store.track!("Errors/#{metric_name}",1, :scope => nil)
33
- end
34
- Thread::current[:scout_apm_scope_name] = nil
35
- end
36
- ScoutApm::Agent.instance.capacity.finish_transaction!
37
- # The context is cleared after instrumentation (rather than before) as tracing controller-actions doesn't occur until the controller-action is called.
38
- # 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.
39
- ScoutApm::Context.clear!
40
- raise e if e
41
- end
42
-
16
+ # Type: the Layer type - "View" or similar
17
+ # Name: specific name - "users/_gravatar"
18
+ # A Block: The code to be instrumented
19
+ #
43
20
  # Options:
44
- # * :scope - If specified, sets the sub-scope for the metric. We allow additional scope level. This is used
45
- # * uri - the request uri
46
21
  # * :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.
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
52
- elsif Thread::current[:scout_ignore_children]
53
- return yield
54
- end
55
-
56
- if options.delete(:scope)
57
- Thread::current[:scout_apm_sub_scope] = metric_name
58
- end
59
-
60
- if options[:ignore_children]
61
- Thread::current[:scout_ignore_children] = true
62
- end
63
- stack_item = ScoutApm::Agent.instance.store.record(metric_name)
22
+ # when rendering the transaction tree in the UI.
23
+ # * :desc - Additional capture, SQL, or HTTP url or similar
24
+ # * :scope - set to true if you want to make this layer a subscope
25
+ def instrument(type, name, options={}) # Takes a block
26
+ layer = ScoutApm::Layer.new(type, name)
27
+ layer.desc = options[:desc] if options[:desc]
28
+ layer.subscopable! if options[:scope]
29
+
30
+ req = ScoutApm::RequestManager.lookup
31
+ req.start_layer(layer)
32
+ req.ignore_children! if options[:ignore_children]
64
33
 
65
34
  begin
66
35
  yield
67
36
  ensure
68
- Thread::current[:scout_apm_sub_scope] = nil if Thread::current[:scout_apm_sub_scope] == metric_name
69
- if options[:ignore_children]
70
- Thread::current[:scout_ignore_children] = nil
71
- end
72
-
73
- ScoutApm::Agent.instance.store.stop_recording(stack_item,options)
37
+ req.stop_layer
38
+ req.acknowledge_children! if options[:ignore_children]
74
39
  end
75
40
  end
76
41
 
42
+ # Wraps a method in a call to #instrument via aggressive monkey patching.
43
+ #
44
+ # Options:
45
+ # type - "View" or "ActiveRecord" and similar
46
+ # name - "users/show", "App#find"
77
47
  def instrument_method(method, options = {})
78
48
  ScoutApm::Agent.instance.logger.info "Instrumenting #{method}"
79
- metric_name = options[:metric_name] || default_metric_name(method)
80
- return if !instrumentable?(method) or instrumented?(method,metric_name)
49
+ type = options[:type] || "Custom"
50
+ name = options[:name] || "#{self.name}/#{method.to_s}"
51
+
52
+ return if !instrumentable?(method) or instrumented?(method, type, name)
81
53
 
82
- class_eval(instrumented_method_string(
83
- method,
84
- {:metric_name => metric_name, :scope => options[:scope] }),
54
+ class_eval(
55
+ instrumented_method_string(method, type, name, {:scope => options[:scope] }),
85
56
  __FILE__, __LINE__
86
57
  )
87
58
 
88
- alias_method _uninstrumented_method_name(method, metric_name), method
89
- alias_method method, _instrumented_method_name(method, metric_name)
59
+ alias_method _uninstrumented_method_name(method, type, name), method
60
+ alias_method method, _instrumented_method_name(method, type, name)
90
61
  end
91
62
 
92
63
  private
93
64
 
94
- def instrumented_method_string(method, options)
65
+ def instrumented_method_string(method, type, name, options={})
95
66
  klass = (self === Module) ? "self" : "self.class"
96
- method_str = "def #{_instrumented_method_name(method, options[:metric_name])}(*args, &block)
97
- result = #{klass}.instrument(\"#{options[:metric_name]}\",{:scope => #{options[:scope] || false}}) do
98
- #{_uninstrumented_method_name(method, options[:metric_name])}(*args, &block)
67
+ method_str = <<-EOF
68
+ def #{_instrumented_method_name(method, type, name)}(*args, &block)
69
+ #{klass}.instrument( "#{type}",
70
+ "#{name}",
71
+ {:scope => #{options[:scope] || false}}
72
+ ) do
73
+ #{_uninstrumented_method_name(method, type, name)}(*args, &block)
99
74
  end
100
- result
101
- end"
75
+ end
76
+ EOF
102
77
 
103
78
  method_str
104
79
  end
@@ -111,26 +86,22 @@ module ScoutApm
111
86
  end
112
87
 
113
88
  # +True+ if the method is already instrumented.
114
- def instrumented?(method,metric_name)
115
- instrumented = method_defined?(_instrumented_method_name(method, metric_name))
116
- ScoutApm::Agent.instance.logger.warn "The method [#{self.name}##{method}] has already been instrumented" if instrumented
89
+ def instrumented?(method, type, name)
90
+ instrumented = method_defined?(_instrumented_method_name(method, type, name))
91
+ ScoutApm::Agent.instance.logger.warn("The method [#{self.name}##{method}] has already been instrumented") if instrumented
117
92
  instrumented
118
93
  end
119
94
 
120
- def default_metric_name(method)
121
- "Custom/#{self.name}/#{method.to_s}"
122
- end
123
-
124
95
  # given a method and a metric, this method returns the
125
96
  # untraced alias of the method name
126
- def _uninstrumented_method_name(method, metric_name)
127
- "#{_sanitize_name(method)}_without_scout_instrument_#{_sanitize_name(metric_name)}"
97
+ def _uninstrumented_method_name(method, type, name)
98
+ "#{_sanitize_name(method)}_without_scout_instrument_#{_sanitize_name(name)}"
128
99
  end
129
100
 
130
101
  # given a method and a metric, this method returns the traced
131
102
  # alias of the method name
132
- def _instrumented_method_name(method, metric_name)
133
- "#{_sanitize_name(method)}_with_scout_instrument_#{_sanitize_name(metric_name)}"
103
+ def _instrumented_method_name(method, type, name)
104
+ "#{_sanitize_name(method)}_with_scout_instrument_#{_sanitize_name(name)}"
134
105
  end
135
106
 
136
107
  # Method names like +any?+ or +replace!+ contain a trailing character that would break when
@@ -0,0 +1,168 @@
1
+ # A TrackedRequest is a stack of layers, where completed layers (go into, then
2
+ # come out of a layer) are forgotten as they finish. Layers are attached to
3
+ # their children as the process goes, building a tree structure within the
4
+ # layer objects. When the last layer is finished (hence the whole request is
5
+ # finished) it hands the root layer off to be recorded.
6
+
7
+ module ScoutApm
8
+ class TrackedRequest
9
+ # Context is application defined extra information. (ie, which user, what
10
+ # is their email/ip, what plan are they on, what locale are they using,
11
+ # etc) See documentation for examples on how to set this from a
12
+ # before_filter
13
+ attr_reader :context
14
+
15
+ # The first layer registered with this request. All other layers will be
16
+ # children of this layer.
17
+ attr_reader :root_layer
18
+
19
+ # As we go through a request, instrumentation can mark more general data into the Request
20
+ # Known Keys:
21
+ # :uri - the full URI requested by the user
22
+ attr_reader :annotations
23
+
24
+ # Nil until the request is finalized, at which point it will hold the
25
+ # entire raw stackprof output for this request
26
+ attr_reader :stackprof
27
+
28
+ # Headers as recorded by rails
29
+ # Can be nil if we never reach a Rails Controller
30
+ attr_reader :headers
31
+
32
+ def initialize
33
+ @layers = []
34
+ @annotations = {}
35
+ @ignoring_children = false
36
+ @context = Context.new
37
+ @root_layer = nil
38
+ @stackprof = nil
39
+ @error = false
40
+ end
41
+
42
+ def start_layer(layer)
43
+ start_request(layer) unless @layer
44
+
45
+ # ScoutApm::Agent.instance.logger.info("Starting Layer: #{layer.to_s}")
46
+ @layers[-1].add_child(layer) if @layers.any?
47
+ @layers.push(layer)
48
+ end
49
+
50
+ def stop_layer
51
+ layer = @layers.pop
52
+ layer.record_stop_time!
53
+
54
+ # Do this here, rather than in the layer because we need this caller. Maybe able to move it?
55
+ if layer.total_exclusive_time > ScoutApm::SlowTransaction::BACKTRACE_THRESHOLD
56
+ layer.store_backtrace(ScoutApm::SlowTransaction.backtrace_parser(caller))
57
+ end
58
+
59
+ if finalized?
60
+ stop_request
61
+ end
62
+ end
63
+
64
+ ###################################
65
+ # Request Lifecycle
66
+ ###################################
67
+
68
+ # Are we finished with this request?
69
+ # We're done if we have no layers left after popping one off
70
+ def finalized?
71
+ @layers.none?
72
+ end
73
+
74
+ # Run at the beginning of the whole request
75
+ #
76
+ # * Capture the first layer as the root_layer
77
+ # * Start Stackprof
78
+ def start_request(layer)
79
+ @root_layer = layer unless @root_layer # capture root layer
80
+ StackProf.start(:mode => :wall, :interval => ScoutApm::Agent.instance.config.value("stackprof_interval"))
81
+ end
82
+
83
+ # Run at the end of the whole request
84
+ #
85
+ # * Collect stackprof info
86
+ # * Send the request off to be stored
87
+ def stop_request
88
+ StackProf.stop
89
+ @stackprof = StackProf.results
90
+
91
+ record!
92
+ end
93
+
94
+ ###################################
95
+ # Annotations
96
+ ###################################
97
+
98
+ # As we learn things about this request, we can add data here.
99
+ # For instance, when we know where Rails routed this request to, we can store that scope info.
100
+ # Or as soon as we know which URI it was directed at, we can store that.
101
+ #
102
+ # This data is internal to ScoutApm, to add custom information, use the Context api.
103
+ def annotate_request(hsh)
104
+ @annotations.merge!(hsh)
105
+ end
106
+
107
+ # This request had an exception. Mark it down as an error
108
+ def error!
109
+ @error = true
110
+ end
111
+
112
+ def error?
113
+ @error
114
+ end
115
+
116
+ def set_headers(headers)
117
+ @headers = headers
118
+ end
119
+
120
+ ###################################
121
+ # Persist the Request
122
+ ###################################
123
+
124
+ # Convert this request to the appropriate structure, then report it into
125
+ # the peristent Store object
126
+ def record!
127
+ @recorded = true
128
+
129
+ metrics = LayerMetricConverter.new(self).call
130
+ ScoutApm::Agent.instance.store.track!(metrics)
131
+
132
+ slow = LayerSlowTransactionConverter.new(self).call
133
+ ScoutApm::Agent.instance.store.track_slow_transaction!(slow)
134
+
135
+ error_metrics = LayerErrorConverter.new(self).call
136
+ ScoutApm::Agent.instance.store.track!(error_metrics)
137
+
138
+ queue_time_metrics = RequestQueueTime.new(self).call
139
+ ScoutApm::Agent.instance.store.track!(queue_time_metrics)
140
+ end
141
+
142
+ # Have we already persisted this request?
143
+ # Used to know when we should just create a new one (don't attempt to add
144
+ # data to an already-recorded request). See RequestManager
145
+ def recorded?
146
+ @recorded
147
+ end
148
+
149
+ ###################################
150
+ # Ignoring Children
151
+ ###################################
152
+
153
+ # Enable this when you would otherwise double track something interesting.
154
+ # This came up when we implemented InfluxDB instrumentation, which is more
155
+ # specific, and useful than the fact that InfluxDB happens to use Net::HTTP
156
+ # internally
157
+ #
158
+ # When enabled, new layers won't be added to the current Request.
159
+
160
+ def ignore_children!
161
+ @ignoring_children = true
162
+ end
163
+
164
+ def acknowledge_children!
165
+ @ignoring_children = false
166
+ end
167
+ end
168
+ end
@@ -1,4 +1,4 @@
1
1
  module ScoutApm
2
- VERSION = "1.1.0.pre1"
2
+ VERSION = "1.2.0.pre1"
3
3
  end
4
4
 
data/lib/scout_apm.rb CHANGED
@@ -10,8 +10,9 @@ require 'net/http'
10
10
  require 'openssl'
11
11
  require 'set'
12
12
  require 'socket'
13
- require 'yaml'
14
13
  require 'thread'
14
+ require 'time'
15
+ require 'yaml'
15
16
 
16
17
  #####################################
17
18
  # Gem Requires
@@ -27,6 +28,12 @@ end
27
28
  #####################################
28
29
  require 'scout_apm/version'
29
30
 
31
+ require 'scout_apm/tracked_request'
32
+ require 'scout_apm/layer'
33
+ require 'scout_apm/request_manager'
34
+ require 'scout_apm/layer_converter'
35
+ require 'scout_apm/request_queue_time'
36
+
30
37
  require 'scout_apm/server_integrations/passenger'
31
38
  require 'scout_apm/server_integrations/puma'
32
39
  require 'scout_apm/server_integrations/rainbows'
@@ -35,6 +42,9 @@ require 'scout_apm/server_integrations/unicorn'
35
42
  require 'scout_apm/server_integrations/webrick'
36
43
  require 'scout_apm/server_integrations/null'
37
44
 
45
+ require 'scout_apm/background_job_integrations/sidekiq'
46
+ require 'scout_apm/background_job_integrations/delayed_job'
47
+
38
48
  require 'scout_apm/framework_integrations/rails_2'
39
49
  require 'scout_apm/framework_integrations/rails_3_or_4'
40
50
  require 'scout_apm/framework_integrations/sinatra'
@@ -50,9 +60,11 @@ require 'scout_apm/deploy_integrations/capistrano_3'
50
60
  require 'scout_apm/instruments/net_http'
51
61
  require 'scout_apm/instruments/moped'
52
62
  require 'scout_apm/instruments/mongoid'
63
+ require 'scout_apm/instruments/delayed_job'
53
64
  require 'scout_apm/instruments/active_record'
54
65
  require 'scout_apm/instruments/action_controller_rails_2'
55
66
  require 'scout_apm/instruments/action_controller_rails_3'
67
+ require 'scout_apm/instruments/middleware'
56
68
  require 'scout_apm/instruments/sinatra'
57
69
  require 'scout_apm/instruments/process/process_cpu'
58
70
  require 'scout_apm/instruments/process/process_memory'
@@ -98,13 +110,14 @@ if defined?(Rails) && defined?(Rails::VERSION) && defined?(Rails::VERSION::MAJOR
98
110
  module ScoutApm
99
111
  class Railtie < Rails::Railtie
100
112
  initializer "scout_apm.start" do |app|
113
+ # attempt to start on first-request if not otherwise started, which is
114
+ # a good catch-all for Webrick, and Passenger and similar, where we
115
+ # can't detect the running app server until actual requests come in.
116
+ app.middleware.use ScoutApm::Middleware
117
+
101
118
  # Attempt to start right away, this will work best for preloading apps, Unicorn & Puma & similar
102
119
  ScoutApm::Agent.instance.start
103
120
 
104
- # And attempt to start on first-request, which is a good catch-all for
105
- # Webrick, and Passenger and similar, where we can't detect the running app server
106
- # until actual requests come in.
107
- app.middleware.use ScoutApm::Middleware
108
121
  end
109
122
  end
110
123
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0.pre1
4
+ version: 1.2.0.pre1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Haynes
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-12-02 00:00:00.000000000 Z
12
+ date: 2015-12-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -72,6 +72,8 @@ files:
72
72
  - lib/scout_apm/agent/reporting.rb
73
73
  - lib/scout_apm/app_server_load.rb
74
74
  - lib/scout_apm/attribute_arranger.rb
75
+ - lib/scout_apm/background_job_integrations/delayed_job.rb
76
+ - lib/scout_apm/background_job_integrations/sidekiq.rb
75
77
  - lib/scout_apm/background_worker.rb
76
78
  - lib/scout_apm/bucket_name_splitter.rb
77
79
  - lib/scout_apm/capacity.rb
@@ -89,6 +91,8 @@ files:
89
91
  - lib/scout_apm/instruments/action_controller_rails_2.rb
90
92
  - lib/scout_apm/instruments/action_controller_rails_3.rb
91
93
  - lib/scout_apm/instruments/active_record.rb
94
+ - lib/scout_apm/instruments/delayed_job.rb
95
+ - lib/scout_apm/instruments/middleware.rb
92
96
  - lib/scout_apm/instruments/mongoid.rb
93
97
  - lib/scout_apm/instruments/moped.rb
94
98
  - lib/scout_apm/instruments/net_http.rb
@@ -97,6 +101,8 @@ files:
97
101
  - lib/scout_apm/instruments/sinatra.rb
98
102
  - lib/scout_apm/layaway.rb
99
103
  - lib/scout_apm/layaway_file.rb
104
+ - lib/scout_apm/layer.rb
105
+ - lib/scout_apm/layer_converter.rb
100
106
  - lib/scout_apm/metric_meta.rb
101
107
  - lib/scout_apm/metric_stats.rb
102
108
  - lib/scout_apm/middleware.rb
@@ -104,6 +110,8 @@ files:
104
110
  - lib/scout_apm/platform_integrations/heroku.rb
105
111
  - lib/scout_apm/platform_integrations/server.rb
106
112
  - lib/scout_apm/reporter.rb
113
+ - lib/scout_apm/request_manager.rb
114
+ - lib/scout_apm/request_queue_time.rb
107
115
  - lib/scout_apm/serializers/app_server_load_serializer.rb
108
116
  - lib/scout_apm/serializers/deploy_serializer.rb
109
117
  - lib/scout_apm/serializers/directive_serializer.rb
@@ -121,6 +129,7 @@ files:
121
129
  - lib/scout_apm/stackprof_tree_collapser.rb
122
130
  - lib/scout_apm/store.rb
123
131
  - lib/scout_apm/tracer.rb
132
+ - lib/scout_apm/tracked_request.rb
124
133
  - lib/scout_apm/utils/fake_stack_prof.rb
125
134
  - lib/scout_apm/utils/installed_gems.rb
126
135
  - lib/scout_apm/utils/null_logger.rb