app_profiler 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
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.