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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4342f9415d422d77b172d358f968f9bc6c0ae670364e2ed351c7d1daf7cfcd3f
4
- data.tar.gz: 4868b701483cc23fef7dad0432437afe61a65ff4ff5bcd752eec37636f4eeddb
3
+ metadata.gz: 160040c64c01368d5a6832e6fccc20ed14eecdd7ff0a4a9a18e4f7aeee62c83b
4
+ data.tar.gz: 76107cd56b460c68e33ef32677a3d822e83319b3cb9084e39e30841ee8bb8db7
5
5
  SHA512:
6
- metadata.gz: ab3d7f1677cc9f3a645ca23cf5fc2b3125cd498cbaace7640e31aad8b45e8f9c23bda135bf470b105e723cafeb1c0456149618c3268cc9ea5674fe2427b00fe0
7
- data.tar.gz: 12189f7de4e869d34939d9c3cdbeeb94d257de0f31c232f2deff2011a879fadc8aaccfde44f9780eec6bd863375f9a098f3c571ed53bc65c9e9825f83be6d96a
6
+ metadata.gz: 2c926b73ab9abe0773f5f7d2415672a89f444c2c3448387f57e07fe69b359c6a70175beaaba91171bda2f24702dae7073b76f094ef9d5de8fcc7ca360c3cc0a4
7
+ data.tar.gz: eb3af95b1164676ec400acd49732532e74c91565cfa70db31db7fbbcb00b188d6e68c5e35ae02ef526bf55694438e48a1fe97ece448a7de649cd0198871fd9de
@@ -42,7 +42,7 @@ module AppProfiler
42
42
  end
43
43
 
44
44
  def profile_data_url(upload)
45
- upload.url
45
+ upload.url.to_s
46
46
  end
47
47
 
48
48
  def profile_header
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AppProfiler
4
- VERSION = "0.0.8"
4
+ VERSION = "0.0.9"
5
5
  end
@@ -3,7 +3,13 @@
3
3
  module AppProfiler
4
4
  module Viewer
5
5
  class BaseViewer
6
- def self.view(_profile)
6
+ class << self
7
+ def view(profile)
8
+ new(profile).view
9
+ end
10
+ end
11
+
12
+ def view(_profile)
7
13
  raise NotImplementedError
8
14
  end
9
15
  end
@@ -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
- mattr_accessor :yarn_setup, default: false
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 \"#{@profile.file}\"")
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
- autoload :Middleware, "app_profiler/middleware"
23
- autoload :RequestParameters, "app_profiler/request_parameters"
24
- autoload :Profiler, "app_profiler/profiler"
25
- autoload :Profile, "app_profiler/profile"
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.8
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: 2021-06-09 00:00:00.000000000 Z
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.17
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.