scout_apm 1.1.0.pre1 → 1.2.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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