scout_apm 2.2.0.pre3 → 2.3.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/CHANGELOG.markdown +147 -2
  4. data/Guardfile +43 -0
  5. data/Rakefile +2 -2
  6. data/ext/allocations/allocations.c +6 -0
  7. data/ext/allocations/extconf.rb +1 -0
  8. data/ext/rusage/README.md +26 -0
  9. data/ext/rusage/extconf.rb +5 -0
  10. data/ext/rusage/rusage.c +52 -0
  11. data/lib/scout_apm.rb +28 -15
  12. data/lib/scout_apm/agent.rb +89 -37
  13. data/lib/scout_apm/agent/logging.rb +6 -1
  14. data/lib/scout_apm/agent/reporting.rb +9 -6
  15. data/lib/scout_apm/app_server_load.rb +21 -10
  16. data/lib/scout_apm/attribute_arranger.rb +6 -3
  17. data/lib/scout_apm/background_job_integrations/delayed_job.rb +71 -1
  18. data/lib/scout_apm/background_job_integrations/resque.rb +85 -0
  19. data/lib/scout_apm/background_job_integrations/sidekiq.rb +22 -20
  20. data/lib/scout_apm/background_recorder.rb +43 -0
  21. data/lib/scout_apm/background_worker.rb +19 -15
  22. data/lib/scout_apm/config.rb +138 -28
  23. data/lib/scout_apm/db_query_metric_set.rb +80 -0
  24. data/lib/scout_apm/db_query_metric_stats.rb +102 -0
  25. data/lib/scout_apm/debug.rb +37 -0
  26. data/lib/scout_apm/environment.rb +22 -15
  27. data/lib/scout_apm/git_revision.rb +51 -0
  28. data/lib/scout_apm/histogram.rb +11 -2
  29. data/lib/scout_apm/instant/assets/xmlhttp_instrumentation.html +2 -2
  30. data/lib/scout_apm/instant/middleware.rb +196 -54
  31. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +89 -68
  32. data/lib/scout_apm/instruments/action_view.rb +49 -0
  33. data/lib/scout_apm/instruments/active_record.rb +127 -3
  34. data/lib/scout_apm/instruments/grape.rb +4 -3
  35. data/lib/scout_apm/instruments/middleware_detailed.rb +4 -6
  36. data/lib/scout_apm/instruments/mongoid.rb +24 -3
  37. data/lib/scout_apm/instruments/net_http.rb +7 -2
  38. data/lib/scout_apm/instruments/percentile_sampler.rb +36 -19
  39. data/lib/scout_apm/instruments/process/process_cpu.rb +3 -2
  40. data/lib/scout_apm/instruments/process/process_memory.rb +3 -3
  41. data/lib/scout_apm/instruments/resque.rb +40 -0
  42. data/lib/scout_apm/layaway.rb +67 -28
  43. data/lib/scout_apm/layer.rb +19 -59
  44. data/lib/scout_apm/layer_children_set.rb +77 -0
  45. data/lib/scout_apm/layer_converters/allocation_metric_converter.rb +5 -6
  46. data/lib/scout_apm/layer_converters/converter_base.rb +201 -14
  47. data/lib/scout_apm/layer_converters/database_converter.rb +55 -0
  48. data/lib/scout_apm/layer_converters/depth_first_walker.rb +22 -10
  49. data/lib/scout_apm/layer_converters/error_converter.rb +5 -7
  50. data/lib/scout_apm/layer_converters/find_layer_by_type.rb +34 -0
  51. data/lib/scout_apm/layer_converters/histograms.rb +14 -0
  52. data/lib/scout_apm/layer_converters/job_converter.rb +36 -50
  53. data/lib/scout_apm/layer_converters/metric_converter.rb +17 -19
  54. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +10 -12
  55. data/lib/scout_apm/layer_converters/slow_job_converter.rb +41 -115
  56. data/lib/scout_apm/layer_converters/slow_request_converter.rb +33 -117
  57. data/lib/scout_apm/limited_layer.rb +126 -0
  58. data/lib/scout_apm/metric_meta.rb +0 -5
  59. data/lib/scout_apm/metric_set.rb +9 -1
  60. data/lib/scout_apm/metric_stats.rb +7 -8
  61. data/lib/scout_apm/rack.rb +26 -0
  62. data/lib/scout_apm/remote/message.rb +23 -0
  63. data/lib/scout_apm/remote/recorder.rb +57 -0
  64. data/lib/scout_apm/remote/router.rb +49 -0
  65. data/lib/scout_apm/remote/server.rb +58 -0
  66. data/lib/scout_apm/reporter.rb +51 -15
  67. data/lib/scout_apm/request_histograms.rb +4 -0
  68. data/lib/scout_apm/request_manager.rb +2 -1
  69. data/lib/scout_apm/scored_item_set.rb +7 -0
  70. data/lib/scout_apm/serializers/db_query_serializer_to_json.rb +15 -0
  71. data/lib/scout_apm/serializers/histograms_serializer_to_json.rb +21 -0
  72. data/lib/scout_apm/serializers/payload_serializer.rb +10 -3
  73. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +6 -6
  74. data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +2 -1
  75. data/lib/scout_apm/server_integrations/puma.rb +5 -2
  76. data/lib/scout_apm/slow_job_policy.rb +1 -10
  77. data/lib/scout_apm/slow_job_record.rb +6 -1
  78. data/lib/scout_apm/slow_request_policy.rb +1 -10
  79. data/lib/scout_apm/slow_transaction.rb +20 -2
  80. data/lib/scout_apm/store.rb +66 -12
  81. data/lib/scout_apm/synchronous_recorder.rb +26 -0
  82. data/lib/scout_apm/tracked_request.rb +136 -71
  83. data/lib/scout_apm/utils/active_record_metric_name.rb +8 -4
  84. data/lib/scout_apm/utils/backtrace_parser.rb +3 -3
  85. data/lib/scout_apm/utils/gzip_helper.rb +24 -0
  86. data/lib/scout_apm/utils/numbers.rb +14 -0
  87. data/lib/scout_apm/utils/scm.rb +14 -0
  88. data/lib/scout_apm/version.rb +1 -1
  89. data/scout_apm.gemspec +5 -4
  90. data/test/test_helper.rb +18 -0
  91. data/test/unit/config_test.rb +59 -8
  92. data/test/unit/db_query_metric_set_test.rb +56 -0
  93. data/test/unit/db_query_metric_stats_test.rb +113 -0
  94. data/test/unit/git_revision_test.rb +15 -0
  95. data/test/unit/histogram_test.rb +14 -0
  96. data/test/unit/instruments/net_http_test.rb +21 -0
  97. data/test/unit/instruments/percentile_sampler_test.rb +137 -0
  98. data/test/unit/layaway_test.rb +20 -0
  99. data/test/unit/layer_children_set_test.rb +88 -0
  100. data/test/unit/layer_converters/depth_first_walker_test.rb +66 -0
  101. data/test/unit/layer_converters/metric_converter_test.rb +22 -0
  102. data/test/unit/layer_converters/stubs.rb +33 -0
  103. data/test/unit/limited_layer_test.rb +53 -0
  104. data/test/unit/remote/test_message.rb +13 -0
  105. data/test/unit/remote/test_router.rb +33 -0
  106. data/test/unit/remote/test_server.rb +15 -0
  107. data/test/unit/serializers/payload_serializer_test.rb +3 -12
  108. data/test/unit/store_test.rb +66 -0
  109. data/test/unit/test_tracked_request.rb +87 -0
  110. data/test/unit/utils/active_record_metric_name_test.rb +8 -0
  111. data/test/unit/utils/backtrace_parser_test.rb +5 -0
  112. data/test/unit/utils/numbers_test.rb +15 -0
  113. data/test/unit/utils/scm.rb +17 -0
  114. metadata +125 -30
  115. data/ext/stacks/extconf.rb +0 -37
  116. data/ext/stacks/scout_atomics.h +0 -86
  117. data/ext/stacks/stacks.c +0 -811
  118. data/lib/scout_apm/capacity.rb +0 -57
  119. data/lib/scout_apm/deploy_integrations/capistrano_2.cap +0 -12
  120. data/lib/scout_apm/deploy_integrations/capistrano_2.rb +0 -83
  121. data/lib/scout_apm/deploy_integrations/capistrano_3.cap +0 -12
  122. data/lib/scout_apm/deploy_integrations/capistrano_3.rb +0 -88
  123. data/lib/scout_apm/instruments/delayed_job.rb +0 -57
  124. data/lib/scout_apm/serializers/deploy_serializer.rb +0 -16
  125. data/lib/scout_apm/trace_compactor.rb +0 -312
  126. data/lib/scout_apm/utils/fake_stacks.rb +0 -87
  127. data/tester.rb +0 -53
@@ -1,57 +0,0 @@
1
- # Encapsulates logic for determining capacity utilization of the Ruby processes.
2
- module ScoutApm
3
- class Capacity
4
- attr_reader :processing_start_time, :accumulated_time, :transaction_entry_time
5
-
6
- def initialize
7
- @processing_start_time = Time.now
8
- @lock ||= Mutex.new # the transaction_entry_time could be modified while processing a request or when #process is called.
9
- @accumulated_time = 0.0
10
- end
11
-
12
- # Called when a transaction is traced.
13
- def start_transaction!
14
- @lock.synchronize do
15
- @transaction_entry_time = Time.now
16
- end
17
- end
18
-
19
- # Called when a transaction completes to record its time used.
20
- def finish_transaction!
21
- @lock.synchronize do
22
- if transaction_entry_time
23
- @accumulated_time += (Time.now - transaction_entry_time).to_f
24
- else
25
- ScoutApm::Agent.instance.logger.warn "No transaction entry time. Not recording capacity metrics for transaction."
26
- end
27
- @transaction_entry_time = nil
28
- end
29
- end
30
-
31
- # Ran when sending metrics to server. Reports capacity usage metrics.
32
- def process
33
- process_time = Time.now
34
- ScoutApm::Agent.instance.logger.debug "Processing capacity usage for [#{@processing_start_time}] to [#{process_time}]. Time Spent: #{@accumulated_time}."
35
- @lock.synchronize do
36
- time_spent = @accumulated_time
37
- @accumulated_time = 0.0
38
- # If a transaction is still running, capture its running time up to now and
39
- # reset the +transaction_entry_time+ to now.
40
- if @transaction_entry_time
41
- time_spent += (process_time - @transaction_entry_time).to_f
42
- ScoutApm::Agent.instance.logger.debug "A transaction is running while calculating capacity. Start time: [#{transaction_entry_time}]. Will update the entry time to [#{process_time}]."
43
- @transaction_entry_time = process_time # prevent from over-counting capacity usage. update the transaction start time to now.
44
- end
45
- time_spent = 0.0 if time_spent < 0.0
46
-
47
- window = (process_time - processing_start_time).to_f # time period we are evaulating capacity usage.
48
- window = 1.0 if window <= 0.0 # prevent divide-by-zero if clock adjusted.
49
- capacity = time_spent / window
50
- ScoutApm::Agent.instance.logger.debug "Instance/Capacity: #{capacity}"
51
- ScoutApm::Agent.instance.store.track_one!("Instance", "Capacity", capacity)
52
-
53
- @processing_start_time = process_time
54
- end
55
- end
56
- end
57
- end
@@ -1,12 +0,0 @@
1
- namespace :scout_apm do
2
- namespace :deploy do
3
- task :starting do
4
- # Warn if missing scout apm deploy creds?
5
- end
6
- task :finished do
7
- ScoutApm::Agent.instance.deploy_integration.report
8
- end
9
- end
10
- end
11
-
12
- after 'deploy:finished', 'scout_apm:deploy:finished'
@@ -1,83 +0,0 @@
1
- require 'scout_apm'
2
-
3
- module ScoutApm
4
- module DeployIntegrations
5
- class Capistrano2
6
- attr_reader :logger
7
-
8
- def initialize(logger)
9
- @logger = logger
10
- @cap = defined?(Capistrano::Configuration) ? ObjectSpace.each_object(Capistrano::Configuration).map.first : nil rescue nil
11
- end
12
-
13
- def name
14
- :capistrano_2
15
- end
16
-
17
- def version
18
- present? ? Capistrano::VERSION : nil
19
- end
20
-
21
- def present?
22
- if !@cap.nil? && @cap.is_a?(Capistrano::Configuration)
23
- require 'capistrano/version'
24
- defined?(Capistrano::VERSION) && Gem::Dependency.new('', '~> 2.0').match?('', Capistrano::VERSION.to_s)
25
- else
26
- return false
27
- end
28
- return true
29
- rescue
30
- return false
31
- end
32
-
33
- def install
34
- logger.debug "Initializing Capistrano2 Deploy Integration."
35
- @cap.load File.expand_path("../capistrano_2.cap", __FILE__)
36
- end
37
-
38
- def root
39
- '.'
40
- end
41
-
42
- def env
43
- @cap.fetch(:stage)
44
- end
45
-
46
- def found?
47
- true
48
- end
49
-
50
- def report
51
- if reporter.can_report?
52
- data = deploy_data
53
- logger.debug "Sending deploy hook data: #{data}"
54
- payload = ScoutApm::Serializers::DeploySerializer.serialize(data)
55
- reporter.report(payload, ScoutApm::Serializers::DeploySerializer::HTTP_HEADERS)
56
- else
57
- logger.warn "Unable to post deploy hook data"
58
- end
59
- end
60
-
61
- def reporter
62
- @reporter ||= ScoutApm::Reporter.new(:deploy_hook, ScoutApm::Agent.instance.config, @logger)
63
- end
64
-
65
- def deploy_data
66
- {:revision => current_revision, :branch => branch, :deployed_by => deployed_by}
67
- end
68
-
69
- def branch
70
- @cap.fetch(:branch)
71
- end
72
-
73
- def current_revision
74
- @cap.fetch(:current_revision) || `git rev-list --max-count=1 --abbrev-commit --abbrev=12 #{branch}`.chomp
75
- end
76
-
77
- def deployed_by
78
- ScoutApm::Agent.instance.config.value('deployed_by')
79
- end
80
-
81
- end
82
- end
83
- end
@@ -1,12 +0,0 @@
1
- namespace :scout_apm do
2
- namespace :deploy do
3
- task :starting do
4
- # Warn if missing scout apm deploy creds?
5
- end
6
- task :finished do
7
- ScoutApm::Agent.instance.deploy_integration.report
8
- end
9
- end
10
- end
11
-
12
- after 'deploy:finished', 'scout_apm:deploy:finished'
@@ -1,88 +0,0 @@
1
- require 'scout_apm'
2
-
3
- module ScoutApm
4
- module DeployIntegrations
5
- class Capistrano3
6
- attr_reader :logger
7
-
8
- def initialize(logger)
9
- @logger = logger
10
- @cap = Rake.application rescue nil
11
- end
12
-
13
- def name
14
- :capistrano_3
15
- end
16
-
17
- def version
18
- present? ? Capistrano::VERSION : nil
19
- end
20
-
21
- def present?
22
- if !@cap.nil? && @cap.is_a?(Capistrano::Application)
23
- require 'capistrano/version'
24
- defined?(Capistrano::VERSION) && Gem::Dependency.new('', '~> 3.0').match?('', Capistrano::VERSION.to_s)
25
- else
26
- return false
27
- end
28
- rescue
29
- return false
30
- end
31
-
32
- def install
33
- logger.debug "Initializing Capistrano3 Deploy Integration."
34
- load File.expand_path("../capistrano_3.cap", __FILE__)
35
- end
36
-
37
- def root
38
- '.'
39
- end
40
-
41
- def env
42
- @cap.fetch(:stage).to_s
43
- end
44
-
45
- def found?
46
- true
47
- end
48
-
49
- def report
50
- if reporter.can_report?
51
- data = deploy_data
52
- logger.debug "Sending deploy hook data: #{data}"
53
- payload = ScoutApm::Serializers::DeploySerializer.serialize(data)
54
- reporter.report(payload, ScoutApm::Serializers::DeploySerializer::HTTP_HEADERS)
55
- else
56
- logger.warn "Unable to post deploy hook data"
57
- end
58
- end
59
-
60
- def reporter
61
- config = if env == ''
62
- ScoutApm::Agent.instance.config
63
- else
64
- ScoutApm::Config.with_file(nil, {:file => { :environment => env }}) # instantiate our own config, with an overridden environment for the deploy-to app name instead of deploy-from app name)
65
- end
66
-
67
- @reporter ||= ScoutApm::Reporter.new(:deploy_hook, config, @logger)
68
- end
69
-
70
- def deploy_data
71
- {:revision => current_revision, :branch => branch, :deployed_by => deployed_by}
72
- end
73
-
74
- def branch
75
- @cap.fetch(:branch)
76
- end
77
-
78
- def current_revision
79
- @cap.fetch(:current_revision) || `git rev-list --max-count=1 --abbrev-commit --abbrev=12 #{branch}`.chomp
80
- end
81
-
82
- def deployed_by
83
- ScoutApm::Agent.instance.config.value('deployed_by')
84
- end
85
-
86
- end
87
- end
88
- end
@@ -1,57 +0,0 @@
1
- module ScoutApm
2
- module Instruments
3
- class DelayedJob
4
- attr_reader :logger
5
-
6
- def initialize(logger=ScoutApm::Agent.instance.logger)
7
- @logger = logger
8
- @installed = false
9
- end
10
-
11
- def installed?
12
- @installed
13
- end
14
-
15
- def install
16
- @installed = true
17
- if defined?(::Delayed::Worker)
18
- ::Delayed::Worker.class_eval do
19
- include ScoutApm::Tracer
20
- include ScoutApm::Instruments::DelayedJobInstruments
21
- alias run_without_scout_instruments run
22
- alias run run_with_scout_instruments
23
- end
24
- end
25
- end
26
- end
27
-
28
- module DelayedJobInstruments
29
- def run_with_scout_instruments(job)
30
- scout_method_name = method_from_handler(job.handler)
31
- queue = job.queue
32
- latency = (Time.now.to_f - job.created_at.to_f) * 1000
33
-
34
- ScoutApm::Agent.instance.store.track_one!("Queue", queue, 0, {:extra_metrics => {:latency => latency}})
35
- req = ScoutApm::RequestManager.lookup
36
- req.job!
37
- req.start_layer( ScoutApm::Layer.new("Job", scout_method_name) )
38
-
39
- begin
40
- run_without_scout_instruments(job)
41
- rescue
42
- req.error!
43
- raise
44
- ensure
45
- req.stop_layer
46
- end
47
- end
48
-
49
- def method_from_handler(handler)
50
- job_handler = YAML.load(handler)
51
- klass = job_handler.object.name
52
- method = job_handler.method_name
53
- "#{klass}##{method}"
54
- end
55
- end
56
- end
57
- end
@@ -1,16 +0,0 @@
1
- # Serialize & deserialize deploy data up to the APM server
2
- module ScoutApm
3
- module Serializers
4
- class DeploySerializer
5
- HTTP_HEADERS = {'Content-Type' => 'application/x-www-form-urlencoded'}
6
-
7
- def self.serialize(data)
8
- URI.encode_www_form(data)
9
- end
10
-
11
- def self.deserialize(data)
12
- Marshal.load(data)
13
- end
14
- end
15
- end
16
- end
@@ -1,312 +0,0 @@
1
- # Takes in a ton of traces. Structure is a several nested arrays:
2
- # [ # Traces
3
- # [ # Trace
4
- # [file,line,method,klass] # TraceLine (raw)
5
- # ]
6
- # ]
7
- #
8
- # Cleans them
9
- # Merges them by gem/app
10
- #
11
- module ScoutApm
12
- class TraceSet
13
- # A TraceCube object which is a glorified hash of { Trace -> Count }. Used to
14
- # collect up the count of each unique trace we've seen
15
- attr_reader :cube
16
-
17
- # Allow layer to push values in
18
- attr_accessor :raw_traces
19
- attr_accessor :skipped_in_gc
20
- attr_accessor :skipped_in_handler
21
- attr_accessor :skipped_in_job_registered
22
- attr_accessor :skipped_in_not_running
23
-
24
- def initialize
25
- @raw_traces = []
26
- @cube = TraceCube.new
27
- end
28
-
29
- # We need to know what the "Start" of this trace is. An untrimmed trace generally is:
30
- #
31
- # Gem
32
- # Gem
33
- # App
34
- # App
35
- # App <---- set root_class of this.
36
- # Rails
37
- # Rails
38
- def set_root_class(klass_name)
39
- @root_klass = klass_name.to_s
40
- end
41
-
42
- def to_a
43
- res = []
44
- create_cube!
45
- @cube.each do |(trace, count)|
46
- res << [trace.to_a, count]
47
- end
48
-
49
- res
50
- end
51
-
52
- def as_json
53
- res = []
54
- create_cube!
55
- @cube.each do |(trace, count)|
56
- res << [trace.as_json, count]
57
- end
58
-
59
- res
60
- end
61
-
62
- def create_cube!
63
- while raw_trace = @raw_traces.shift
64
- clean_trace = ScoutApm::CleanTrace.new(raw_trace, @root_klass)
65
- @cube << clean_trace
66
- end
67
- @raw_traces = []
68
- end
69
-
70
- def total_count
71
- create_cube!
72
- cube.inject(0) do |sum, (_, count)|
73
- sum + count
74
- end
75
- end
76
-
77
- def inspect
78
- create_cube!
79
- cube.map do |(trace, count)|
80
- "\t#{count} -- #{trace.first.klass}##{trace.first.method}\n\t\t#{trace.to_a[1].try(:klass)}##{trace.to_a[1].try(:method)}"
81
- end.join("\n")
82
- end
83
- end
84
-
85
- # A trace is a list of individual lines, where one called another, forming a backtrace.
86
- # Each line is made up of File, Line #, Klass, Method
87
- #
88
- # For the purpouses of this class:
89
- # "Top" of the trace means the currently-running method.
90
- # "Bottom" means the root of the call tree, from program start into rails and so on.
91
- #
92
- # This class trims off top and bottom to get a the meat of the trace
93
- class CleanTrace
94
- include Enumerable
95
-
96
- attr_reader :lines
97
-
98
- def initialize(raw_trace, root_klass=nil)
99
- @lines = Array(raw_trace).map {|frame, lineno| TraceLine.new(frame, lineno)}
100
- @root_klass = root_klass
101
-
102
- # A trace has interesting data in the middle of it, since normally it'll go
103
- # RailsCode -> App Code -> Gem Code.
104
- #
105
- # So we drop the code that leads up to your app, since a deep trace that
106
- # always says that you went through middleware and the rails router doesn't
107
- # help diagnose issues.
108
- drop_below_app
109
-
110
- # Then we drop most of the Gem Code, since you didn't write it, and in the
111
- # vast majority of the cases, the time spent there is because your app code
112
- # asked, not because of inherent issues with the gem. For instance, if you
113
- # fire off a slow query to a database gem, you probably want to be
114
- # optimizing the query, not trying to make the database gem faster.
115
- drop_above_app
116
- end
117
-
118
- # Iterate starting at END of array until a controller line is found. Pop off at that index - 1.
119
- def drop_below_app
120
- pops = 0
121
- index = lines.size - 1 # last index, not size.
122
-
123
- while index >= 0 && !lines[index].controller?(@root_klass)
124
- index -= 1
125
- pops += 1
126
- end
127
-
128
- lines.pop(pops)
129
- end
130
-
131
- # Find the closest mention of the application code from the currently-running method.
132
- # Then adjust by 1 if possible to capture the "first" line
133
- def drop_above_app
134
- ai = @lines.find_index(&:app?)
135
- if ai
136
- ai -= 1 if ai > 0
137
- @lines = @lines[ai .. -1]
138
- else
139
- @lines = [] # No app line in backtrace, nothing to show?
140
- end
141
- end
142
-
143
- def each
144
- @lines.each { |line| yield line }
145
- end
146
-
147
- def empty?
148
- @lines.empty?
149
- end
150
-
151
- def as_json
152
- @lines.map { |line| line.as_json }
153
- end
154
-
155
- ###############################
156
- # Hash Key interface
157
- def hash
158
- @lines.hash
159
- end
160
-
161
- def eql?(other)
162
- @lines.eql?(other.lines)
163
- end
164
- ###############################
165
- end
166
-
167
- class TraceLine
168
- # An opaque C object, only call Stacks#frame_* methods on this.
169
- attr_reader :frame
170
-
171
- # The line number. This doesn't appear to be obtainable from the frame itself
172
- attr_reader :lineno
173
-
174
- def initialize(frame, lineno)
175
- @frame = frame
176
- @lineno = lineno
177
- end
178
-
179
- # Returns the name of the last gem in the line
180
- def gem_name
181
- @gem_name ||= begin
182
- r = %r{\/gems/(.*?)/}.freeze
183
- results = file.scan(r)
184
- results[-1][0] # Scan will return a nested array, so extract out that nesting
185
- rescue
186
- nil
187
- end
188
- end
189
-
190
- def stdlib_name
191
- @stdlib_name ||= begin
192
- r = %r{#{Regexp.escape(RbConfig::TOPDIR)}/(.*?)}.freeze
193
- results = file.scan(r)
194
- results[-1][0] # Scan will return a nested array, so extract out that nesting
195
- rescue
196
- nil
197
- end
198
- end
199
-
200
- def file
201
- ScoutApm::Instruments::Stacks.frame_file(frame)
202
- end
203
-
204
-
205
- # If we ever want to get the "first line of the method" - ScoutApm::Instruments::Stacks.frame_lineno(frame)
206
- def line
207
- lineno
208
- end
209
-
210
- def klass
211
- ScoutApm::Instruments::Stacks.frame_klass(frame)
212
- end
213
-
214
- def method
215
- ScoutApm::Instruments::Stacks.frame_method(frame)
216
- end
217
-
218
- def gem?
219
- !!gem_name
220
- end
221
-
222
- def stdlib?
223
- !!stdlib_name
224
- end
225
-
226
- def app?
227
- r = %r|^#{Regexp.escape(ScoutApm::Environment.instance.root.to_s)}/|.freeze
228
- result = !gem_name && !stdlib_name && file =~ r
229
- !!result # coerce to a bool
230
- end
231
-
232
- def trim_file(file_path)
233
- return if file_path.nil?
234
- if gem?
235
- r = %r{.*gems/.*?/}.freeze
236
- file_path.sub(r, "/")
237
- elsif stdlib?
238
- file_path.sub(RbConfig::TOPDIR, '')
239
- elsif app?
240
- file_path.sub(ScoutApm::Environment.instance.root.to_s, '')
241
- end
242
- end
243
-
244
- # If root_klass is provided, just see if this is exactly that class. If not,
245
- # fall back on "is this in the app"
246
- def controller?(root_klass)
247
- return false if klass.nil? # main function doesn't have a file associated
248
-
249
- if root_klass
250
- klass == root_klass
251
- else
252
- app?
253
- end
254
- end
255
-
256
- def formatted_to_s
257
- "#{stdlib_name} #{klass}##{method} -- #{file}:#{line}"
258
- end
259
-
260
- def as_json
261
- [ trim_file(file), line, klass, method, app?, gem_name, stdlib_name ]
262
- end
263
-
264
- ###############################
265
- # Hash Key interface
266
-
267
- def hash
268
- # Note that this does not include line number. It caused a few situations
269
- # where we had a bunch of time spent in one method, but across a few lines,
270
- # we decided that it made sense to group them together.
271
- file.hash ^ method.hash ^ klass.hash
272
- end
273
-
274
- def eql?(other)
275
- file == other.file &&
276
- method == other.method &&
277
- klass == other.klass
278
- end
279
-
280
- ###############################
281
- end
282
-
283
- # Collects clean traces and counts how many of each we have.
284
- class TraceCube
285
- include Enumerable
286
-
287
- attr_reader :traces
288
- attr_reader :total_count
289
-
290
- def initialize
291
- @traces = Hash.new{ |h,k| h[k] = 0 }
292
- @total_count = 0
293
- end
294
-
295
- def <<(clean_trace)
296
- @total_count += 1
297
- @traces[clean_trace] += 1
298
- end
299
-
300
- # Yields two element array, the trace and the count of that trace
301
- # In descending order of count.
302
- def each
303
- @traces
304
- .to_a
305
- .each { |(trace, count)|
306
- next if trace.empty?
307
- yield [trace, count]
308
- }
309
- end
310
- end
311
- end
312
-