app_profiler 0.0.8 → 0.0.9
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/middleware/upload_action.rb +1 -1
- data/lib/app_profiler/railtie.rb +3 -0
- data/lib/app_profiler/version.rb +1 -1
- data/lib/app_profiler/viewer/base_viewer.rb +7 -1
- data/lib/app_profiler/viewer/speedscope_remote_viewer/base_middleware.rb +99 -0
- data/lib/app_profiler/viewer/speedscope_remote_viewer/middleware.rb +58 -0
- data/lib/app_profiler/viewer/speedscope_remote_viewer.rb +26 -0
- data/lib/app_profiler/viewer/speedscope_viewer.rb +5 -49
- data/lib/app_profiler/yarn/command.rb +75 -0
- data/lib/app_profiler/yarn/with_speedscope.rb +22 -0
- data/lib/app_profiler.rb +8 -5
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 160040c64c01368d5a6832e6fccc20ed14eecdd7ff0a4a9a18e4f7aeee62c83b
|
4
|
+
data.tar.gz: 76107cd56b460c68e33ef32677a3d822e83319b3cb9084e39e30841ee8bb8db7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c926b73ab9abe0773f5f7d2415672a89f444c2c3448387f57e07fe69b359c6a70175beaaba91171bda2f24702dae7073b76f094ef9d5de8fcc7ca360c3cc0a4
|
7
|
+
data.tar.gz: eb3af95b1164676ec400acd49732532e74c91565cfa70db31db7fbbcb00b188d6e68c5e35ae02ef526bf55694438e48a1fe97ece448a7de649cd0198871fd9de
|
data/lib/app_profiler/railtie.rb
CHANGED
@@ -30,6 +30,9 @@ module AppProfiler
|
|
30
30
|
|
31
31
|
initializer "app_profiler.add_middleware" do |app|
|
32
32
|
unless AppProfiler.middleware.disabled
|
33
|
+
if AppProfiler.viewer == Viewer::SpeedscopeRemoteViewer
|
34
|
+
app.middleware.insert_before(0, Viewer::SpeedscopeRemoteViewer::Middleware)
|
35
|
+
end
|
33
36
|
app.middleware.insert_before(0, AppProfiler.middleware)
|
34
37
|
end
|
35
38
|
end
|
data/lib/app_profiler/version.rb
CHANGED
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails-html-sanitizer"
|
4
|
+
|
5
|
+
module AppProfiler
|
6
|
+
module Viewer
|
7
|
+
class SpeedscopeRemoteViewer < BaseViewer
|
8
|
+
class BaseMiddleware
|
9
|
+
class Sanitizer < Rails::Html::SafeListSanitizer
|
10
|
+
self.allowed_tags = Set.new(%w(strong em b i p code pre tt samp kbd var sub
|
11
|
+
sup dfn cite big small address hr br div span
|
12
|
+
h1 h2 h3 h4 h5 h6 ul ol li dl dt dd abbr
|
13
|
+
acronym a img blockquote del ins script))
|
14
|
+
end
|
15
|
+
|
16
|
+
private_constant(:Sanitizer)
|
17
|
+
|
18
|
+
def self.id(file)
|
19
|
+
file.basename.to_s.delete_suffix(".json")
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(app)
|
23
|
+
@app = app
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(env)
|
27
|
+
request = Rack::Request.new(env)
|
28
|
+
|
29
|
+
return index(env) if request.path_info =~ %r(\A/app_profiler/?\z)
|
30
|
+
return viewer(env, Regexp.last_match(1)) if request.path_info =~ %r(\A/app_profiler/viewer/(.*)\z)
|
31
|
+
return show(env, Regexp.last_match(1)) if request.path_info =~ %r(\A/app_profiler/(.*)\z)
|
32
|
+
|
33
|
+
@app.call(env)
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def id(file)
|
39
|
+
self.class.id(file)
|
40
|
+
end
|
41
|
+
|
42
|
+
def profile_files
|
43
|
+
AppProfiler.profile_root.glob("**/*.json")
|
44
|
+
end
|
45
|
+
|
46
|
+
def render(html)
|
47
|
+
[
|
48
|
+
200,
|
49
|
+
{ "Content-Type" => "text/html" },
|
50
|
+
[
|
51
|
+
+<<~HTML,
|
52
|
+
<!doctype html>
|
53
|
+
<html>
|
54
|
+
<head>
|
55
|
+
<title>App Profiler</title>
|
56
|
+
</head>
|
57
|
+
<body>
|
58
|
+
#{sanitizer.sanitize(html)}
|
59
|
+
</body>
|
60
|
+
</html>
|
61
|
+
HTML
|
62
|
+
],
|
63
|
+
]
|
64
|
+
end
|
65
|
+
|
66
|
+
def sanitizer
|
67
|
+
@sanitizer ||= Sanitizer.new
|
68
|
+
end
|
69
|
+
|
70
|
+
def viewer(_env, path)
|
71
|
+
raise NotImplementedError
|
72
|
+
end
|
73
|
+
|
74
|
+
def index(_env)
|
75
|
+
render(
|
76
|
+
String.new.tap do |content|
|
77
|
+
content << "<h1>Profiles</h1>"
|
78
|
+
profile_files.each do |file|
|
79
|
+
content << <<~HTML
|
80
|
+
<p>
|
81
|
+
<a href="/app_profiler/#{id(file)}">
|
82
|
+
#{id(file)}
|
83
|
+
</a>
|
84
|
+
</p>
|
85
|
+
HTML
|
86
|
+
end
|
87
|
+
end
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
def show(env, id)
|
92
|
+
raise NotImplementedError
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private_constant(:BaseMiddleware)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "app_profiler/yarn/command"
|
4
|
+
require "app_profiler/yarn/with_speedscope"
|
5
|
+
|
6
|
+
module AppProfiler
|
7
|
+
module Viewer
|
8
|
+
class SpeedscopeRemoteViewer < BaseViewer
|
9
|
+
class Middleware < BaseMiddleware
|
10
|
+
include Yarn::WithSpeedscope
|
11
|
+
|
12
|
+
def initialize(app)
|
13
|
+
super
|
14
|
+
@speedscope = Rack::File.new(
|
15
|
+
File.join(AppProfiler.root, "node_modules/speedscope/dist/release")
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
attr_reader(:speedscope)
|
22
|
+
|
23
|
+
def viewer(env, path)
|
24
|
+
setup_yarn unless yarn_setup
|
25
|
+
env[Rack::PATH_INFO] = path.delete_prefix("/app_profiler")
|
26
|
+
|
27
|
+
speedscope.call(env)
|
28
|
+
end
|
29
|
+
|
30
|
+
def show(_env, name)
|
31
|
+
profile = profile_files.find do |file|
|
32
|
+
id(file) == name
|
33
|
+
end || raise(ArgumentError)
|
34
|
+
|
35
|
+
render(
|
36
|
+
<<~HTML
|
37
|
+
<script type="text/javascript">
|
38
|
+
var graph = #{profile.read};
|
39
|
+
var json = JSON.stringify(graph);
|
40
|
+
var blob = new Blob([json], { type: 'text/plain' });
|
41
|
+
var objUrl = encodeURIComponent(URL.createObjectURL(blob));
|
42
|
+
var iframe = document.createElement('iframe');
|
43
|
+
|
44
|
+
document.body.style.margin = '0px';
|
45
|
+
document.body.appendChild(iframe);
|
46
|
+
|
47
|
+
iframe.style.width = '100vw';
|
48
|
+
iframe.style.height = '100vh';
|
49
|
+
iframe.style.border = 'none';
|
50
|
+
iframe.setAttribute('src', '/app_profiler/viewer/index.html#profileURL=' + objUrl + '&title=' + 'Flamegraph for #{name}');
|
51
|
+
</script>
|
52
|
+
HTML
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "app_profiler/viewer/speedscope_remote_viewer/base_middleware"
|
4
|
+
require "app_profiler/viewer/speedscope_remote_viewer/middleware"
|
5
|
+
|
6
|
+
module AppProfiler
|
7
|
+
module Viewer
|
8
|
+
class SpeedscopeRemoteViewer < BaseViewer
|
9
|
+
class << self
|
10
|
+
def view(profile)
|
11
|
+
new(profile).view
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(profile)
|
16
|
+
super()
|
17
|
+
@profile = profile
|
18
|
+
end
|
19
|
+
|
20
|
+
def view
|
21
|
+
id = Middleware.id(@profile.file)
|
22
|
+
AppProfiler.logger.info("[Profiler] Profile available at /app_profiler/#{id}\n")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,11 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "app_profiler/yarn/command"
|
4
|
+
require "app_profiler/yarn/with_speedscope"
|
5
|
+
|
3
6
|
module AppProfiler
|
4
7
|
module Viewer
|
5
8
|
class SpeedscopeViewer < BaseViewer
|
6
|
-
|
7
|
-
|
8
|
-
class YarnError < StandardError; end
|
9
|
+
include Yarn::WithSpeedscope
|
9
10
|
|
10
11
|
class << self
|
11
12
|
def view(profile)
|
@@ -19,52 +20,7 @@ module AppProfiler
|
|
19
20
|
end
|
20
21
|
|
21
22
|
def view
|
22
|
-
yarn("run speedscope
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def yarn(command)
|
28
|
-
setup_yarn unless yarn_setup
|
29
|
-
exec("yarn #{command}") do
|
30
|
-
raise YarnError, "Failed to run #{command}."
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def setup_yarn
|
35
|
-
ensure_yarn_installed
|
36
|
-
yarn("init --yes") unless package_json_exists?
|
37
|
-
# We currently only support this gem in the root Gemfile.
|
38
|
-
# See https://github.com/Shopify/app_profiler/issues/15
|
39
|
-
# for more information
|
40
|
-
yarn("add --dev --ignore-workspace-root-check speedscope") unless speedscope_added?
|
41
|
-
end
|
42
|
-
|
43
|
-
def ensure_yarn_installed
|
44
|
-
exec("which yarn > /dev/null") do
|
45
|
-
raise(
|
46
|
-
YarnError,
|
47
|
-
<<~MSG.squish
|
48
|
-
`yarn` command not found.
|
49
|
-
Please install `yarn` or make it available in PATH.
|
50
|
-
MSG
|
51
|
-
)
|
52
|
-
end
|
53
|
-
self.yarn_setup = true
|
54
|
-
end
|
55
|
-
|
56
|
-
def package_json_exists?
|
57
|
-
AppProfiler.root.join("package.json").exist?
|
58
|
-
end
|
59
|
-
|
60
|
-
def speedscope_added?
|
61
|
-
AppProfiler.root.join("node_modules/speedscope").exist?
|
62
|
-
end
|
63
|
-
|
64
|
-
def exec(command)
|
65
|
-
system(command).tap do |return_code|
|
66
|
-
yield unless return_code
|
67
|
-
end
|
23
|
+
yarn("run", "speedscope", @profile.file.to_s)
|
68
24
|
end
|
69
25
|
end
|
70
26
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module AppProfiler
|
3
|
+
module Yarn
|
4
|
+
module Command
|
5
|
+
class YarnError < StandardError; end
|
6
|
+
|
7
|
+
VALID_COMMANDS = [
|
8
|
+
["which", "yarn"],
|
9
|
+
["yarn", "init", "--yes"],
|
10
|
+
["yarn", "add", "speedscope", "--dev", "--ignore-workspace-root-check"],
|
11
|
+
["yarn", "run", "speedscope", /.*\.json/],
|
12
|
+
]
|
13
|
+
|
14
|
+
private_constant(:VALID_COMMANDS)
|
15
|
+
mattr_accessor(:yarn_setup, default: false)
|
16
|
+
|
17
|
+
def yarn(command, *options)
|
18
|
+
setup_yarn unless yarn_setup
|
19
|
+
|
20
|
+
exec("yarn", command, *options) do
|
21
|
+
raise YarnError, "Failed to run #{command}."
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def setup_yarn
|
26
|
+
ensure_yarn_installed
|
27
|
+
|
28
|
+
yarn("init", "--yes") unless package_json_exists?
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def ensure_command_valid(command)
|
34
|
+
unless valid_command?(command)
|
35
|
+
raise YarnError, "Illegal command: #{command.join(' ')}."
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def valid_command?(command)
|
40
|
+
VALID_COMMANDS.any? do |valid_command|
|
41
|
+
valid_command.zip(command).all? do |valid_part, part|
|
42
|
+
part.match?(valid_part)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def ensure_yarn_installed
|
48
|
+
exec("which", "yarn", silent: true) do
|
49
|
+
raise(
|
50
|
+
YarnError,
|
51
|
+
<<~MSG.squish
|
52
|
+
`yarn` command not found.
|
53
|
+
Please install `yarn` or make it available in PATH.
|
54
|
+
MSG
|
55
|
+
)
|
56
|
+
end
|
57
|
+
self.yarn_setup = true
|
58
|
+
end
|
59
|
+
|
60
|
+
def package_json_exists?
|
61
|
+
AppProfiler.root.join("package.json").exist?
|
62
|
+
end
|
63
|
+
|
64
|
+
def exec(*command, silent: false)
|
65
|
+
ensure_command_valid(command)
|
66
|
+
|
67
|
+
if silent
|
68
|
+
system(*command, out: File::NULL).tap { |return_code| yield unless return_code }
|
69
|
+
else
|
70
|
+
system(*command).tap { |return_code| yield unless return_code }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module AppProfiler
|
3
|
+
module Yarn
|
4
|
+
module WithSpeedscope
|
5
|
+
include Command
|
6
|
+
|
7
|
+
def setup_yarn
|
8
|
+
super
|
9
|
+
# We currently only support this gem in the root Gemfile.
|
10
|
+
# See https://github.com/Shopify/app_profiler/issues/15
|
11
|
+
# for more information
|
12
|
+
yarn("add", "speedscope", "--dev", "--ignore-workspace-root-check") unless speedscope_added?
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def speedscope_added?
|
18
|
+
AppProfiler.root.join("node_modules/speedscope").exist?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/app_profiler.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support"
|
3
|
+
require "active_support/core_ext/class"
|
4
|
+
require "active_support/core_ext/module"
|
5
|
+
require "logger"
|
4
6
|
require "app_profiler/version"
|
5
7
|
require "app_profiler/railtie" if defined?(Rails::Railtie)
|
6
8
|
|
@@ -17,12 +19,13 @@ module AppProfiler
|
|
17
19
|
module Viewer
|
18
20
|
autoload :BaseViewer, "app_profiler/viewer/base_viewer"
|
19
21
|
autoload :SpeedscopeViewer, "app_profiler/viewer/speedscope_viewer"
|
22
|
+
autoload :SpeedscopeRemoteViewer, "app_profiler/viewer/speedscope_remote_viewer"
|
20
23
|
end
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
25
|
+
require "app_profiler/middleware"
|
26
|
+
require "app_profiler/request_parameters"
|
27
|
+
require "app_profiler/profiler"
|
28
|
+
require "app_profiler/profile"
|
26
29
|
|
27
30
|
mattr_accessor :logger, default: Logger.new($stdout)
|
28
31
|
mattr_accessor :root
|
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.0.
|
4
|
+
version: 0.0.9
|
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: 2022-03-02 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: activesupport
|
@@ -162,7 +162,12 @@ files:
|
|
162
162
|
- lib/app_profiler/storage/google_cloud_storage.rb
|
163
163
|
- lib/app_profiler/version.rb
|
164
164
|
- lib/app_profiler/viewer/base_viewer.rb
|
165
|
+
- lib/app_profiler/viewer/speedscope_remote_viewer.rb
|
166
|
+
- lib/app_profiler/viewer/speedscope_remote_viewer/base_middleware.rb
|
167
|
+
- lib/app_profiler/viewer/speedscope_remote_viewer/middleware.rb
|
165
168
|
- lib/app_profiler/viewer/speedscope_viewer.rb
|
169
|
+
- lib/app_profiler/yarn/command.rb
|
170
|
+
- lib/app_profiler/yarn/with_speedscope.rb
|
166
171
|
homepage: https://github.com/Shopify/app_profiler
|
167
172
|
licenses: []
|
168
173
|
metadata:
|
@@ -182,7 +187,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
182
187
|
- !ruby/object:Gem::Version
|
183
188
|
version: '0'
|
184
189
|
requirements: []
|
185
|
-
rubygems_version: 3.2.
|
190
|
+
rubygems_version: 3.2.20
|
186
191
|
signing_key:
|
187
192
|
specification_version: 4
|
188
193
|
summary: Collect performance profiles for your Rails application.
|