scout_apm 2.0.0.pre2 → 2.0.0.pre3

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