scout_apm 1.4.6 → 1.5.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +9 -0
  3. data/lib/scout_apm/agent/reporting.rb +8 -6
  4. data/lib/scout_apm/agent.rb +10 -6
  5. data/lib/scout_apm/background_job_integrations/sidekiq.rb +23 -11
  6. data/lib/scout_apm/call_set.rb +61 -0
  7. data/lib/scout_apm/config.rb +2 -1
  8. data/lib/scout_apm/environment.rb +12 -7
  9. data/lib/scout_apm/histogram.rb +124 -0
  10. data/lib/scout_apm/instruments/.DS_Store +0 -0
  11. data/lib/scout_apm/instruments/action_controller_rails_2.rb +1 -0
  12. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +1 -0
  13. data/lib/scout_apm/instruments/delayed_job.rb +1 -0
  14. data/lib/scout_apm/instruments/process/process_memory.rb +1 -1
  15. data/lib/scout_apm/instruments/sinatra.rb +1 -1
  16. data/lib/scout_apm/job_record.rb +76 -0
  17. data/lib/scout_apm/layaway.rb +4 -1
  18. data/lib/scout_apm/layaway_file.rb +4 -4
  19. data/lib/scout_apm/layer.rb +14 -4
  20. data/lib/scout_apm/layer_converters/converter_base.rb +30 -0
  21. data/lib/scout_apm/layer_converters/depth_first_walker.rb +36 -0
  22. data/lib/scout_apm/layer_converters/error_converter.rb +20 -0
  23. data/lib/scout_apm/layer_converters/job_converter.rb +84 -0
  24. data/lib/scout_apm/layer_converters/metric_converter.rb +45 -0
  25. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +60 -0
  26. data/lib/scout_apm/layer_converters/slow_job_converter.rb +88 -0
  27. data/lib/scout_apm/layer_converters/slow_request_converter.rb +111 -0
  28. data/lib/scout_apm/metric_meta.rb +9 -0
  29. data/lib/scout_apm/metric_set.rb +44 -0
  30. data/lib/scout_apm/reporter.rb +12 -5
  31. data/lib/scout_apm/serializers/jobs_serializer_to_json.rb +28 -0
  32. data/lib/scout_apm/serializers/metrics_to_json_serializer.rb +54 -0
  33. data/lib/scout_apm/serializers/payload_serializer.rb +5 -3
  34. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +9 -4
  35. data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +29 -0
  36. data/lib/scout_apm/slow_item_set.rb +80 -0
  37. data/lib/scout_apm/slow_job_policy.rb +29 -0
  38. data/lib/scout_apm/slow_job_record.rb +33 -0
  39. data/lib/scout_apm/slow_transaction.rb +0 -22
  40. data/lib/scout_apm/stackprof_tree_collapser.rb +7 -8
  41. data/lib/scout_apm/store.rb +55 -35
  42. data/lib/scout_apm/tracked_request.rb +67 -10
  43. data/lib/scout_apm/utils/active_record_metric_name.rb +13 -0
  44. data/lib/scout_apm/utils/backtrace_parser.rb +31 -0
  45. data/lib/scout_apm/utils/fake_stack_prof.rb +1 -1
  46. data/lib/scout_apm/utils/sql_sanitizer.rb +6 -0
  47. data/lib/scout_apm/version.rb +1 -1
  48. data/lib/scout_apm.rb +25 -5
  49. data/test/unit/histogram_test.rb +93 -0
  50. data/test/unit/serializers/payload_serializer_test.rb +5 -5
  51. data/test/unit/{slow_transaction_set_test.rb → slow_item_set_test.rb} +8 -8
  52. data/test/unit/slow_job_policy_test.rb +55 -0
  53. metadata +30 -9
  54. data/lib/scout_apm/layer_converter.rb +0 -222
  55. data/lib/scout_apm/request_queue_time.rb +0 -57
  56. data/lib/scout_apm/slow_transaction_set.rb +0 -67
@@ -0,0 +1,93 @@
1
+ require 'test_helper'
2
+
3
+ require 'scout_apm/histogram'
4
+
5
+ # These tests assume insertion order. Because of the approximate nature of
6
+ # this data structure, changing the order can slightly change the results,
7
+ # causing these tests to fail. The overall "shape" of the histogram should be
8
+ # approximately the same in the case of alternate orders, but I'm punting on
9
+ # any attempts to automate that.
10
+ class HistogramTest < Minitest::Test
11
+ # When we have enough slots, we don't have any fuzz in the data, so exact numbers.
12
+ def test_histogram_min_and_max_with_big_enough_histogram
13
+ hist = ScoutApm::NumericHistogram.new(10)
14
+
15
+ 10.times {
16
+ (1..10).to_a.each do |i|
17
+ hist.add(i)
18
+ end
19
+ }
20
+
21
+ assert_equal 1, hist.quantile(0)
22
+ assert_equal 10, hist.quantile(100)
23
+ end
24
+
25
+ # When we don't have enough slots, we have to approximate the buckets
26
+ # In this case, the true range is 1 through 10, and we only have 5 buckets to allocate.
27
+ # 1 2 3 4 5 6 7 8 9 10 # <-- True Range
28
+ # x x x x x # <-- Where buckets get adjusted to
29
+ def test_histogram_min_and_max_with_fewer_buckets
30
+ hist = ScoutApm::NumericHistogram.new(5)
31
+
32
+ 10.times {
33
+ (1..10).to_a.each do |i|
34
+ hist.add(i)
35
+ end
36
+ }
37
+
38
+ assert_equal 1.5, hist.quantile(0).round(1)
39
+ assert_equal 9.5, hist.quantile(100).round(1)
40
+ end
41
+
42
+ def test_combine
43
+ hist1 = ScoutApm::NumericHistogram.new(5)
44
+ 10.times {
45
+ (1..10).to_a.each do |i|
46
+ hist1.add(i)
47
+ end
48
+ }
49
+
50
+ hist2 = ScoutApm::NumericHistogram.new(10)
51
+ 10.times {
52
+ (1..10).to_a.each do |i|
53
+ hist2.add(i)
54
+ end
55
+ }
56
+
57
+ combined = hist1.combine!(hist2)
58
+ assert_equal 1.5, combined.quantile(0).round(1)
59
+ assert_equal 9.5, combined.quantile(100).round(1)
60
+ assert_equal 200, combined.total
61
+ end
62
+
63
+ def test_combine_retains_order
64
+ hist1 = ScoutApm::NumericHistogram.new(5)
65
+ 10.times {
66
+ (10..50).to_a.each do |i|
67
+ hist1.add(i)
68
+ end
69
+ }
70
+
71
+ hist2 = ScoutApm::NumericHistogram.new(10)
72
+ 10.times {
73
+ (1..10).to_a.each do |i|
74
+ hist2.add(i)
75
+ end
76
+ }
77
+
78
+ combined = hist1.combine!(hist2)
79
+ assert combined.quantile(0) < combined.quantile(100)
80
+ end
81
+
82
+ def test_mean
83
+ hist = ScoutApm::NumericHistogram.new(5)
84
+ 10.times {
85
+ (1..10).to_a.each do |i|
86
+ hist.add(i)
87
+ end
88
+ }
89
+
90
+ assert_equal 5.5, hist.mean
91
+ end
92
+ end
93
+
@@ -33,7 +33,7 @@ class PayloadSerializerTest < Minitest::Test
33
33
  :unique_id => "unique_idz",
34
34
  :agent_version => 123
35
35
  }
36
- payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, {}, {})
36
+ payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, {}, {}, [], [])
37
37
 
38
38
  # symbol keys turn to strings
39
39
  formatted_metadata = {
@@ -74,7 +74,7 @@ class PayloadSerializerTest < Minitest::Test
74
74
  stats.total_exclusive_time = 0.07813208899999999
75
75
  }
76
76
  }
77
- payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize({}, metrics, {})
77
+ payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize({}, metrics, {}, [], [])
78
78
  formatted_metrics = [
79
79
  {
80
80
  "key" => {
@@ -145,8 +145,8 @@ class PayloadSerializerTest < Minitest::Test
145
145
  context = ScoutApm::Context.new
146
146
  context.add({"this" => "that"})
147
147
  context.add_user({"hello" => "goodbye"})
148
- slow_t = ScoutApm::SlowTransaction.new("http://example.com/blabla", "Buckethead/something/else", 1.23, slow_transaction_metrics, context, Time.at(1448198788), [])
149
- payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize({}, {}, [slow_t])
148
+ slow_t = ScoutApm::SlowTransaction.new("http://example.com/blabla", "Buckethead/something/else", 1.23, slow_transaction_metrics, context, Time.at(1448198788), StackProf.new)
149
+ payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize({}, {}, [slow_t], [], [])
150
150
  formatted_slow_transactions = [
151
151
  {
152
152
  "key" => {
@@ -204,7 +204,7 @@ class PayloadSerializerTest < Minitest::Test
204
204
  :quotie => "here are some \"quotes\"",
205
205
  :payload_version => 2,
206
206
  }
207
- payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, {}, {})
207
+ payload = ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, {}, {}, [], [])
208
208
 
209
209
  # symbol keys turn to strings
210
210
  formatted_metadata = {
@@ -1,17 +1,17 @@
1
1
  require 'test_helper'
2
2
 
3
- require 'scout_apm/slow_transaction_set'
3
+ require 'scout_apm/slow_item_set'
4
4
  require 'scout_apm/slow_transaction'
5
5
 
6
- class SlowTransactionSetTest < Minitest::Test
6
+ class SlowItemSetTest < Minitest::Test
7
7
  def test_adding_to_empty_set
8
- set = ScoutApm::SlowTransactionSet.new(3, 1)
8
+ set = ScoutApm::SlowItemSet.new(3, 1)
9
9
  set << make_slow("Controller/Foo")
10
10
  assert_equal 1, set.count
11
11
  end
12
12
 
13
13
  def test_adding_to_partially_full_set
14
- set = ScoutApm::SlowTransactionSet.new(3, 1)
14
+ set = ScoutApm::SlowItemSet.new(3, 1)
15
15
  set << make_slow("Controller/Foo")
16
16
  set << make_slow("Controller/Foo")
17
17
  assert_equal 2, set.count
@@ -19,7 +19,7 @@ class SlowTransactionSetTest < Minitest::Test
19
19
 
20
20
  def test_overflow_of_one_type
21
21
  max_size = 3
22
- set = ScoutApm::SlowTransactionSet.new(max_size, 1)
22
+ set = ScoutApm::SlowItemSet.new(max_size, 1)
23
23
  set << make_slow("Controller/Foo")
24
24
  set << make_slow("Controller/Foo")
25
25
  set << make_slow("Controller/Foo")
@@ -31,7 +31,7 @@ class SlowTransactionSetTest < Minitest::Test
31
31
 
32
32
  def test_eviction_of_overrepresented
33
33
  max_size = 3
34
- set = ScoutApm::SlowTransactionSet.new(max_size, 1)
34
+ set = ScoutApm::SlowItemSet.new(max_size, 1)
35
35
  set << make_slow("Controller/Foo")
36
36
  set << make_slow("Controller/Foo")
37
37
  set << make_slow("Controller/Foo")
@@ -49,7 +49,7 @@ class SlowTransactionSetTest < Minitest::Test
49
49
  # evicted one was the fastest of the Foos
50
50
  def test_eviction_of_fastest
51
51
  max_size = 3
52
- set = ScoutApm::SlowTransactionSet.new(max_size, 1)
52
+ set = ScoutApm::SlowItemSet.new(max_size, 1)
53
53
 
54
54
  [1,2,3].shuffle.each do |seconds| # Shuffle to remove any assumptions on order
55
55
  set << make_slow("Controller/Foo", seconds)
@@ -63,7 +63,7 @@ class SlowTransactionSetTest < Minitest::Test
63
63
  def test_eviction_when_no_overrepresented
64
64
  max_size = 4
65
65
  fair = 2
66
- set = ScoutApm::SlowTransactionSet.new(max_size, fair)
66
+ set = ScoutApm::SlowItemSet.new(max_size, fair)
67
67
 
68
68
  # Full, but each is at fair level
69
69
  set << make_slow("Controller/Bar")
@@ -0,0 +1,55 @@
1
+ require 'test_helper'
2
+
3
+ require 'scout_apm/slow_job_policy'
4
+
5
+ class SlowJobPolicyTest < Minitest::Test
6
+ def test_first_call_is_not_slow
7
+ policy = ScoutApm::SlowJobPolicy.new
8
+ assert !policy.slow?("TestWorker", 10)
9
+ end
10
+
11
+ # All of these get faster and faster, so none are marked as slow.
12
+ def test_fast_calls_are_not_slow
13
+ policy = ScoutApm::SlowJobPolicy.new
14
+ assert !policy.slow?("TestWorker", 10)
15
+ assert !policy.slow?("TestWorker", 8)
16
+ assert !policy.slow?("TestWorker", 6)
17
+ assert !policy.slow?("TestWorker", 4)
18
+ assert !policy.slow?("TestWorker", 2)
19
+ end
20
+
21
+ def test_slow_calls_are_marked_as_slow
22
+ policy = ScoutApm::SlowJobPolicy.new
23
+ policy.slow?("TestWorker", 10) # Prime it with a not-slow
24
+
25
+ assert policy.slow?("TestWorker", 12)
26
+ assert policy.slow?("TestWorker", 14)
27
+ assert policy.slow?("TestWorker", 16)
28
+ assert policy.slow?("TestWorker", 18)
29
+ end
30
+
31
+ def test_mix_of_fast_and_slow
32
+ policy = ScoutApm::SlowJobPolicy.new
33
+ policy.slow?("TestWorker", 10) # Prime it with a not-slow
34
+
35
+ assert policy.slow?("TestWorker", 12)
36
+ assert !policy.slow?("TestWorker", 8)
37
+ assert policy.slow?("TestWorker", 13)
38
+ assert !policy.slow?("TestWorker", 6)
39
+ end
40
+
41
+ def test_different_workers_dont_interfere
42
+ policy = ScoutApm::SlowJobPolicy.new
43
+ policy.slow?("TestWorker", 10) # Prime it with a not-slow
44
+ policy.slow?("OtherWorker", 1.0) # Prime it with a not-slow
45
+
46
+ assert !policy.slow?("TestWorker", 8)
47
+ assert policy.slow?("OtherWorker", 2)
48
+ assert !policy.slow?("TestWorker", 1)
49
+ assert policy.slow?("OtherWorker", 3)
50
+ assert policy.slow?("TestWorker", 12)
51
+ assert !policy.slow?("OtherWorker", 1)
52
+ assert policy.slow?("TestWorker", 12)
53
+ assert policy.slow?("OtherWorker", 4)
54
+ end
55
+ 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.4.6
4
+ version: 1.5.0.pre
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-04-04 00:00:00.000000000 Z
12
+ date: 2016-04-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -76,6 +76,7 @@ files:
76
76
  - lib/scout_apm/background_job_integrations/sidekiq.rb
77
77
  - lib/scout_apm/background_worker.rb
78
78
  - lib/scout_apm/bucket_name_splitter.rb
79
+ - lib/scout_apm/call_set.rb
79
80
  - lib/scout_apm/capacity.rb
80
81
  - lib/scout_apm/config.rb
81
82
  - lib/scout_apm/context.rb
@@ -88,6 +89,8 @@ files:
88
89
  - lib/scout_apm/framework_integrations/rails_3_or_4.rb
89
90
  - lib/scout_apm/framework_integrations/ruby.rb
90
91
  - lib/scout_apm/framework_integrations/sinatra.rb
92
+ - lib/scout_apm/histogram.rb
93
+ - lib/scout_apm/instruments/.DS_Store
91
94
  - lib/scout_apm/instruments/action_controller_rails_2.rb
92
95
  - lib/scout_apm/instruments/action_controller_rails_3_rails4.rb
93
96
  - lib/scout_apm/instruments/active_record.rb
@@ -105,11 +108,20 @@ files:
105
108
  - lib/scout_apm/instruments/rails_router.rb
106
109
  - lib/scout_apm/instruments/redis.rb
107
110
  - lib/scout_apm/instruments/sinatra.rb
111
+ - lib/scout_apm/job_record.rb
108
112
  - lib/scout_apm/layaway.rb
109
113
  - lib/scout_apm/layaway_file.rb
110
114
  - lib/scout_apm/layer.rb
111
- - lib/scout_apm/layer_converter.rb
115
+ - lib/scout_apm/layer_converters/converter_base.rb
116
+ - lib/scout_apm/layer_converters/depth_first_walker.rb
117
+ - lib/scout_apm/layer_converters/error_converter.rb
118
+ - lib/scout_apm/layer_converters/job_converter.rb
119
+ - lib/scout_apm/layer_converters/metric_converter.rb
120
+ - lib/scout_apm/layer_converters/request_queue_time_converter.rb
121
+ - lib/scout_apm/layer_converters/slow_job_converter.rb
122
+ - lib/scout_apm/layer_converters/slow_request_converter.rb
112
123
  - lib/scout_apm/metric_meta.rb
124
+ - lib/scout_apm/metric_set.rb
113
125
  - lib/scout_apm/metric_stats.rb
114
126
  - lib/scout_apm/middleware.rb
115
127
  - lib/scout_apm/platform_integrations/cloud_foundry.rb
@@ -117,12 +129,14 @@ files:
117
129
  - lib/scout_apm/platform_integrations/server.rb
118
130
  - lib/scout_apm/reporter.rb
119
131
  - lib/scout_apm/request_manager.rb
120
- - lib/scout_apm/request_queue_time.rb
121
132
  - lib/scout_apm/serializers/app_server_load_serializer.rb
122
133
  - lib/scout_apm/serializers/deploy_serializer.rb
123
134
  - lib/scout_apm/serializers/directive_serializer.rb
135
+ - lib/scout_apm/serializers/jobs_serializer_to_json.rb
136
+ - lib/scout_apm/serializers/metrics_to_json_serializer.rb
124
137
  - lib/scout_apm/serializers/payload_serializer.rb
125
138
  - lib/scout_apm/serializers/payload_serializer_to_json.rb
139
+ - lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb
126
140
  - lib/scout_apm/server_integrations/null.rb
127
141
  - lib/scout_apm/server_integrations/passenger.rb
128
142
  - lib/scout_apm/server_integrations/puma.rb
@@ -130,15 +144,18 @@ files:
130
144
  - lib/scout_apm/server_integrations/thin.rb
131
145
  - lib/scout_apm/server_integrations/unicorn.rb
132
146
  - lib/scout_apm/server_integrations/webrick.rb
147
+ - lib/scout_apm/slow_item_set.rb
148
+ - lib/scout_apm/slow_job_policy.rb
149
+ - lib/scout_apm/slow_job_record.rb
133
150
  - lib/scout_apm/slow_request_policy.rb
134
151
  - lib/scout_apm/slow_transaction.rb
135
- - lib/scout_apm/slow_transaction_set.rb
136
152
  - lib/scout_apm/stack_item.rb
137
153
  - lib/scout_apm/stackprof_tree_collapser.rb
138
154
  - lib/scout_apm/store.rb
139
155
  - lib/scout_apm/tracer.rb
140
156
  - lib/scout_apm/tracked_request.rb
141
157
  - lib/scout_apm/utils/active_record_metric_name.rb
158
+ - lib/scout_apm/utils/backtrace_parser.rb
142
159
  - lib/scout_apm/utils/fake_stack_prof.rb
143
160
  - lib/scout_apm/utils/installed_gems.rb
144
161
  - lib/scout_apm/utils/null_logger.rb
@@ -154,10 +171,12 @@ files:
154
171
  - test/unit/agent_test.rb
155
172
  - test/unit/config_test.rb
156
173
  - test/unit/environment_test.rb
174
+ - test/unit/histogram_test.rb
157
175
  - test/unit/instruments/active_record_instruments_test.rb
158
176
  - test/unit/layaway_test.rb
159
177
  - test/unit/serializers/payload_serializer_test.rb
160
- - test/unit/slow_transaction_set_test.rb
178
+ - test/unit/slow_item_set_test.rb
179
+ - test/unit/slow_job_policy_test.rb
161
180
  - test/unit/sql_sanitizer_test.rb
162
181
  - test/unit/utils/active_record_metric_name_test.rb
163
182
  homepage: https://github.com/scoutapp/scout_apm_ruby
@@ -175,9 +194,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
175
194
  version: '0'
176
195
  required_rubygems_version: !ruby/object:Gem::Requirement
177
196
  requirements:
178
- - - ">="
197
+ - - ">"
179
198
  - !ruby/object:Gem::Version
180
- version: '0'
199
+ version: 1.3.1
181
200
  requirements: []
182
201
  rubyforge_project: scout_apm
183
202
  rubygems_version: 2.6.2
@@ -190,10 +209,12 @@ test_files:
190
209
  - test/unit/agent_test.rb
191
210
  - test/unit/config_test.rb
192
211
  - test/unit/environment_test.rb
212
+ - test/unit/histogram_test.rb
193
213
  - test/unit/instruments/active_record_instruments_test.rb
194
214
  - test/unit/layaway_test.rb
195
215
  - test/unit/serializers/payload_serializer_test.rb
196
- - test/unit/slow_transaction_set_test.rb
216
+ - test/unit/slow_item_set_test.rb
217
+ - test/unit/slow_job_policy_test.rb
197
218
  - test/unit/sql_sanitizer_test.rb
198
219
  - test/unit/utils/active_record_metric_name_test.rb
199
220
  has_rdoc:
@@ -1,222 +0,0 @@
1
- module ScoutApm
2
- class LayerConverterBase
3
- attr_reader :walker
4
- attr_reader :request
5
- attr_reader :root_layer
6
-
7
- def initialize(request)
8
- @request = request
9
- @root_layer = request.root_layer
10
- @walker = LayerDepthFirstWalker.new(root_layer)
11
- end
12
-
13
- # Scope is determined by the first Controller we hit. Most of the time
14
- # there will only be 1 anyway. But if you have a controller that calls
15
- # another controller method, we may pick that up:
16
- # def update
17
- # show
18
- # render :update
19
- # end
20
- def scope_layer
21
- @scope_layer ||= walker.walk do |layer|
22
- if layer.type == "Controller"
23
- break layer
24
- end
25
- end
26
- end
27
- end
28
-
29
- # Take a TrackedRequest and turn it into a hash of:
30
- # MetricMeta => MetricStats
31
- # This will do some merging of metrics as duplicate calls are seen.
32
- class LayerMetricConverter < LayerConverterBase
33
- def call
34
- scope = scope_layer
35
-
36
- # TODO: Track requests that never reach a Controller (for example, when
37
- # Middleware decides to return rather than passing onward)
38
- return {} unless scope
39
-
40
- create_metrics
41
- end
42
-
43
- # Full metrics from this request. These get aggregated in Store for the
44
- # overview metrics, or stored permanently in a SlowTransaction
45
- # Some merging of metrics will happen here, so if a request calls the same
46
- # ActiveRecord or View repeatedly, it'll get merged.
47
- def create_metrics
48
- metric_hash = Hash.new
49
-
50
- walker.walk do |layer|
51
- meta_options = if layer == scope_layer # We don't scope the controller under itself
52
- {}
53
- else
54
- {:scope => scope_layer.legacy_metric_name}
55
- end
56
-
57
- # we don't need to use the full metric name for scoped metrics as we only display metrics aggregrated
58
- # by type.
59
- metric_name = meta_options.has_key?(:scope) ? layer.type : layer.legacy_metric_name
60
-
61
- meta = MetricMeta.new(metric_name, meta_options)
62
- metric_hash[meta] ||= MetricStats.new( meta_options.has_key?(:scope) )
63
-
64
- stat = metric_hash[meta]
65
- stat.update!(layer.total_call_time, layer.total_exclusive_time)
66
- end
67
- metric_hash
68
- end
69
- end
70
-
71
- class LayerErrorConverter < LayerConverterBase
72
- def call
73
- scope = scope_layer
74
-
75
- # Should we mark a request as errored out if a middleware raises?
76
- # How does that interact w/ a tool like Sentry or Honeybadger?
77
- return {} unless scope
78
- return {} unless request.error?
79
-
80
- meta = MetricMeta.new("Errors/#{scope.legacy_metric_name}", {})
81
- stat = MetricStats.new
82
- stat.update!(1)
83
-
84
- { meta => stat }
85
- end
86
- end
87
-
88
- # Take a TrackedRequest and turn it into a slow transaction if needed
89
- # return a 2 element array, [ Slow Transaction or Nil , Hash of metrics to store ]
90
- class LayerSlowTransactionConverter < LayerConverterBase
91
- def call
92
- scope = scope_layer
93
- return [nil, {}] unless scope
94
-
95
- policy = ScoutApm::Agent.instance.slow_request_policy.capture_type(root_layer.total_call_time)
96
- if policy == ScoutApm::SlowRequestPolicy::CAPTURE_NONE
97
- return [nil, {}]
98
- end
99
-
100
- # increment the slow transaction count if this is a slow transaction.
101
- meta = MetricMeta.new("SlowTransaction/#{scope.legacy_metric_name}")
102
- stat = MetricStats.new
103
- stat.update!(1)
104
-
105
- uri = request.annotations[:uri] || ""
106
-
107
- metrics = create_metrics
108
- # Disable stackprof output for now
109
- stackprof = [] # request.stackprof
110
-
111
- [
112
- SlowTransaction.new(uri,
113
- scope.legacy_metric_name,
114
- root_layer.total_call_time,
115
- metrics,
116
- request.context,
117
- root_layer.stop_time,
118
- stackprof),
119
- { meta => stat }
120
- ]
121
-
122
- end
123
-
124
- # Full metrics from this request. These get aggregated in Store for the
125
- # overview metrics, or stored permanently in a SlowTransaction
126
- # Some merging of metrics will happen here, so if a request calls the same
127
- # ActiveRecord or View repeatedly, it'll get merged.
128
- def create_metrics
129
- metric_hash = Hash.new
130
-
131
- # Keep a list of subscopes, but only ever use the front one. The rest
132
- # get pushed/popped in cases when we have many levels of subscopable
133
- # layers. This lets us push/pop without otherwise keeping track very closely.
134
- subscope_layers = []
135
-
136
- walker.before do |layer|
137
- if layer.subscopable?
138
- subscope_layers.push(layer)
139
- end
140
- end
141
-
142
- walker.after do |layer|
143
- if layer.subscopable?
144
- subscope_layers.pop
145
- end
146
- end
147
-
148
- walker.walk do |layer|
149
- meta_options = if subscope_layers.first && layer != subscope_layers.first # Don't scope under ourself.
150
- subscope_name = subscope_layers.first.legacy_metric_name
151
- {:scope => subscope_name}
152
- elsif layer == scope_layer # We don't scope the controller under itself
153
- {}
154
- else
155
- {:scope => scope_layer.legacy_metric_name}
156
- end
157
-
158
- # Specific Metric
159
- meta_options.merge!(:desc => layer.desc.to_s) if layer.desc
160
- meta = MetricMeta.new(layer.legacy_metric_name, meta_options)
161
- meta.extra.merge!(:backtrace => ScoutApm::SlowTransaction.backtrace_parser(layer.backtrace)) if layer.backtrace
162
- metric_hash[meta] ||= MetricStats.new( meta_options.has_key?(:scope) )
163
- stat = metric_hash[meta]
164
- stat.update!(layer.total_call_time, layer.total_exclusive_time)
165
-
166
- # Merged Metric (no specifics, just sum up by type)
167
- meta = MetricMeta.new("#{layer.type}/all")
168
- metric_hash[meta] ||= MetricStats.new(false)
169
- stat = metric_hash[meta]
170
- stat.update!(layer.total_call_time, layer.total_exclusive_time)
171
- end
172
-
173
- metric_hash
174
- end
175
- end
176
-
177
- class LayerDepthFirstWalker
178
- attr_reader :root_layer
179
-
180
- def initialize(root_layer)
181
- @root_layer = root_layer
182
- end
183
-
184
- def before(&block)
185
- @before_block = block
186
- end
187
-
188
- def after(&block)
189
- @after_block = block
190
- end
191
-
192
- def walk(layer=root_layer, &block)
193
- # Need to run this for the root layer the first time through.
194
- if layer == root_layer
195
- @before_block.call(layer) if @before_block
196
- yield layer
197
- @after_block.call(layer) if @after_block
198
- end
199
-
200
- layer.children.each do |child|
201
- @before_block.call(child) if @before_block
202
- yield child
203
- walk(child, &block)
204
- @after_block.call(child) if @after_block
205
- end
206
- nil
207
- end
208
-
209
- # Do this w/o using recursion, since it's prone to stack overflows
210
- # Takes a block to run over each layer
211
- # def walk
212
- # layer_stack = [root_layer]
213
-
214
- # while layer_stack.any?
215
- # current_layer = layer_stack.pop
216
- # current_layer.children.reverse.each { |child| layer_stack.push(child) }
217
-
218
- # yield current_layer
219
- # end
220
- # end
221
- end
222
- end
@@ -1,57 +0,0 @@
1
- module ScoutApm
2
- class RequestQueueTime < LayerConverterBase
3
- HEADERS = %w(X-Queue-Start X-Request-Start X-QUEUE-START X-REQUEST-START x-queue-start x-request-start)
4
-
5
- # Headers is a hash of request headers. In Rails, request.headers would be appropriate
6
- def initialize(request)
7
- super(request)
8
- @headers = request.headers
9
- end
10
-
11
- def call
12
- return {} unless headers
13
-
14
- raw_start = locate_timestamp
15
- return {} unless raw_start
16
-
17
- parsed_start = parse(raw_start)
18
- return {} unless parsed_start
19
-
20
- request_start = root_layer.start_time
21
- queue_time = (request_start - parsed_start).to_f
22
-
23
- # If we end up with a negative value, just bail out and don't report anything
24
- return {} if queue_time < 0
25
-
26
- meta = MetricMeta.new("QueueTime/Request", {:scope => scope_layer.legacy_metric_name})
27
- stat = MetricStats.new(true)
28
- stat.update!(queue_time)
29
-
30
- { meta => stat }
31
- end
32
-
33
- private
34
-
35
- attr_reader :headers
36
-
37
- # Looks through the possible headers with this data, and extracts the raw
38
- # value of the header
39
- # Returns nil if not found
40
- def locate_timestamp
41
- return nil unless headers
42
-
43
- header = HEADERS.find { |candidate| headers[candidate] }
44
- if header
45
- data = headers[header]
46
- data.to_s.gsub(/(t=|\.)/, '')
47
- else
48
- nil
49
- end
50
- end
51
-
52
- # Returns a timestamp in fractional seconds since epoch
53
- def parse(time_string)
54
- Time.at("#{time_string[0,10]}.#{time_string[10,13]}".to_f)
55
- end
56
- end
57
- end