app_profiler 0.1.10 → 0.2.1
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} +32 -18
- data/lib/app_profiler/backend/vernier_backend.rb +108 -0
- data/lib/app_profiler/backend.rb +9 -0
- data/lib/app_profiler/middleware/base_action.rb +1 -1
- data/lib/app_profiler/middleware/upload_action.rb +1 -1
- data/lib/app_profiler/middleware.rb +2 -2
- 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 +51 -23
- data/lib/app_profiler/railtie.rb +2 -0
- data/lib/app_profiler/request_parameters.rb +18 -4
- data/lib/app_profiler/server.rb +2 -2
- data/lib/app_profiler/storage/base_storage.rb +7 -5
- data/lib/app_profiler/storage/google_cloud_storage.rb +5 -0
- data/lib/app_profiler/version.rb +1 -1
- data/lib/app_profiler/viewer/speedscope_remote_viewer/base_middleware.rb +47 -7
- data/lib/app_profiler/viewer/speedscope_remote_viewer/middleware.rb +2 -2
- data/lib/app_profiler/yarn/command.rb +1 -1
- data/lib/app_profiler.rb +73 -6
- metadata +13 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: daf174f3e936f7c9e466d16f75cc866f779944ae781e39501dae6d51cd74da42
|
4
|
+
data.tar.gz: 42871b642bf002450af142941793c41a1bfd76d6b6c6932b45e6cca9c287cb58
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf696efe9cfe2582aafd201e30fe6f82aae17f1b137e93a7d2e2b2c34256f4ace88de0d771fc398844c1010999b5c9424f1b2ab0026395ee88b2ad440a88d54a
|
7
|
+
data.tar.gz: eb47ff8a3c0131065400a702d447fb8c170c9d2769860921be61d082ad6affe1ca714bbd211fd2cfe2aec78a89a57bbd32fc76d950d5fa1531feb5b43e29dac9
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppProfiler
|
4
|
+
module Backend
|
5
|
+
class BaseBackend
|
6
|
+
def run(params = {}, &block)
|
7
|
+
raise NotImplementedError
|
8
|
+
end
|
9
|
+
|
10
|
+
def start(params = {})
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
def stop
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
def results
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def running?
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
def run_lock
|
28
|
+
@run_lock ||= Mutex.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def name
|
32
|
+
raise NotImplementedError
|
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,25 @@
|
|
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
|
+
class << self
|
20
|
+
def name
|
21
|
+
:stackprof
|
22
|
+
end
|
23
|
+
end
|
11
24
|
|
12
|
-
class << self
|
13
25
|
def run(params = {})
|
14
26
|
started = start(params)
|
15
27
|
|
@@ -27,14 +39,16 @@ module AppProfiler
|
|
27
39
|
def start(params = {})
|
28
40
|
# Do not start the profiler if StackProf was started somewhere else.
|
29
41
|
return false if running?
|
42
|
+
return false unless acquire_run_lock
|
30
43
|
|
31
44
|
clear
|
32
45
|
|
33
46
|
StackProf.start(**DEFAULTS, **params)
|
34
47
|
rescue => error
|
35
48
|
AppProfiler.logger.info(
|
36
|
-
"[Profiler] failed to start the profiler error_class=#{error.class} error_message=#{error.message}"
|
49
|
+
"[Profiler] failed to start the profiler error_class=#{error.class} error_message=#{error.message}",
|
37
50
|
)
|
51
|
+
release_run_lock
|
38
52
|
# This is a boolean instead of nil because StackProf#start returns a
|
39
53
|
# boolean as well.
|
40
54
|
false
|
@@ -42,24 +56,30 @@ module AppProfiler
|
|
42
56
|
|
43
57
|
def stop
|
44
58
|
StackProf.stop
|
59
|
+
ensure
|
60
|
+
release_run_lock
|
45
61
|
end
|
46
62
|
|
47
63
|
def results
|
48
|
-
stackprof_profile =
|
64
|
+
stackprof_profile = backend_results
|
49
65
|
|
50
66
|
return unless stackprof_profile
|
51
67
|
|
52
|
-
|
68
|
+
BaseProfile.from_stackprof(stackprof_profile)
|
53
69
|
rescue => error
|
54
70
|
AppProfiler.logger.info(
|
55
|
-
"[Profiler] failed to obtain the profile error_class=#{error.class} error_message=#{error.message}"
|
71
|
+
"[Profiler] failed to obtain the profile error_class=#{error.class} error_message=#{error.message}",
|
56
72
|
)
|
57
73
|
nil
|
58
74
|
end
|
59
75
|
|
76
|
+
def running?
|
77
|
+
StackProf.running?
|
78
|
+
end
|
79
|
+
|
60
80
|
private
|
61
81
|
|
62
|
-
def
|
82
|
+
def backend_results
|
63
83
|
StackProf.results
|
64
84
|
end
|
65
85
|
|
@@ -73,14 +93,8 @@ module AppProfiler
|
|
73
93
|
# Ref: https://github.com/tmm1/stackprof/blob/0ded6c/ext/stackprof/stackprof.c#L118-L123
|
74
94
|
#
|
75
95
|
def clear
|
76
|
-
|
77
|
-
end
|
78
|
-
|
79
|
-
def running?
|
80
|
-
StackProf.running?
|
96
|
+
backend_results
|
81
97
|
end
|
82
98
|
end
|
83
99
|
end
|
84
|
-
|
85
|
-
private_constant :Profiler
|
86
100
|
end
|
@@ -0,0 +1,108 @@
|
|
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
|
+
class << self
|
19
|
+
def name
|
20
|
+
:vernier
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def run(params = {})
|
25
|
+
started = start(params)
|
26
|
+
|
27
|
+
yield
|
28
|
+
|
29
|
+
return unless started
|
30
|
+
|
31
|
+
stop
|
32
|
+
results
|
33
|
+
ensure
|
34
|
+
# Only stop the profiler if profiling was started in this context.
|
35
|
+
stop if started
|
36
|
+
end
|
37
|
+
|
38
|
+
def start(params = {})
|
39
|
+
# Do not start the profiler if we already have a collector started somewhere else.
|
40
|
+
return false if running?
|
41
|
+
return false unless acquire_run_lock
|
42
|
+
|
43
|
+
@mode = params.delete(:mode) || DEFAULTS[:mode]
|
44
|
+
raise ArgumentError unless AVAILABLE_MODES.include?(@mode)
|
45
|
+
|
46
|
+
@metadata = params.delete(:metadata)
|
47
|
+
clear
|
48
|
+
|
49
|
+
@collector ||= ::Vernier::Collector.new(@mode, **params)
|
50
|
+
@collector.start
|
51
|
+
rescue => error
|
52
|
+
AppProfiler.logger.info(
|
53
|
+
"[Profiler] failed to start the profiler error_class=#{error.class} error_message=#{error.message}",
|
54
|
+
)
|
55
|
+
release_run_lock
|
56
|
+
# This is a boolean instead of nil to be consistent with the stackprof backend behaviour
|
57
|
+
# boolean as well.
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
def stop
|
62
|
+
return false unless running?
|
63
|
+
|
64
|
+
@results = @collector&.stop
|
65
|
+
@collector = nil
|
66
|
+
!@results.nil?
|
67
|
+
ensure
|
68
|
+
release_run_lock
|
69
|
+
end
|
70
|
+
|
71
|
+
def results
|
72
|
+
vernier_profile = backend_results
|
73
|
+
clear
|
74
|
+
|
75
|
+
return unless vernier_profile
|
76
|
+
|
77
|
+
# HACK: - "data" is private, but we want to avoid serializing to JSON then
|
78
|
+
# parsing back from JSON by just directly getting the hash
|
79
|
+
data = ::Vernier::Output::Firefox.new(vernier_profile).send(:data)
|
80
|
+
data[:meta][:mode] = @mode # TODO: https://github.com/jhawthorn/vernier/issues/30
|
81
|
+
data[:meta].merge!(@metadata) if @metadata
|
82
|
+
@mode = nil
|
83
|
+
@metadata = nil
|
84
|
+
|
85
|
+
BaseProfile.from_vernier(data)
|
86
|
+
rescue => error
|
87
|
+
AppProfiler.logger.info(
|
88
|
+
"[Profiler] failed to obtain the profile error_class=#{error.class} error_message=#{error.message}",
|
89
|
+
)
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
|
93
|
+
def running?
|
94
|
+
@collector != nil
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def backend_results
|
100
|
+
@results
|
101
|
+
end
|
102
|
+
|
103
|
+
def clear
|
104
|
+
@results = nil
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
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
|
@@ -14,7 +14,7 @@ module AppProfiler
|
|
14
14
|
append_headers(
|
15
15
|
response,
|
16
16
|
upload: profile_upload,
|
17
|
-
autoredirect: autoredirect.nil? ? AppProfiler.autoredirect : autoredirect
|
17
|
+
autoredirect: autoredirect.nil? ? AppProfiler.autoredirect : autoredirect,
|
18
18
|
) if response
|
19
19
|
end
|
20
20
|
end
|
@@ -31,7 +31,7 @@ module AppProfiler
|
|
31
31
|
|
32
32
|
return yield unless before_profile(env, params_hash)
|
33
33
|
|
34
|
-
profile = AppProfiler.run(params_hash) do
|
34
|
+
profile = AppProfiler.run(**params_hash) do
|
35
35
|
response = yield
|
36
36
|
end
|
37
37
|
|
@@ -41,7 +41,7 @@ module AppProfiler
|
|
41
41
|
profile,
|
42
42
|
response: response,
|
43
43
|
autoredirect: params.autoredirect,
|
44
|
-
async: params.async
|
44
|
+
async: params.async,
|
45
45
|
)
|
46
46
|
|
47
47
|
response
|
@@ -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,47 +1,52 @@
|
|
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
|
|
12
|
-
|
13
|
-
|
14
|
-
|
16
|
+
delegate :[], to: :@data
|
17
|
+
|
18
|
+
class << self
|
19
|
+
# This function should not be called if `StackProf.results` returns nil.
|
20
|
+
def from_stackprof(data)
|
21
|
+
options = INTERNAL_METADATA_KEYS.map { |key| [key, data[:metadata]&.delete(key)] }.to_h
|
15
22
|
|
16
|
-
|
17
|
-
|
23
|
+
StackprofProfile.new(data, **options).tap do |profile|
|
24
|
+
raise ArgumentError, "invalid profile data" unless profile.valid?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def from_vernier(data)
|
29
|
+
options = INTERNAL_METADATA_KEYS.map { |key| [key, data[:meta]&.delete(key)] }.to_h
|
30
|
+
|
31
|
+
VernierProfile.new(data, **options).tap do |profile|
|
32
|
+
raise ArgumentError, "invalid profile data" unless profile.valid?
|
33
|
+
end
|
18
34
|
end
|
19
35
|
end
|
20
36
|
|
21
|
-
# `data` is assumed to be a Hash
|
37
|
+
# `data` is assumed to be a Hash for Stackprof,
|
38
|
+
# a vernier "result" object for vernier
|
22
39
|
def initialize(data, id: nil, context: nil)
|
23
40
|
@id = id.presence || SecureRandom.hex
|
24
41
|
@context = context
|
25
42
|
@data = data
|
26
43
|
end
|
27
44
|
|
28
|
-
def valid?
|
29
|
-
mode.present?
|
30
|
-
end
|
31
|
-
|
32
|
-
def mode
|
33
|
-
@data[:mode]
|
34
|
-
end
|
35
|
-
|
36
|
-
def view(params = {})
|
37
|
-
AppProfiler.viewer.view(self, **params)
|
38
|
-
end
|
39
|
-
|
40
45
|
def upload
|
41
46
|
AppProfiler.storage.upload(self).tap do |upload|
|
42
47
|
if upload && defined?(upload.url)
|
43
48
|
AppProfiler.logger.info(
|
44
|
-
<<~INFO.squish
|
49
|
+
<<~INFO.squish,
|
45
50
|
[Profiler] data uploaded:
|
46
51
|
profile_url=#{upload.url}
|
47
52
|
profile_viewer_url=#{AppProfiler.profile_url(upload)}
|
@@ -51,7 +56,7 @@ module AppProfiler
|
|
51
56
|
end
|
52
57
|
rescue => error
|
53
58
|
AppProfiler.logger.info(
|
54
|
-
"[Profiler] failed to upload profile error_class=#{error.class} error_message=#{error.message}"
|
59
|
+
"[Profiler] failed to upload profile error_class=#{error.class} error_message=#{error.message}",
|
55
60
|
)
|
56
61
|
nil
|
57
62
|
end
|
@@ -60,6 +65,10 @@ module AppProfiler
|
|
60
65
|
AppProfiler.storage.enqueue_upload(self)
|
61
66
|
end
|
62
67
|
|
68
|
+
def valid?
|
69
|
+
mode.present?
|
70
|
+
end
|
71
|
+
|
63
72
|
def file
|
64
73
|
@file ||= path.tap do |p|
|
65
74
|
p.dirname.mkpath
|
@@ -71,6 +80,22 @@ module AppProfiler
|
|
71
80
|
@data
|
72
81
|
end
|
73
82
|
|
83
|
+
def metadata
|
84
|
+
@data[:metadata]
|
85
|
+
end
|
86
|
+
|
87
|
+
def mode
|
88
|
+
raise NotImplementedError
|
89
|
+
end
|
90
|
+
|
91
|
+
def format
|
92
|
+
raise NotImplementedError
|
93
|
+
end
|
94
|
+
|
95
|
+
def view(params = {})
|
96
|
+
raise NotImplementedError
|
97
|
+
end
|
98
|
+
|
74
99
|
private
|
75
100
|
|
76
101
|
def path
|
@@ -79,11 +104,14 @@ module AppProfiler
|
|
79
104
|
mode,
|
80
105
|
id,
|
81
106
|
Socket.gethostname,
|
82
|
-
].compact.join("-") <<
|
107
|
+
].compact.join("-") << format
|
83
108
|
|
84
109
|
raise UnsafeFilename if /[^0-9A-Za-z.\-\_]/.match?(filename)
|
85
110
|
|
86
111
|
AppProfiler.profile_root.join(filename)
|
87
112
|
end
|
88
113
|
end
|
114
|
+
|
115
|
+
include ActiveSupport::Deprecation::DeprecatedConstantAccessor
|
116
|
+
deprecate_constant "Profile", "AppProfiler::BaseProfile", deprecator: ActiveSupport::Deprecation.new
|
89
117
|
end
|
data/lib/app_profiler/railtie.rb
CHANGED
@@ -40,6 +40,8 @@ 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
|
44
|
+
AppProfiler.forward_metadata_on_upload = app.config.app_profiler.forward_metadata_on_upload || false
|
43
45
|
end
|
44
46
|
|
45
47
|
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/server.rb
CHANGED
@@ -280,7 +280,7 @@ module AppProfiler
|
|
280
280
|
@listen_thread = nil
|
281
281
|
|
282
282
|
@logger.info(
|
283
|
-
"[AppProfiler::Server] listening on addr=#{@transport.socket.addr}"
|
283
|
+
"[AppProfiler::Server] listening on addr=#{@transport.socket.addr}",
|
284
284
|
)
|
285
285
|
@pid = Process.pid
|
286
286
|
end
|
@@ -342,7 +342,7 @@ module AppProfiler
|
|
342
342
|
end
|
343
343
|
rescue => e
|
344
344
|
@logger.error(
|
345
|
-
"[AppProfiler::Server] exception #{e} responding to request #{request}: #{e.message}"
|
345
|
+
"[AppProfiler::Server] exception #{e} responding to request #{request}: #{e.message}",
|
346
346
|
)
|
347
347
|
ensure
|
348
348
|
session.close
|
@@ -6,12 +6,14 @@ module AppProfiler
|
|
6
6
|
class_attribute :bucket_name, default: "profiles"
|
7
7
|
class_attribute :credentials, default: {}
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
class << self
|
10
|
+
def upload(_profile)
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
14
|
+
def enqueue_upload(_profile)
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
15
17
|
end
|
16
18
|
end
|
17
19
|
end
|
@@ -15,6 +15,10 @@ module AppProfiler
|
|
15
15
|
def upload(profile, _params = {})
|
16
16
|
file = profile.file.open
|
17
17
|
|
18
|
+
metadata = if AppProfiler.forward_metadata_on_upload && profile.metadata.present?
|
19
|
+
profile.metadata
|
20
|
+
end
|
21
|
+
|
18
22
|
ActiveSupport::Notifications.instrument(
|
19
23
|
"gcs_upload.app_profiler",
|
20
24
|
file_size: file.size,
|
@@ -24,6 +28,7 @@ module AppProfiler
|
|
24
28
|
gcs_filename(profile),
|
25
29
|
content_type: "application/json",
|
26
30
|
content_encoding: "gzip",
|
31
|
+
metadata: metadata,
|
27
32
|
)
|
28
33
|
ensure
|
29
34
|
profile.file.unlink
|
data/lib/app_profiler/version.rb
CHANGED
@@ -9,17 +9,57 @@ module AppProfiler
|
|
9
9
|
class BaseMiddleware
|
10
10
|
class Sanitizer < Rails::HTML::Sanitizer.best_supported_vendor.safe_list_sanitizer
|
11
11
|
self.allowed_tags = Set.new([
|
12
|
-
"strong",
|
13
|
-
"
|
14
|
-
"
|
15
|
-
"
|
12
|
+
"strong",
|
13
|
+
"em",
|
14
|
+
"b",
|
15
|
+
"i",
|
16
|
+
"p",
|
17
|
+
"code",
|
18
|
+
"pre",
|
19
|
+
"tt",
|
20
|
+
"samp",
|
21
|
+
"kbd",
|
22
|
+
"var",
|
23
|
+
"sub",
|
24
|
+
"sup",
|
25
|
+
"dfn",
|
26
|
+
"cite",
|
27
|
+
"big",
|
28
|
+
"small",
|
29
|
+
"address",
|
30
|
+
"hr",
|
31
|
+
"br",
|
32
|
+
"div",
|
33
|
+
"span",
|
34
|
+
"h1",
|
35
|
+
"h2",
|
36
|
+
"h3",
|
37
|
+
"h4",
|
38
|
+
"h5",
|
39
|
+
"h6",
|
40
|
+
"ul",
|
41
|
+
"ol",
|
42
|
+
"li",
|
43
|
+
"dl",
|
44
|
+
"dt",
|
45
|
+
"dd",
|
46
|
+
"abbr",
|
47
|
+
"acronym",
|
48
|
+
"a",
|
49
|
+
"img",
|
50
|
+
"blockquote",
|
51
|
+
"del",
|
52
|
+
"ins",
|
53
|
+
"script",
|
16
54
|
])
|
17
55
|
end
|
18
56
|
|
19
57
|
private_constant(:Sanitizer)
|
20
58
|
|
21
|
-
|
22
|
-
file
|
59
|
+
class << self
|
60
|
+
def id(file)
|
61
|
+
file.basename.to_s.delete_suffix(".json")
|
62
|
+
end
|
23
63
|
end
|
24
64
|
|
25
65
|
def initialize(app)
|
@@ -87,7 +127,7 @@ module AppProfiler
|
|
87
127
|
</p>
|
88
128
|
HTML
|
89
129
|
end
|
90
|
-
end
|
130
|
+
end,
|
91
131
|
)
|
92
132
|
end
|
93
133
|
|
@@ -12,7 +12,7 @@ module AppProfiler
|
|
12
12
|
def initialize(app)
|
13
13
|
super
|
14
14
|
@speedscope = Rack::File.new(
|
15
|
-
File.join(AppProfiler.root, "node_modules/speedscope/dist/release")
|
15
|
+
File.join(AppProfiler.root, "node_modules/speedscope/dist/release"),
|
16
16
|
)
|
17
17
|
end
|
18
18
|
|
@@ -33,7 +33,7 @@ module AppProfiler
|
|
33
33
|
end || raise(ArgumentError)
|
34
34
|
|
35
35
|
render(
|
36
|
-
<<~HTML
|
36
|
+
<<~HTML,
|
37
37
|
<script type="text/javascript">
|
38
38
|
var graph = #{profile.read};
|
39
39
|
var json = JSON.stringify(graph);
|
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)
|
@@ -58,19 +61,76 @@ module AppProfiler
|
|
58
61
|
mattr_reader :profile_enqueue_success, default: nil
|
59
62
|
mattr_reader :profile_enqueue_failure, default: nil
|
60
63
|
mattr_reader :after_process_queue, default: nil
|
64
|
+
mattr_accessor :forward_metadata_on_upload, default: false
|
61
65
|
|
62
66
|
class << self
|
63
|
-
def run(*args, &block)
|
64
|
-
|
67
|
+
def run(*args, backend: nil, **kwargs, &block)
|
68
|
+
orig_backend = self.backend
|
69
|
+
begin
|
70
|
+
self.backend = backend if backend
|
71
|
+
profiler.run(*args, **kwargs, &block)
|
72
|
+
rescue BackendError => e
|
73
|
+
logger.error(
|
74
|
+
"[AppProfiler.run] exception #{e} configuring backend #{backend}: #{e.message}",
|
75
|
+
)
|
76
|
+
yield
|
77
|
+
end
|
78
|
+
ensure
|
79
|
+
AppProfiler.backend = orig_backend
|
65
80
|
end
|
66
81
|
|
67
82
|
def start(*args)
|
68
|
-
|
83
|
+
profiler.start(*args)
|
69
84
|
end
|
70
85
|
|
71
86
|
def stop
|
72
|
-
|
73
|
-
|
87
|
+
profiler.stop
|
88
|
+
profiler.results
|
89
|
+
end
|
90
|
+
|
91
|
+
def running?
|
92
|
+
@backend&.running?
|
93
|
+
end
|
94
|
+
|
95
|
+
def profiler
|
96
|
+
backend
|
97
|
+
@backend ||= @profiler_backend.new
|
98
|
+
end
|
99
|
+
|
100
|
+
def backend=(new_backend)
|
101
|
+
return if new_backend == backend
|
102
|
+
|
103
|
+
new_profiler_backend = backend_for(new_backend)
|
104
|
+
|
105
|
+
if running?
|
106
|
+
raise BackendError,
|
107
|
+
"cannot change backend to #{new_backend} while #{backend} backend is running"
|
108
|
+
end
|
109
|
+
|
110
|
+
return if @profiler_backend == new_profiler_backend
|
111
|
+
|
112
|
+
clear
|
113
|
+
@profiler_backend = new_profiler_backend
|
114
|
+
end
|
115
|
+
|
116
|
+
def backend_for(backend_name)
|
117
|
+
if vernier_supported? &&
|
118
|
+
backend_name == AppProfiler::Backend::VernierBackend.name
|
119
|
+
AppProfiler::Backend::VernierBackend
|
120
|
+
elsif backend_name == AppProfiler::Backend::StackprofBackend.name
|
121
|
+
AppProfiler::Backend::StackprofBackend
|
122
|
+
else
|
123
|
+
raise BackendError, "unknown backend #{backend_name}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def backend
|
128
|
+
@profiler_backend ||= Backend::StackprofBackend
|
129
|
+
@profiler_backend.name
|
130
|
+
end
|
131
|
+
|
132
|
+
def vernier_supported?
|
133
|
+
defined?(AppProfiler::Backend::VernierBackend.name)
|
74
134
|
end
|
75
135
|
|
76
136
|
def profile_header=(profile_header)
|
@@ -120,6 +180,13 @@ module AppProfiler
|
|
120
180
|
|
121
181
|
AppProfiler.profile_url_formatter.call(upload)
|
122
182
|
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def clear
|
187
|
+
@backend.stop if @backend&.running?
|
188
|
+
@backend = nil
|
189
|
+
end
|
123
190
|
end
|
124
191
|
|
125
192
|
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.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gannon McGibbon
|
@@ -10,10 +10,10 @@ authors:
|
|
10
10
|
- Jon Simpson
|
11
11
|
- Kevin Jalbert
|
12
12
|
- Scott Francis
|
13
|
-
autorequire:
|
13
|
+
autorequire:
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
|
-
date:
|
16
|
+
date: 2024-08-01 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: activesupport
|
@@ -127,7 +127,7 @@ dependencies:
|
|
127
127
|
- - ">="
|
128
128
|
- !ruby/object:Gem::Version
|
129
129
|
version: '0'
|
130
|
-
description:
|
130
|
+
description:
|
131
131
|
email:
|
132
132
|
- gems@shopify.com
|
133
133
|
executables: []
|
@@ -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
|
@@ -160,7 +165,7 @@ homepage: https://github.com/Shopify/app_profiler
|
|
160
165
|
licenses: []
|
161
166
|
metadata:
|
162
167
|
allowed_push_host: https://rubygems.org
|
163
|
-
post_install_message:
|
168
|
+
post_install_message:
|
164
169
|
rdoc_options: []
|
165
170
|
require_paths:
|
166
171
|
- lib
|
@@ -175,8 +180,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
175
180
|
- !ruby/object:Gem::Version
|
176
181
|
version: '0'
|
177
182
|
requirements: []
|
178
|
-
rubygems_version: 3.
|
179
|
-
signing_key:
|
183
|
+
rubygems_version: 3.5.16
|
184
|
+
signing_key:
|
180
185
|
specification_version: 4
|
181
186
|
summary: Collect performance profiles for your Rails application.
|
182
187
|
test_files: []
|