scout_apm 2.0.0.pre2 → 2.0.0.pre3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 119d6658eaefd7e3b177c1e982006f0d59dd208b
4
- data.tar.gz: 5585bca449804241d8778a1ff716444c50ac86d8
3
+ metadata.gz: d49313e6479d1530b66fcc237fbe105646dd33d8
4
+ data.tar.gz: bfc290ad58b83f3c3509cc81ffb14d9d08ab1237
5
5
  SHA512:
6
- metadata.gz: 5e6ad957c5948b239d530e9cc4e35445a37abb1331ef3c20357371a1c5da9286e4b9d38e84df2aecedd23a39e1a5d0163d76dd76549e41c700b78e15f770338d
7
- data.tar.gz: 060b1d7a09e6e5c37cd1a087bd7bf13faaab3e3b5b3aaf8d7f4173a50fa97c76d39c4ccaf299b05cdd48232a6b7638caeab88674110072a26692ad77fad2533e
6
+ metadata.gz: 420825d2d15e709e0d07209ff6d32c03fb51400123b31b880de67dd1c1890e4ab23421a05830d1821569db135aa4f30ab9e1b9b82e1112b713d0b09a797c8f5b
7
+ data.tar.gz: 21d53aadf8fbb4584e5c5a75437f6b1433f28d7792c4715435b49f7f0170b619fc38246bae2e8b64b3e6acd546c57b7eead417d8ddafaafa1a34abd3c2f8f777
data/CHANGELOG.markdown CHANGED
@@ -8,6 +8,15 @@
8
8
  * seconds_since_startup (larger memory increases and other other odd behavior more common when close to startup)
9
9
  * Initial support for instant traces
10
10
  * Collect 95th percentiles
11
+ * Remove unused & old references to Stackprof
12
+
13
+ # 1.6.0
14
+
15
+ * Dynamic algorithm for selecting when to collect traces. Now, we will collect a
16
+ more complete cross-section of your application's performance, dynamically
17
+ tuned as your application runs.
18
+ * Record and report 95th percentiles for each action
19
+ * A variety of bug fixes
11
20
 
12
21
  # 1.5.5
13
22
 
@@ -24,8 +24,9 @@ module ScoutApm
24
24
  # Histogram of the cumulative requests since the start of the process
25
25
  attr_reader :request_histograms
26
26
 
27
- # Histogram of the requests since last reset. Reset by the sampler, so once per minutes.
28
- attr_reader :request_histograms_resettable
27
+ # Histogram of the requests, distinct by reporting period (minute)
28
+ # { StoreReportingPeriodTimestamp => RequestHistograms }
29
+ attr_reader :request_histograms_by_time
29
30
 
30
31
  # All access to the agent is thru this class method to ensure multiple Agent instances are not initialized per-Ruby process.
31
32
  def self.instance(options = {})
@@ -50,7 +51,7 @@ module ScoutApm
50
51
  @slow_request_policy = ScoutApm::SlowRequestPolicy.new
51
52
  @slow_job_policy = ScoutApm::SlowJobPolicy.new
52
53
  @request_histograms = ScoutApm::RequestHistograms.new
53
- @request_histograms_resettable = ScoutApm::RequestHistograms.new
54
+ @request_histograms_by_time = Hash.new { |h, k| h[k] = ScoutApm::RequestHistograms.new }
54
55
 
55
56
  @store = ScoutApm::Store.new
56
57
  @layaway = ScoutApm::Layaway.new
@@ -124,11 +125,10 @@ module ScoutApm
124
125
 
125
126
  load_instruments if should_load_instruments?(options)
126
127
 
127
- @samplers = [
128
- ScoutApm::Instruments::Process::ProcessCpu.new(environment.processors, logger),
128
+ [ ScoutApm::Instruments::Process::ProcessCpu.new(environment.processors, logger),
129
129
  ScoutApm::Instruments::Process::ProcessMemory.new(logger),
130
130
  ScoutApm::Instruments::PercentileSampler.new(logger, 95),
131
- ]
131
+ ].each { |s| store.add_sampler(s) }
132
132
 
133
133
  app_server_load_hook
134
134
 
@@ -242,16 +242,19 @@ module ScoutApm
242
242
  @background_worker = ScoutApm::BackgroundWorker.new
243
243
  @background_worker_thread = Thread.new do
244
244
  @background_worker.start {
245
- # First, run periodic samplers. These should run once a minute,
246
- # rather than per-request. "CPU Load" and similar.
247
- run_samplers
248
- capacity.process
249
-
250
245
  ScoutApm::Agent.instance.process_metrics
246
+ clean_old_percentiles
251
247
  }
252
248
  end
253
249
  end
254
250
 
251
+ def clean_old_percentiles
252
+ request_histograms_by_time.
253
+ keys.
254
+ select {|timestamp| timestamp.age_in_seconds > 60 * 10 }.
255
+ each {|old_timestamp| request_histograms_by_time.delete(old_timestamp) }
256
+ end
257
+
255
258
  # If we want to skip the app_server_check, then we must load it.
256
259
  def should_load_instruments?(options={})
257
260
  return true if options[:skip_app_server_check]
@@ -284,10 +287,6 @@ module ScoutApm
284
287
  install_instrument(ScoutApm::Instruments::Redis)
285
288
  install_instrument(ScoutApm::Instruments::InfluxDB)
286
289
  install_instrument(ScoutApm::Instruments::Elasticsearch)
287
-
288
- if StackProf.respond_to?(:fake?) && StackProf.fake?
289
- logger.info 'StackProf not found - add `gem "stackprof"` to your Gemfile to enable advanced code profiling (only for Ruby 2.1+)'
290
- end
291
290
  rescue
292
291
  logger.warn "Exception loading instruments:"
293
292
  logger.warn $!.message
@@ -314,24 +313,6 @@ module ScoutApm
314
313
  environment.deploy_integration
315
314
  end
316
315
 
317
- # TODO: Extract a proper class / registery for these. They don't really belong here
318
- def run_samplers
319
- @samplers.each do |sampler|
320
- begin
321
- if sampler.respond_to? :metrics
322
- store.track!(sampler.metrics)
323
- else
324
- result = sampler.run
325
- store.track_one!(sampler.metric_type, sampler.metric_name, result) if result
326
- end
327
- rescue => e
328
- logger.info "Error reading #{sampler.human_name}"
329
- logger.debug e.message
330
- logger.debug e.backtrace.join("\n")
331
- end
332
- end
333
- end
334
-
335
316
  def app_server_missing?(options = {})
336
317
  !environment.app_server_integration(true).found? && !options[:skip_app_server_check]
337
318
  end
@@ -68,7 +68,6 @@ module ScoutApm
68
68
  'host' => 'https://checkin.scoutapp.com',
69
69
  'direct_host' => 'https://apm.scoutapp.com',
70
70
  'log_level' => 'info',
71
- 'stackprof_interval' => 20000, # microseconds, 1000 = 1 millisecond, so 20k == 20 milliseconds
72
71
  'uri_reporting' => 'full_path',
73
72
  'report_format' => 'json',
74
73
  'disabled_instruments' => [],
@@ -14,25 +14,24 @@ module ScoutApm
14
14
  "Percentiles"
15
15
  end
16
16
 
17
- def metrics
17
+ # Gets the 95th%ile for the time requested
18
+ def metrics(time)
18
19
  ms = {}
19
-
20
- ScoutApm::Agent.instance.request_histograms_resettable.each_name do |name|
20
+ histos = ScoutApm::Agent.instance.request_histograms_by_time[time]
21
+ histos.each_name do |name|
21
22
  percentiles.each do |percentile|
22
23
  meta = MetricMeta.new("Percentile/#{percentile}/#{name}")
23
24
  stat = MetricStats.new
24
- stat.update!(ScoutApm::Agent.instance.request_histograms_resettable.quantile(name, percentile))
25
+ stat.update!(histos.quantile(name, percentile))
25
26
  ms[meta] = stat
26
27
  end
27
28
  end
28
29
 
29
- # Wipe the histograms, get ready for the next minute's worth of data.
30
- ScoutApm::Agent.instance.request_histograms_resettable.reset_all!
30
+ # Wipe the histograms we just collected data on
31
+ ScoutApm::Agent.instance.request_histograms_by_time.delete(time)
31
32
 
32
33
  ms
33
34
  end
34
-
35
- private
36
35
  end
37
36
  end
38
37
  end
@@ -29,6 +29,18 @@ module ScoutApm
29
29
  "Process CPU"
30
30
  end
31
31
 
32
+ def metrics(_time)
33
+ result = run
34
+ if result
35
+ meta = MetricMeta.new("#{metric_type}/#{metric_name}")
36
+ stat = MetricStats.new(false)
37
+ stat.update!(result)
38
+ { meta => stat }
39
+ else
40
+ {}
41
+ end
42
+ end
43
+
32
44
  # TODO: Figure out a good default instead of nil
33
45
  def run
34
46
  res = nil
@@ -33,6 +33,18 @@ module ScoutApm
33
33
  "Process Memory"
34
34
  end
35
35
 
36
+ def metrics(_time)
37
+ result = run
38
+ if result
39
+ meta = MetricMeta.new("#{metric_type}/#{metric_name}")
40
+ stat = MetricStats.new(false)
41
+ stat.update!(result)
42
+ { meta => stat }
43
+ else
44
+ {}
45
+ end
46
+ end
47
+
36
48
  def run
37
49
  self.class.rss_in_mb.tap { |res| logger.debug "#{human_name}: #{res.inspect}" }
38
50
  end
@@ -46,9 +46,6 @@ module ScoutApm
46
46
  end
47
47
  end
48
48
 
49
- # Disable stackprof output for now
50
- stackprof = [] # request.stackprof
51
-
52
49
  SlowTransaction.new(uri,
53
50
  scope.legacy_metric_name,
54
51
  root_layer.total_call_time,
@@ -56,7 +53,7 @@ module ScoutApm
56
53
  allocation_metrics,
57
54
  request.context,
58
55
  root_layer.stop_time,
59
- stackprof,
56
+ [], # stackprof, now unused.
60
57
  mem_delta,
61
58
  root_layer.total_allocations,
62
59
  @points)
@@ -11,7 +11,6 @@ module ScoutApm
11
11
  attr_reader :context
12
12
  attr_reader :time
13
13
  attr_reader :prof
14
- attr_reader :raw_prof
15
14
  attr_reader :mem_delta
16
15
  attr_reader :allocations
17
16
  attr_accessor :hostname # hack - we need to reset these server side.
@@ -25,9 +24,7 @@ module ScoutApm
25
24
  @allocation_metrics = allocation_metrics
26
25
  @context = context
27
26
  @time = time
28
- @prof = ScoutApm::StackprofTreeCollapser.new(raw_stackprof).call
29
- @raw_prof = raw_stackprof # Send whole data up to server
30
-
27
+ @prof = []
31
28
  @mem_delta = mem_delta
32
29
  @allocations = allocations
33
30
  @seconds_since_startup = (Time.now - ScoutApm::Agent.instance.process_start_time)
@@ -6,9 +6,13 @@ module ScoutApm
6
6
  # A hash of reporting periods. { StoreReportingPeriodTimestamp => StoreReportingPeriod }
7
7
  attr_reader :reporting_periods
8
8
 
9
+ # Used to pull metrics into each reporting period, as that reporting period is finished.
10
+ attr_reader :samplers
11
+
9
12
  def initialize
10
13
  @mutex = Mutex.new
11
14
  @reporting_periods = Hash.new { |h,k| h[k] = StoreReportingPeriod.new(k) }
15
+ @samplers = []
12
16
  end
13
17
 
14
18
  def current_timestamp
@@ -66,12 +70,32 @@ module ScoutApm
66
70
  @mutex.synchronize {
67
71
  reporting_periods.select { |time, rp| force || time.timestamp < current_timestamp.timestamp}.
68
72
  each { |time, rp|
73
+ collect_samplers(rp)
69
74
  layaway.add_reporting_period(time, rp)
70
75
  reporting_periods.delete(time)
71
76
  }
72
77
  }
73
78
  ScoutApm::Agent.instance.logger.debug("Finished writing to layaway")
74
79
  end
80
+
81
+ ######################################
82
+ # Sampler support
83
+ def add_sampler(sampler)
84
+ @samplers << sampler
85
+ end
86
+
87
+ def collect_samplers(rp)
88
+ @samplers.each do |sampler|
89
+ begin
90
+ metrics = sampler.metrics(rp.timestamp)
91
+ rp.absorb_metrics!(metrics)
92
+ rescue => e
93
+ ScoutApm::Agent.instance.logger.info "Error reading #{sampler.human_name} for period: #{rp}"
94
+ ScoutApm::Agent.instance.logger.debug e.message
95
+ ScoutApm::Agent.instance.logger.debug e.backtrace.join("\n")
96
+ end
97
+ end
98
+ end
75
99
  end
76
100
 
77
101
  # A timestamp, normalized to the beginning of a minute. Used as a hash key to
@@ -22,10 +22,6 @@ module ScoutApm
22
22
  # :queue_latency - how long a background Job spent in the queue before starting processing
23
23
  attr_reader :annotations
24
24
 
25
- # Nil until the request is finalized, at which point it will hold the
26
- # entire raw stackprof output for this request
27
- attr_reader :stackprof
28
-
29
25
  # Headers as recorded by rails
30
26
  # Can be nil if we never reach a Rails Controller
31
27
  attr_reader :headers
@@ -52,7 +48,6 @@ module ScoutApm
52
48
  @ignoring_children = false
53
49
  @context = Context.new
54
50
  @root_layer = nil
55
- @stackprof = nil
56
51
  @error = false
57
52
  @instant_key = nil
58
53
  @mem_start = mem_usage
@@ -158,21 +153,14 @@ module ScoutApm
158
153
  # Run at the beginning of the whole request
159
154
  #
160
155
  # * Capture the first layer as the root_layer
161
- # * Start Stackprof (disabling to avoid conflicts if stackprof is included as middleware since we aren't sending this up to server now)
162
156
  def start_request(layer)
163
157
  @root_layer = layer unless @root_layer # capture root layer
164
- #StackProf.start(:mode => :wall, :interval => ScoutApm::Agent.instance.config.value("stackprof_interval"))
165
158
  end
166
159
 
167
160
  # Run at the end of the whole request
168
161
  #
169
- # * Collect stackprof info
170
162
  # * Send the request off to be stored
171
163
  def stop_request
172
- # ScoutApm::Agent.instance.logger.debug("stop_request: #{annotations[:uri]}" )
173
- #StackProf.stop # disabling to avoid conflicts if stackprof is included as middleware since we aren't sending this up to server now
174
- #@stackprof = StackProf.results
175
-
176
164
  record!
177
165
  end
178
166
 
@@ -234,7 +222,8 @@ module ScoutApm
234
222
  # Update immediate and long-term histograms for both job and web requests
235
223
  if unique_name != :unknown
236
224
  ScoutApm::Agent.instance.request_histograms.add(unique_name, root_layer.total_call_time)
237
- ScoutApm::Agent.instance.request_histograms_resettable.add(unique_name, root_layer.total_call_time)
225
+ ScoutApm::Agent.instance.request_histograms_by_time[ScoutApm::Agent.instance.store.current_timestamp].
226
+ add(unique_name, root_layer.total_call_time)
238
227
  end
239
228
 
240
229
  metrics = LayerConverters::MetricConverter.new(self).call
@@ -1,4 +1,4 @@
1
1
  module ScoutApm
2
- VERSION = "2.0.0.pre2"
2
+ VERSION = "2.0.0.pre3"
3
3
  end
4
4
 
data/lib/scout_apm.rb CHANGED
@@ -18,11 +18,6 @@ require 'yaml'
18
18
  #####################################
19
19
  # Gem Requires
20
20
  #####################################
21
- begin
22
- require 'stackprof'
23
- rescue LoadError
24
- require 'scout_apm/utils/fake_stack_prof'
25
- end
26
21
  require 'rusage'
27
22
 
28
23
  #####################################
@@ -116,7 +111,6 @@ require 'scout_apm/metric_set'
116
111
  require 'scout_apm/store'
117
112
  require 'scout_apm/tracer'
118
113
  require 'scout_apm/context'
119
- require 'scout_apm/stackprof_tree_collapser'
120
114
  require 'scout_apm/instant_reporting'
121
115
 
122
116
  require 'scout_apm/metric_meta'
@@ -6,7 +6,6 @@ require 'scout_apm/serializers/payload_serializer_to_json'
6
6
  require 'scout_apm/slow_transaction'
7
7
  require 'scout_apm/metric_meta'
8
8
  require 'scout_apm/metric_stats'
9
- require 'scout_apm/stackprof_tree_collapser'
10
9
  require 'scout_apm/utils/fake_stack_prof'
11
10
  require 'scout_apm/context'
12
11
  require 'ostruct'
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: 2.0.0.pre2
4
+ version: 2.0.0.pre3
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: 2016-06-01 00:00:00.000000000 Z
12
+ date: 2016-06-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rusage
@@ -200,13 +200,11 @@ files:
200
200
  - lib/scout_apm/slow_request_policy.rb
201
201
  - lib/scout_apm/slow_transaction.rb
202
202
  - lib/scout_apm/stack_item.rb
203
- - lib/scout_apm/stackprof_tree_collapser.rb
204
203
  - lib/scout_apm/store.rb
205
204
  - lib/scout_apm/tracer.rb
206
205
  - lib/scout_apm/tracked_request.rb
207
206
  - lib/scout_apm/utils/active_record_metric_name.rb
208
207
  - lib/scout_apm/utils/backtrace_parser.rb
209
- - lib/scout_apm/utils/fake_stack_prof.rb
210
208
  - lib/scout_apm/utils/installed_gems.rb
211
209
  - lib/scout_apm/utils/klass_helper.rb
212
210
  - lib/scout_apm/utils/null_logger.rb
@@ -1,103 +0,0 @@
1
- module ScoutApm
2
- class StackprofTreeCollapser
3
- attr_reader :raw_stackprof
4
- attr_reader :nodes
5
-
6
- def initialize(raw_stackprof)
7
- @raw_stackprof = raw_stackprof
8
-
9
- # Log the raw stackprof info
10
- #unless StackProf.respond_to?(:fake?) && StackProf.fake?
11
- # begin
12
- # ScoutApm::Agent.instance.logger.debug("StackProf - Samples: #{raw_stackprof[:samples]}, GC: #{raw_stackprof[:gc_samples]}, missed: #{raw_stackprof[:missed_samples]}, Interval: #{raw_stackprof[:interval]}")
13
- # rescue
14
- # ScoutApm::Agent.instance.logger.debug("StackProf Raw - #{raw_stackprof.inspect}")
15
- # end
16
- #end
17
- end
18
-
19
- def call
20
- build_tree
21
- connect_children
22
- total_samples_of_app_nodes
23
- rescue
24
- []
25
- end
26
-
27
- private
28
-
29
- def build_tree
30
- @nodes = raw_stackprof[:frames].map do |(frame_id, frame_data)|
31
- TreeNode.new(frame_id, # frame_id
32
- frame_data[:name], # name
33
- frame_data[:file], # file
34
- frame_data[:line], # line
35
- frame_data[:samples], # samples
36
- frame_data[:total_samples], # total_samples
37
- (frame_data[:edges] || {}), # children_edges [ { id => weight } ]
38
- [], # children [ treenode, ... ]
39
- [] # parents [ [treenode, int (weight) ], [...] ]
40
- )
41
- end
42
- end
43
-
44
- def connect_children
45
- nodes.each do |node|
46
- children = nodes.find_all { |n| node.children_edges.keys.include? n.frame_id }
47
-
48
- node.children_edges.each do |(frame_id, weight)|
49
- child = children.detect{ |c| c.frame_id == frame_id }
50
- child.parents << [node, weight]
51
- end
52
-
53
- node.children = children
54
- end
55
- end
56
-
57
- def in_app_nodes
58
- nodes.select {|n| n.app? }
59
- end
60
-
61
- def total_samples_of_app_nodes
62
- in_app_nodes.reject{|n| n.calls_only_app_nodes? && !n.has_samples? }.
63
- map{|n| { :samples => n.total_samples,
64
- :name => n.name,
65
- :file => n.file,
66
- :line => n.line
67
- }
68
- }
69
- end
70
-
71
- ###########################################
72
- # TreeNode class represents a single node.
73
- ###########################################
74
- TreeNode = Struct.new(:frame_id, :name, :file, :line, :samples, :total_samples,
75
- :children_edges, :children, :parents) do
76
- def app?
77
- @is_app ||= file =~ /^#{ScoutApm::Environment.instance.root}/
78
- end
79
-
80
- # Force object_id to be the equality mechanism, rather than struct's
81
- # default which delegates to == on each value. That is wrong because
82
- # we want to be able to dup a node in the tree construction process and
83
- # not have those compare equal to each other.
84
- def ==(other)
85
- object_id == other.object_id
86
- end
87
-
88
- def inspect
89
- "#{frame_id}: #{name} - ##{samples}\n" +
90
- " Parents: #{parents.map{ |(p, w)| "#{p.name}: #{w}"}.join("\n ") }\n" +
91
- " Children: #{children_edges.inspect} \n"
92
- end
93
-
94
- def calls_only_app_nodes?
95
- children.all?(&:app?)
96
- end
97
-
98
- def has_samples?
99
- samples > 0
100
- end
101
- end
102
- end
103
- end
@@ -1,40 +0,0 @@
1
- # A fake implementation of stackprof, for systems that don't support it.
2
- module StackProf
3
- def self.start(*args)
4
- @running = true
5
- end
6
-
7
- def self.stop(*args)
8
- @running = false
9
- end
10
-
11
- def self.running?
12
- !!@running
13
- end
14
-
15
- def self.run(*args)
16
- start
17
- yield
18
- stop
19
- results
20
- end
21
-
22
- def self.sample(*args)
23
- end
24
-
25
- def self.results(*args)
26
- {
27
- :version => 0.0,
28
- :mode => :wall,
29
- :interval => 1000,
30
- :samples => 0,
31
- :gc_samples => 0,
32
- :missed_samples => 0,
33
- :frames => {},
34
- }
35
- end
36
-
37
- def self.fake?
38
- true
39
- end
40
- end