app_profiler 0.1.9 → 0.2.0
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 +4 -4
- data/lib/app_profiler/backend/base_backend.rb +49 -0
- data/lib/app_profiler/{profiler.rb → backend/stackprof_backend.rb} +28 -16
- data/lib/app_profiler/backend/vernier_backend.rb +106 -0
- data/lib/app_profiler/backend.rb +9 -0
- data/lib/app_profiler/middleware/base_action.rb +1 -1
- data/lib/app_profiler/middleware/view_action.rb +2 -2
- data/lib/app_profiler/middleware.rb +1 -1
- data/lib/app_profiler/parameters.rb +7 -5
- data/lib/app_profiler/profile/stackprof.rb +19 -0
- data/lib/app_profiler/profile/vernier.rb +19 -0
- data/lib/app_profiler/profile.rb +39 -17
- data/lib/app_profiler/railtie.rb +1 -0
- data/lib/app_profiler/request_parameters.rb +18 -4
- data/lib/app_profiler/version.rb +1 -1
- data/lib/app_profiler/viewer/base_viewer.rb +3 -3
- data/lib/app_profiler/viewer/speedscope_remote_viewer.rb +10 -4
- data/lib/app_profiler/viewer/speedscope_viewer.rb +3 -3
- data/lib/app_profiler.rb +72 -6
- metadata +9 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1c36371d12c24dfed23e6e492b5c3912e01480bfd7a5973bf1f311a86adc86d3
|
4
|
+
data.tar.gz: 1b78da495c55cee24ab6b0d6f8d8194ca6cfb27030de7a4ba90db37fa60e3988
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd4e66293f491f25caba2a352b509012344bdc6e04c880cb4c3d60d00f3dc38cab50b9c59cf0cbe14c36adc2591610bd004addc28521cf077b7d3b7c91585947
|
7
|
+
data.tar.gz: 036c9de32a86b5e99e1d244fe1200aa3e0dabdf6d610d91c34aa6621bf8a194b9a769a8a9bae254432d2e2bfae67b137a1209c4214bb2c2ce774aa9db9bea907
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppProfiler
|
4
|
+
module Backend
|
5
|
+
class BaseBackend
|
6
|
+
def self.name
|
7
|
+
raise NotImplementedError
|
8
|
+
end
|
9
|
+
|
10
|
+
def run(params = {}, &block)
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
def start(params = {})
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
def stop
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def results
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
def running?
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
def run_lock
|
32
|
+
@run_lock ||= Mutex.new
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def acquire_run_lock
|
39
|
+
self.class.run_lock.try_lock
|
40
|
+
end
|
41
|
+
|
42
|
+
def release_run_lock
|
43
|
+
self.class.run_lock.unlock
|
44
|
+
rescue ThreadError
|
45
|
+
AppProfiler.logger.warn("[AppProfiler] run lock not released as it was never acquired")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -3,13 +3,23 @@
|
|
3
3
|
require "stackprof"
|
4
4
|
|
5
5
|
module AppProfiler
|
6
|
-
module
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
module Backend
|
7
|
+
class StackprofBackend < BaseBackend
|
8
|
+
DEFAULTS = {
|
9
|
+
mode: :cpu,
|
10
|
+
raw: true,
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
AVAILABLE_MODES = [
|
14
|
+
:wall,
|
15
|
+
:cpu,
|
16
|
+
:object,
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
def self.name
|
20
|
+
:stackprof
|
21
|
+
end
|
11
22
|
|
12
|
-
class << self
|
13
23
|
def run(params = {})
|
14
24
|
started = start(params)
|
15
25
|
|
@@ -27,6 +37,7 @@ module AppProfiler
|
|
27
37
|
def start(params = {})
|
28
38
|
# Do not start the profiler if StackProf was started somewhere else.
|
29
39
|
return false if running?
|
40
|
+
return false unless acquire_run_lock
|
30
41
|
|
31
42
|
clear
|
32
43
|
|
@@ -35,6 +46,7 @@ module AppProfiler
|
|
35
46
|
AppProfiler.logger.info(
|
36
47
|
"[Profiler] failed to start the profiler error_class=#{error.class} error_message=#{error.message}"
|
37
48
|
)
|
49
|
+
release_run_lock
|
38
50
|
# This is a boolean instead of nil because StackProf#start returns a
|
39
51
|
# boolean as well.
|
40
52
|
false
|
@@ -42,14 +54,16 @@ module AppProfiler
|
|
42
54
|
|
43
55
|
def stop
|
44
56
|
StackProf.stop
|
57
|
+
ensure
|
58
|
+
release_run_lock
|
45
59
|
end
|
46
60
|
|
47
61
|
def results
|
48
|
-
stackprof_profile =
|
62
|
+
stackprof_profile = backend_results
|
49
63
|
|
50
64
|
return unless stackprof_profile
|
51
65
|
|
52
|
-
|
66
|
+
BaseProfile.from_stackprof(stackprof_profile)
|
53
67
|
rescue => error
|
54
68
|
AppProfiler.logger.info(
|
55
69
|
"[Profiler] failed to obtain the profile error_class=#{error.class} error_message=#{error.message}"
|
@@ -57,9 +71,13 @@ module AppProfiler
|
|
57
71
|
nil
|
58
72
|
end
|
59
73
|
|
74
|
+
def running?
|
75
|
+
StackProf.running?
|
76
|
+
end
|
77
|
+
|
60
78
|
private
|
61
79
|
|
62
|
-
def
|
80
|
+
def backend_results
|
63
81
|
StackProf.results
|
64
82
|
end
|
65
83
|
|
@@ -73,14 +91,8 @@ module AppProfiler
|
|
73
91
|
# Ref: https://github.com/tmm1/stackprof/blob/0ded6c/ext/stackprof/stackprof.c#L118-L123
|
74
92
|
#
|
75
93
|
def clear
|
76
|
-
|
77
|
-
end
|
78
|
-
|
79
|
-
def running?
|
80
|
-
StackProf.running?
|
94
|
+
backend_results
|
81
95
|
end
|
82
96
|
end
|
83
97
|
end
|
84
|
-
|
85
|
-
private_constant :Profiler
|
86
98
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
gem("vernier", ">= 0.7.0")
|
4
|
+
require "vernier"
|
5
|
+
|
6
|
+
module AppProfiler
|
7
|
+
module Backend
|
8
|
+
class VernierBackend < BaseBackend
|
9
|
+
DEFAULTS = {
|
10
|
+
mode: :wall,
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
AVAILABLE_MODES = [
|
14
|
+
:wall,
|
15
|
+
:retained,
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
def self.name
|
19
|
+
:vernier
|
20
|
+
end
|
21
|
+
|
22
|
+
def run(params = {})
|
23
|
+
started = start(params)
|
24
|
+
|
25
|
+
yield
|
26
|
+
|
27
|
+
return unless started
|
28
|
+
|
29
|
+
stop
|
30
|
+
results
|
31
|
+
ensure
|
32
|
+
# Only stop the profiler if profiling was started in this context.
|
33
|
+
stop if started
|
34
|
+
end
|
35
|
+
|
36
|
+
def start(params = {})
|
37
|
+
# Do not start the profiler if we already have a collector started somewhere else.
|
38
|
+
return false if running?
|
39
|
+
return false unless acquire_run_lock
|
40
|
+
|
41
|
+
@mode = params.delete(:mode) || DEFAULTS[:mode]
|
42
|
+
raise ArgumentError unless AVAILABLE_MODES.include?(@mode)
|
43
|
+
|
44
|
+
@metadata = params.delete(:metadata)
|
45
|
+
clear
|
46
|
+
|
47
|
+
@collector ||= ::Vernier::Collector.new(@mode, **params)
|
48
|
+
@collector.start
|
49
|
+
rescue => error
|
50
|
+
AppProfiler.logger.info(
|
51
|
+
"[Profiler] failed to start the profiler error_class=#{error.class} error_message=#{error.message}"
|
52
|
+
)
|
53
|
+
release_run_lock
|
54
|
+
# This is a boolean instead of nil to be consistent with the stackprof backend behaviour
|
55
|
+
# boolean as well.
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
def stop
|
60
|
+
return false unless running?
|
61
|
+
|
62
|
+
@results = @collector&.stop
|
63
|
+
@collector = nil
|
64
|
+
!@results.nil?
|
65
|
+
ensure
|
66
|
+
release_run_lock
|
67
|
+
end
|
68
|
+
|
69
|
+
def results
|
70
|
+
vernier_profile = backend_results
|
71
|
+
clear
|
72
|
+
|
73
|
+
return unless vernier_profile
|
74
|
+
|
75
|
+
# HACK: - "data" is private, but we want to avoid serializing to JSON then
|
76
|
+
# parsing back from JSON by just directly getting the hash
|
77
|
+
data = ::Vernier::Output::Firefox.new(vernier_profile).send(:data)
|
78
|
+
data[:meta][:mode] = @mode # TODO: https://github.com/jhawthorn/vernier/issues/30
|
79
|
+
data[:meta].merge!(@metadata) if @metadata
|
80
|
+
@mode = nil
|
81
|
+
@metadata = nil
|
82
|
+
|
83
|
+
BaseProfile.from_vernier(data)
|
84
|
+
rescue => error
|
85
|
+
AppProfiler.logger.info(
|
86
|
+
"[Profiler] failed to obtain the profile error_class=#{error.class} error_message=#{error.message}"
|
87
|
+
)
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def running?
|
92
|
+
@collector != nil
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def backend_results
|
98
|
+
@results
|
99
|
+
end
|
100
|
+
|
101
|
+
def clear
|
102
|
+
@results = nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppProfiler
|
4
|
+
module Backend
|
5
|
+
autoload :BaseBackend, "app_profiler/backend/base_backend"
|
6
|
+
autoload :StackprofBackend, "app_profiler/backend/stackprof_backend"
|
7
|
+
autoload :VernierBackend, "app_profiler/backend/vernier_backend"
|
8
|
+
end
|
9
|
+
end
|
@@ -4,17 +4,18 @@ require "rack"
|
|
4
4
|
|
5
5
|
module AppProfiler
|
6
6
|
class Parameters
|
7
|
-
DEFAULT_INTERVALS = { "cpu" => 1000, "wall" => 1000, "object" => 2000 }.freeze
|
8
|
-
MIN_INTERVALS = { "cpu" => 200, "wall" => 200, "object" => 400 }.freeze
|
9
|
-
MODES = DEFAULT_INTERVALS.keys.freeze
|
7
|
+
DEFAULT_INTERVALS = { "cpu" => 1000, "wall" => 1000, "object" => 2000, "retained" => 0 }.freeze
|
8
|
+
MIN_INTERVALS = { "cpu" => 200, "wall" => 200, "object" => 400, "retained" => 0 }.freeze
|
10
9
|
|
11
|
-
attr_reader :autoredirect, :async
|
10
|
+
attr_reader :autoredirect, :async, :backend
|
12
11
|
|
13
|
-
def initialize(mode: :wall, interval: nil, ignore_gc: false, autoredirect: false,
|
12
|
+
def initialize(mode: :wall, interval: nil, ignore_gc: false, autoredirect: false,
|
13
|
+
async: false, backend: nil, metadata: {})
|
14
14
|
@mode = mode.to_sym
|
15
15
|
@interval = [interval&.to_i || DEFAULT_INTERVALS.fetch(@mode.to_s), MIN_INTERVALS.fetch(@mode.to_s)].max
|
16
16
|
@ignore_gc = !!ignore_gc
|
17
17
|
@autoredirect = autoredirect
|
18
|
+
@backend = backend || AppProfiler::Backend::StackprofBackend.name
|
18
19
|
@metadata = { context: AppProfiler.context }.merge(metadata)
|
19
20
|
@async = async
|
20
21
|
end
|
@@ -29,6 +30,7 @@ module AppProfiler
|
|
29
30
|
interval: @interval,
|
30
31
|
ignore_gc: @ignore_gc,
|
31
32
|
metadata: @metadata,
|
33
|
+
backend: @backend,
|
32
34
|
}
|
33
35
|
end
|
34
36
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppProfiler
|
4
|
+
class StackprofProfile < BaseProfile
|
5
|
+
FILE_EXTENSION = ".json"
|
6
|
+
|
7
|
+
def mode
|
8
|
+
@data[:mode]
|
9
|
+
end
|
10
|
+
|
11
|
+
def format
|
12
|
+
FILE_EXTENSION
|
13
|
+
end
|
14
|
+
|
15
|
+
def view(params = {})
|
16
|
+
AppProfiler.viewer.view(self, **params)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppProfiler
|
4
|
+
class VernierProfile < BaseProfile
|
5
|
+
FILE_EXTENSION = ".gecko.json"
|
6
|
+
|
7
|
+
def mode
|
8
|
+
@data[:meta][:mode]
|
9
|
+
end
|
10
|
+
|
11
|
+
def format
|
12
|
+
FILE_EXTENSION
|
13
|
+
end
|
14
|
+
|
15
|
+
def view(params = {})
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/app_profiler/profile.rb
CHANGED
@@ -1,42 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/deprecation/constant_accessor"
|
4
|
+
|
3
5
|
module AppProfiler
|
4
|
-
|
6
|
+
autoload :StackprofProfile, "app_profiler/profile/stackprof"
|
7
|
+
autoload :VernierProfile, "app_profiler/profile/vernier"
|
8
|
+
|
9
|
+
class BaseProfile
|
5
10
|
INTERNAL_METADATA_KEYS = [:id, :context]
|
6
11
|
private_constant :INTERNAL_METADATA_KEYS
|
7
12
|
class UnsafeFilename < StandardError; end
|
8
13
|
|
9
|
-
delegate :[], to: :@data
|
10
14
|
attr_reader :id, :context
|
11
15
|
|
16
|
+
delegate :[], to: :@data
|
17
|
+
|
12
18
|
# This function should not be called if `StackProf.results` returns nil.
|
13
19
|
def self.from_stackprof(data)
|
14
20
|
options = INTERNAL_METADATA_KEYS.map { |key| [key, data[:metadata]&.delete(key)] }.to_h
|
15
21
|
|
16
|
-
new(data, **options).tap do |profile|
|
22
|
+
StackprofProfile.new(data, **options).tap do |profile|
|
23
|
+
raise ArgumentError, "invalid profile data" unless profile.valid?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.from_vernier(data)
|
28
|
+
options = INTERNAL_METADATA_KEYS.map { |key| [key, data[:meta]&.delete(key)] }.to_h
|
29
|
+
|
30
|
+
VernierProfile.new(data, **options).tap do |profile|
|
17
31
|
raise ArgumentError, "invalid profile data" unless profile.valid?
|
18
32
|
end
|
19
33
|
end
|
20
34
|
|
21
|
-
# `data` is assumed to be a Hash
|
35
|
+
# `data` is assumed to be a Hash for Stackprof,
|
36
|
+
# a vernier "result" object for vernier
|
22
37
|
def initialize(data, id: nil, context: nil)
|
23
38
|
@id = id.presence || SecureRandom.hex
|
24
39
|
@context = context
|
25
40
|
@data = data
|
26
41
|
end
|
27
42
|
|
28
|
-
def valid?
|
29
|
-
mode.present?
|
30
|
-
end
|
31
|
-
|
32
|
-
def mode
|
33
|
-
@data[:mode]
|
34
|
-
end
|
35
|
-
|
36
|
-
def view
|
37
|
-
AppProfiler.viewer.view(self)
|
38
|
-
end
|
39
|
-
|
40
43
|
def upload
|
41
44
|
AppProfiler.storage.upload(self).tap do |upload|
|
42
45
|
if upload && defined?(upload.url)
|
@@ -60,6 +63,10 @@ module AppProfiler
|
|
60
63
|
AppProfiler.storage.enqueue_upload(self)
|
61
64
|
end
|
62
65
|
|
66
|
+
def valid?
|
67
|
+
mode.present?
|
68
|
+
end
|
69
|
+
|
63
70
|
def file
|
64
71
|
@file ||= path.tap do |p|
|
65
72
|
p.dirname.mkpath
|
@@ -71,6 +78,18 @@ module AppProfiler
|
|
71
78
|
@data
|
72
79
|
end
|
73
80
|
|
81
|
+
def mode
|
82
|
+
raise NotImplementedError
|
83
|
+
end
|
84
|
+
|
85
|
+
def format
|
86
|
+
raise NotImplementedError
|
87
|
+
end
|
88
|
+
|
89
|
+
def view(params = {})
|
90
|
+
raise NotImplementedError
|
91
|
+
end
|
92
|
+
|
74
93
|
private
|
75
94
|
|
76
95
|
def path
|
@@ -79,11 +98,14 @@ module AppProfiler
|
|
79
98
|
mode,
|
80
99
|
id,
|
81
100
|
Socket.gethostname,
|
82
|
-
].compact.join("-") <<
|
101
|
+
].compact.join("-") << format
|
83
102
|
|
84
103
|
raise UnsafeFilename if /[^0-9A-Za-z.\-\_]/.match?(filename)
|
85
104
|
|
86
105
|
AppProfiler.profile_root.join(filename)
|
87
106
|
end
|
88
107
|
end
|
108
|
+
|
109
|
+
include ActiveSupport::Deprecation::DeprecatedConstantAccessor
|
110
|
+
deprecate_constant "Profile", "AppProfiler::BaseProfile", deprecator: ActiveSupport::Deprecation.new
|
89
111
|
end
|
data/lib/app_profiler/railtie.rb
CHANGED
@@ -40,6 +40,7 @@ module AppProfiler
|
|
40
40
|
AppProfiler.profile_enqueue_success = app.config.app_profiler.profile_enqueue_success
|
41
41
|
AppProfiler.profile_enqueue_failure = app.config.app_profiler.profile_enqueue_failure
|
42
42
|
AppProfiler.after_process_queue = app.config.app_profiler.after_process_queue
|
43
|
+
AppProfiler.backend = app.config.app_profiler.profiler_backend || :stackprof
|
43
44
|
end
|
44
45
|
|
45
46
|
initializer "app_profiler.add_middleware" do |app|
|
@@ -16,17 +16,30 @@ module AppProfiler
|
|
16
16
|
query_param("async")
|
17
17
|
end
|
18
18
|
|
19
|
+
def backend
|
20
|
+
backend = query_param("backend") || profile_header_param("backend") ||
|
21
|
+
AppProfiler.backend
|
22
|
+
backend.to_sym
|
23
|
+
end
|
24
|
+
|
19
25
|
def valid?
|
20
26
|
if mode.blank?
|
21
27
|
return false
|
22
28
|
end
|
23
29
|
|
24
|
-
|
25
|
-
|
30
|
+
return false if backend != AppProfiler::Backend::StackprofBackend.name && !AppProfiler.vernier_supported?
|
31
|
+
|
32
|
+
if AppProfiler.vernier_supported? && backend == AppProfiler::Backend::VernierBackend.name &&
|
33
|
+
!AppProfiler::Backend::VernierBackend::AVAILABLE_MODES.include?(mode.to_sym)
|
34
|
+
AppProfiler.logger.info("[AppProfiler] unsupported profiling mode=#{mode} for backend #{backend}")
|
35
|
+
return false
|
36
|
+
elsif backend == AppProfiler::Backend::StackprofBackend.name &&
|
37
|
+
!AppProfiler::Backend::StackprofBackend::AVAILABLE_MODES.include?(mode.to_sym)
|
38
|
+
AppProfiler.logger.info("[AppProfiler] unsupported profiling mode=#{mode} for backend #{backend}")
|
26
39
|
return false
|
27
40
|
end
|
28
41
|
|
29
|
-
if interval.to_i < Parameters::MIN_INTERVALS[mode]
|
42
|
+
if interval.to_i < Parameters::MIN_INTERVALS[mode.to_s]
|
30
43
|
return false
|
31
44
|
end
|
32
45
|
|
@@ -38,6 +51,7 @@ module AppProfiler
|
|
38
51
|
mode: mode.to_sym,
|
39
52
|
interval: interval.to_i,
|
40
53
|
ignore_gc: !!ignore_gc,
|
54
|
+
backend: backend,
|
41
55
|
metadata: {
|
42
56
|
id: request_id,
|
43
57
|
context: context,
|
@@ -56,7 +70,7 @@ module AppProfiler
|
|
56
70
|
end
|
57
71
|
|
58
72
|
def interval
|
59
|
-
query_param("interval") || profile_header_param("interval") || Parameters::DEFAULT_INTERVALS[mode]
|
73
|
+
query_param("interval") || profile_header_param("interval") || Parameters::DEFAULT_INTERVALS[mode.to_s]
|
60
74
|
end
|
61
75
|
|
62
76
|
def request_id
|
data/lib/app_profiler/version.rb
CHANGED
@@ -4,12 +4,12 @@ module AppProfiler
|
|
4
4
|
module Viewer
|
5
5
|
class BaseViewer
|
6
6
|
class << self
|
7
|
-
def view(profile)
|
8
|
-
new(profile).view
|
7
|
+
def view(profile, params = {})
|
8
|
+
new(profile).view(**params)
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
def view(
|
12
|
+
def view(_params = {})
|
13
13
|
raise NotImplementedError
|
14
14
|
end
|
15
15
|
end
|
@@ -7,8 +7,8 @@ module AppProfiler
|
|
7
7
|
module Viewer
|
8
8
|
class SpeedscopeRemoteViewer < BaseViewer
|
9
9
|
class << self
|
10
|
-
def view(profile)
|
11
|
-
new(profile).view
|
10
|
+
def view(profile, params = {})
|
11
|
+
new(profile).view(**params)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
@@ -17,9 +17,15 @@ module AppProfiler
|
|
17
17
|
@profile = profile
|
18
18
|
end
|
19
19
|
|
20
|
-
def view
|
20
|
+
def view(response: nil, autoredirect: nil, async: false)
|
21
21
|
id = Middleware.id(@profile.file)
|
22
|
-
|
22
|
+
|
23
|
+
if response && response[0].to_i < 500
|
24
|
+
response[1]["Location"] = "/app_profiler/#{id}"
|
25
|
+
response[0] = 303
|
26
|
+
else
|
27
|
+
AppProfiler.logger.info("[Profiler] Profile available at /app_profiler/#{id}\n")
|
28
|
+
end
|
23
29
|
end
|
24
30
|
end
|
25
31
|
end
|
@@ -9,8 +9,8 @@ module AppProfiler
|
|
9
9
|
include Yarn::WithSpeedscope
|
10
10
|
|
11
11
|
class << self
|
12
|
-
def view(profile)
|
13
|
-
new(profile).view
|
12
|
+
def view(profile, params = {})
|
13
|
+
new(profile).view(**params)
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
@@ -19,7 +19,7 @@ module AppProfiler
|
|
19
19
|
@profile = profile
|
20
20
|
end
|
21
21
|
|
22
|
-
def view
|
22
|
+
def view(_params = {})
|
23
23
|
yarn("run", "speedscope", @profile.file.to_s)
|
24
24
|
end
|
25
25
|
end
|
data/lib/app_profiler.rb
CHANGED
@@ -9,6 +9,9 @@ module AppProfiler
|
|
9
9
|
class ConfigurationError < StandardError
|
10
10
|
end
|
11
11
|
|
12
|
+
class BackendError < StandardError
|
13
|
+
end
|
14
|
+
|
12
15
|
DefaultProfileFormatter = proc do |upload|
|
13
16
|
"#{AppProfiler.speedscope_host}#profileURL=#{upload.url}"
|
14
17
|
end
|
@@ -32,8 +35,8 @@ module AppProfiler
|
|
32
35
|
require "app_profiler/middleware"
|
33
36
|
require "app_profiler/parameters"
|
34
37
|
require "app_profiler/request_parameters"
|
35
|
-
require "app_profiler/profiler"
|
36
38
|
require "app_profiler/profile"
|
39
|
+
require "app_profiler/backend"
|
37
40
|
require "app_profiler/server"
|
38
41
|
|
39
42
|
mattr_accessor :logger, default: Logger.new($stdout)
|
@@ -60,17 +63,73 @@ module AppProfiler
|
|
60
63
|
mattr_reader :after_process_queue, default: nil
|
61
64
|
|
62
65
|
class << self
|
63
|
-
def run(*args, &block)
|
64
|
-
|
66
|
+
def run(*args, backend: nil, **kwargs, &block)
|
67
|
+
orig_backend = self.backend
|
68
|
+
begin
|
69
|
+
self.backend = backend if backend
|
70
|
+
profiler.run(*args, **kwargs, &block)
|
71
|
+
rescue BackendError => e
|
72
|
+
logger.error(
|
73
|
+
"[AppProfiler.run] exception #{e} configuring backend #{backend}: #{e.message}"
|
74
|
+
)
|
75
|
+
yield
|
76
|
+
end
|
77
|
+
ensure
|
78
|
+
AppProfiler.backend = orig_backend
|
65
79
|
end
|
66
80
|
|
67
81
|
def start(*args)
|
68
|
-
|
82
|
+
profiler.start(*args)
|
69
83
|
end
|
70
84
|
|
71
85
|
def stop
|
72
|
-
|
73
|
-
|
86
|
+
profiler.stop
|
87
|
+
profiler.results
|
88
|
+
end
|
89
|
+
|
90
|
+
def running?
|
91
|
+
@backend&.running?
|
92
|
+
end
|
93
|
+
|
94
|
+
def profiler
|
95
|
+
backend
|
96
|
+
@backend ||= @profiler_backend.new
|
97
|
+
end
|
98
|
+
|
99
|
+
def backend=(new_backend)
|
100
|
+
return if new_backend == backend
|
101
|
+
|
102
|
+
new_profiler_backend = backend_for(new_backend)
|
103
|
+
|
104
|
+
if running?
|
105
|
+
raise BackendError,
|
106
|
+
"cannot change backend to #{new_backend} while #{backend} backend is running"
|
107
|
+
end
|
108
|
+
|
109
|
+
return if @profiler_backend == new_profiler_backend
|
110
|
+
|
111
|
+
clear
|
112
|
+
@profiler_backend = new_profiler_backend
|
113
|
+
end
|
114
|
+
|
115
|
+
def backend_for(backend_name)
|
116
|
+
if vernier_supported? &&
|
117
|
+
backend_name == AppProfiler::Backend::VernierBackend.name
|
118
|
+
AppProfiler::Backend::VernierBackend
|
119
|
+
elsif backend_name == AppProfiler::Backend::StackprofBackend.name
|
120
|
+
AppProfiler::Backend::StackprofBackend
|
121
|
+
else
|
122
|
+
raise BackendError, "unknown backend #{backend_name}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def backend
|
127
|
+
@profiler_backend ||= Backend::StackprofBackend
|
128
|
+
@profiler_backend.name
|
129
|
+
end
|
130
|
+
|
131
|
+
def vernier_supported?
|
132
|
+
defined?(AppProfiler::Backend::VernierBackend.name)
|
74
133
|
end
|
75
134
|
|
76
135
|
def profile_header=(profile_header)
|
@@ -120,6 +179,13 @@ module AppProfiler
|
|
120
179
|
|
121
180
|
AppProfiler.profile_url_formatter.call(upload)
|
122
181
|
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
def clear
|
186
|
+
@backend.stop if @backend&.running?
|
187
|
+
@backend = nil
|
188
|
+
end
|
123
189
|
end
|
124
190
|
|
125
191
|
require "app_profiler/railtie" if defined?(Rails::Railtie)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: app_profiler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gannon McGibbon
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
|
-
date:
|
16
|
+
date: 2024-06-18 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: activesupport
|
@@ -135,13 +135,18 @@ extensions: []
|
|
135
135
|
extra_rdoc_files: []
|
136
136
|
files:
|
137
137
|
- lib/app_profiler.rb
|
138
|
+
- lib/app_profiler/backend.rb
|
139
|
+
- lib/app_profiler/backend/base_backend.rb
|
140
|
+
- lib/app_profiler/backend/stackprof_backend.rb
|
141
|
+
- lib/app_profiler/backend/vernier_backend.rb
|
138
142
|
- lib/app_profiler/middleware.rb
|
139
143
|
- lib/app_profiler/middleware/base_action.rb
|
140
144
|
- lib/app_profiler/middleware/upload_action.rb
|
141
145
|
- lib/app_profiler/middleware/view_action.rb
|
142
146
|
- lib/app_profiler/parameters.rb
|
143
147
|
- lib/app_profiler/profile.rb
|
144
|
-
- lib/app_profiler/
|
148
|
+
- lib/app_profiler/profile/stackprof.rb
|
149
|
+
- lib/app_profiler/profile/vernier.rb
|
145
150
|
- lib/app_profiler/railtie.rb
|
146
151
|
- lib/app_profiler/request_parameters.rb
|
147
152
|
- lib/app_profiler/server.rb
|
@@ -175,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
175
180
|
- !ruby/object:Gem::Version
|
176
181
|
version: '0'
|
177
182
|
requirements: []
|
178
|
-
rubygems_version: 3.
|
183
|
+
rubygems_version: 3.5.11
|
179
184
|
signing_key:
|
180
185
|
specification_version: 4
|
181
186
|
summary: Collect performance profiles for your Rails application.
|