app_profiler 0.1.0 → 0.1.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/middleware/upload_action.rb +2 -10
- data/lib/app_profiler/profile.rb +9 -3
- data/lib/app_profiler/railtie.rb +25 -0
- data/lib/app_profiler/request_parameters.rb +1 -0
- data/lib/app_profiler/server.rb +307 -0
- data/lib/app_profiler/version.rb +1 -1
- data/lib/app_profiler/viewer/speedscope_remote_viewer/base_middleware.rb +7 -5
- data/lib/app_profiler/yarn/command.rb +2 -1
- data/lib/app_profiler/yarn/with_speedscope.rb +1 -0
- data/lib/app_profiler.rb +13 -4
- metadata +22 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 340bc83668cbe8abd27780ef23456a6d98f1f8c46059f2b447a25f1e6784483e
|
4
|
+
data.tar.gz: 06fcbd4abe95e53f8e37595df7ca445bcd59f61cd9ba24af8c82578cdb6bb9c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04cf486ef95efd05af19fd241f05b45c0a942c1e701295181fdef898f59cb24ace85c86e047ea63359b5c5ddfb2f8a233afbc3958004a823e0ffcf0daf692830
|
7
|
+
data.tar.gz: bab5ddd1f1dcfb4d06e23b575341a7d7a783e04191b62a74dd553cf78d5726187500931e34c49ebe5e019d60602ceec074d4071d024008c05c8bfe5e5ddb7d52
|
@@ -21,26 +21,18 @@ module AppProfiler
|
|
21
21
|
def append_headers(response, upload:, autoredirect:)
|
22
22
|
return unless upload
|
23
23
|
|
24
|
-
response[1][profile_header] = profile_url(upload)
|
24
|
+
response[1][profile_header] = AppProfiler.profile_url(upload)
|
25
25
|
response[1][profile_data_header] = profile_data_url(upload)
|
26
26
|
|
27
27
|
return unless autoredirect
|
28
28
|
|
29
29
|
# Automatically redirect to profile if autoredirect is true.
|
30
30
|
if response[0].to_i < 500
|
31
|
-
response[1]["Location"] = profile_url(upload)
|
31
|
+
response[1]["Location"] = AppProfiler.profile_url(upload)
|
32
32
|
response[0] = 303
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
def profile_url(upload)
|
37
|
-
if AppProfiler.profile_url_formatter.nil?
|
38
|
-
"#{AppProfiler.speedscope_host}#profileURL=#{upload.url}"
|
39
|
-
else
|
40
|
-
AppProfiler.profile_url_formatter.call(upload)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
36
|
def profile_data_url(upload)
|
45
37
|
upload.url.to_s
|
46
38
|
end
|
data/lib/app_profiler/profile.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module AppProfiler
|
4
4
|
class Profile
|
5
|
-
INTERNAL_METADATA_KEYS =
|
5
|
+
INTERNAL_METADATA_KEYS = [:id, :context]
|
6
6
|
private_constant :INTERNAL_METADATA_KEYS
|
7
7
|
class UnsafeFilename < StandardError; end
|
8
8
|
|
@@ -40,7 +40,13 @@ module AppProfiler
|
|
40
40
|
def upload
|
41
41
|
AppProfiler.storage.upload(self).tap do |upload|
|
42
42
|
if upload && defined?(upload.url)
|
43
|
-
AppProfiler.logger.info(
|
43
|
+
AppProfiler.logger.info(
|
44
|
+
<<~INFO.squish
|
45
|
+
[Profiler] data uploaded:
|
46
|
+
profile_url=#{upload.url}
|
47
|
+
profile_viewer_url=#{AppProfiler.profile_url(upload)}
|
48
|
+
INFO
|
49
|
+
)
|
44
50
|
end
|
45
51
|
end
|
46
52
|
rescue => error
|
@@ -71,7 +77,7 @@ module AppProfiler
|
|
71
77
|
Socket.gethostname,
|
72
78
|
].compact.join("-") << ".json"
|
73
79
|
|
74
|
-
raise UnsafeFilename if /[^0-9A-Za-z.\-\_]/.match(filename)
|
80
|
+
raise UnsafeFilename if /[^0-9A-Za-z.\-\_]/.match?(filename)
|
75
81
|
|
76
82
|
AppProfiler.profile_root.join(filename)
|
77
83
|
end
|
data/lib/app_profiler/railtie.rb
CHANGED
@@ -16,6 +16,12 @@ module AppProfiler
|
|
16
16
|
AppProfiler.middleware = app.config.app_profiler.middleware || Middleware
|
17
17
|
AppProfiler.middleware.action = app.config.app_profiler.middleware_action || default_middleware_action
|
18
18
|
AppProfiler.middleware.disabled = app.config.app_profiler.middleware_disabled || false
|
19
|
+
AppProfiler.server.enabled = app.config.app_profiler.server_enabled || false
|
20
|
+
AppProfiler.server.transport = app.config.app_profiler.server_transport || default_appprofiler_transport
|
21
|
+
AppProfiler.server.port = app.config.app_profiler.server_port || 0
|
22
|
+
AppProfiler.server.duration = app.config.app_profiler.server_duration || 30
|
23
|
+
AppProfiler.server.cors = app.config.app_profiler.server_cors || true
|
24
|
+
AppProfiler.server.cors_host = app.config.app_profiler.server_cors_host || "*"
|
19
25
|
AppProfiler.autoredirect = app.config.app_profiler.autoredirect || false
|
20
26
|
AppProfiler.speedscope_host = app.config.app_profiler.speedscope_host || ENV.fetch(
|
21
27
|
"APP_PROFILER_SPEEDSCOPE_URL", "https://speedscope.app"
|
@@ -37,6 +43,15 @@ module AppProfiler
|
|
37
43
|
end
|
38
44
|
end
|
39
45
|
|
46
|
+
initializer "app_profiler.enable_server" do
|
47
|
+
if AppProfiler.server.enabled
|
48
|
+
AppProfiler::Server.start
|
49
|
+
ActiveSupport::ForkTracker.after_fork do
|
50
|
+
AppProfiler::Server.start
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
40
55
|
private
|
41
56
|
|
42
57
|
def default_middleware_action
|
@@ -46,5 +61,15 @@ module AppProfiler
|
|
46
61
|
Middleware::UploadAction
|
47
62
|
end
|
48
63
|
end
|
64
|
+
|
65
|
+
def default_appprofiler_transport
|
66
|
+
if Rails.env.development?
|
67
|
+
# default to TCP server in development so that if wanted users are able to target
|
68
|
+
# the server with speedscope
|
69
|
+
AppProfiler::Server::TRANSPORT_TCP
|
70
|
+
else
|
71
|
+
AppProfiler::Server::TRANSPORT_UNIX
|
72
|
+
end
|
73
|
+
end
|
49
74
|
end
|
50
75
|
end
|
@@ -0,0 +1,307 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "socket"
|
4
|
+
require "rack"
|
5
|
+
require "tempfile"
|
6
|
+
|
7
|
+
# This module provides a means to start a golang-inspired profile server
|
8
|
+
# it is implemented using stdlib and Rack to avoid additional dependencies
|
9
|
+
|
10
|
+
module AppProfiler
|
11
|
+
module Server
|
12
|
+
HTTP_OK = 200
|
13
|
+
HTTP_BAD_REQUEST = 400
|
14
|
+
HTTP_NOT_FOUND = 404
|
15
|
+
HTTP_NOT_ALLOWED = 405
|
16
|
+
HTTP_CONFLICT = 409
|
17
|
+
|
18
|
+
TRANSPORT_UNIX = "unix"
|
19
|
+
TRANSPORT_TCP = "tcp"
|
20
|
+
|
21
|
+
DEFAULTS = {
|
22
|
+
enabled: false,
|
23
|
+
transport: TRANSPORT_UNIX,
|
24
|
+
cors: true,
|
25
|
+
cors_host: "*",
|
26
|
+
port: 0,
|
27
|
+
duration: 30,
|
28
|
+
}
|
29
|
+
|
30
|
+
mattr_accessor :enabled, default: DEFAULTS[:enabled]
|
31
|
+
mattr_accessor :transport, default: DEFAULTS[:transport]
|
32
|
+
mattr_accessor :cors, default: DEFAULTS[:cors]
|
33
|
+
mattr_accessor :cors_host, default: DEFAULTS[:cors_host]
|
34
|
+
mattr_accessor :port, default: DEFAULTS[:port]
|
35
|
+
mattr_accessor :duration, default: DEFAULTS[:duration]
|
36
|
+
|
37
|
+
class ProfileApplication
|
38
|
+
class InvalidProfileArgsError < StandardError; end
|
39
|
+
|
40
|
+
def initialize
|
41
|
+
@semaphore = Thread::Mutex.new
|
42
|
+
@profile_running = false
|
43
|
+
end
|
44
|
+
|
45
|
+
def call(env)
|
46
|
+
handle(Rack::Request.new(env)).finish
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def handle(request)
|
52
|
+
response = Rack::Response.new
|
53
|
+
if request.request_method != "GET"
|
54
|
+
response.status = HTTP_NOT_ALLOWED
|
55
|
+
response.write("Only GET requests are supported")
|
56
|
+
return response
|
57
|
+
end
|
58
|
+
case request.path
|
59
|
+
when "/profile"
|
60
|
+
begin
|
61
|
+
stackprof_args, duration = validate_profile_params(request.params)
|
62
|
+
rescue InvalidProfileArgsError => e
|
63
|
+
response.status = HTTP_BAD_REQUEST
|
64
|
+
response.write("Invalid argument #{e.message}")
|
65
|
+
return response
|
66
|
+
end
|
67
|
+
|
68
|
+
if start_running
|
69
|
+
start_time = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
|
70
|
+
AppProfiler.start(**stackprof_args)
|
71
|
+
sleep(duration)
|
72
|
+
profile = AppProfiler.stop
|
73
|
+
stop_running
|
74
|
+
response.status = HTTP_OK
|
75
|
+
response.set_header("Content-Type", "application/json")
|
76
|
+
if AppProfiler::Server.cors
|
77
|
+
response.set_header("Access-Control-Allow-Origin", AppProfiler::Server.cors_host)
|
78
|
+
end
|
79
|
+
profile_hash = profile.to_h
|
80
|
+
profile_hash["start_time_nsecs"] = start_time # NOTE: this is not part of the stackprof profile spec
|
81
|
+
response.write(JSON.dump(profile_hash))
|
82
|
+
else
|
83
|
+
response.status = HTTP_CONFLICT
|
84
|
+
response.write("A profile is already running")
|
85
|
+
end
|
86
|
+
else
|
87
|
+
response.status = HTTP_NOT_FOUND
|
88
|
+
response.write("Unsupported endpoint #{request.path}")
|
89
|
+
end
|
90
|
+
response
|
91
|
+
end
|
92
|
+
|
93
|
+
def validate_profile_params(params)
|
94
|
+
params = params.symbolize_keys
|
95
|
+
stackprof_args = {}
|
96
|
+
begin
|
97
|
+
duration = Float(params.key?(:duration) ? params[:duration] : AppProfiler::Server.duration)
|
98
|
+
rescue ArgumentError
|
99
|
+
raise InvalidProfileArgsError, "invalid duration #{params[:duration]}"
|
100
|
+
end
|
101
|
+
if params.key?(:mode)
|
102
|
+
if ["cpu", "wall", "object"].include?(params[:mode])
|
103
|
+
stackprof_args[:mode] = params[:mode].to_sym
|
104
|
+
else
|
105
|
+
raise InvalidProfileArgsError, "invalid mode #{params[:mode]}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
if params.key?(:interval)
|
109
|
+
stackprof_args[:interval] = params[:interval].to_i
|
110
|
+
raise InvalidProfileArgsError, "invalid interval #{params[:interval]}" if stackprof_args[:interval] <= 0
|
111
|
+
end
|
112
|
+
[stackprof_args, duration]
|
113
|
+
end
|
114
|
+
|
115
|
+
# Prevent multiple concurrent profiles by synchronizing between threads
|
116
|
+
def start_running
|
117
|
+
@semaphore.synchronize do
|
118
|
+
return false if @profile_running
|
119
|
+
|
120
|
+
@profile_running = true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def stop_running
|
125
|
+
@semaphore.synchronize { @profile_running = false }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# This is a minimal, non-compliant "HTTP" server.
|
130
|
+
# It will create an extremely minimal rack environment hash and hand it off
|
131
|
+
# to our application to process
|
132
|
+
class ProfileServer
|
133
|
+
PROFILER_TEMPFILE_PATH = "/tmp/app_profiler" # for tempfile that indicates port in filename or unix sockets
|
134
|
+
|
135
|
+
class Transport
|
136
|
+
attr_reader :socket
|
137
|
+
|
138
|
+
def client
|
139
|
+
raise(NotImplementedError)
|
140
|
+
end
|
141
|
+
|
142
|
+
def stop
|
143
|
+
raise(NotImplementedError)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class UNIX < Transport
|
148
|
+
def initialize
|
149
|
+
super
|
150
|
+
FileUtils.mkdir_p(PROFILER_TEMPFILE_PATH)
|
151
|
+
@socket_file = File.join(PROFILER_TEMPFILE_PATH, "app-profiler-#{Process.pid}.sock")
|
152
|
+
File.unlink(@socket_file) if File.exist?(@socket_file) && File.socket?(@socket_file)
|
153
|
+
@socket = UNIXServer.new(@socket_file)
|
154
|
+
end
|
155
|
+
|
156
|
+
def client
|
157
|
+
UNIXSocket.new(@socket_file)
|
158
|
+
end
|
159
|
+
|
160
|
+
def stop
|
161
|
+
File.unlink(@socket_file) if File.exist?(@socket_file) && File.socket?(@socket_file)
|
162
|
+
@socket.close
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class TCP < Transport
|
167
|
+
SERVER_ADDRESS = "127.0.0.1" # it is ONLY safe to run this bound to localhost
|
168
|
+
|
169
|
+
def initialize(port = 0)
|
170
|
+
super()
|
171
|
+
FileUtils.mkdir_p(PROFILER_TEMPFILE_PATH)
|
172
|
+
@socket = TCPServer.new(SERVER_ADDRESS, port)
|
173
|
+
@port = @socket.addr[1]
|
174
|
+
@port_file = Tempfile.new("profileserver-#{Process.pid}-port-#{@port}-", PROFILER_TEMPFILE_PATH)
|
175
|
+
end
|
176
|
+
|
177
|
+
def client
|
178
|
+
TCPSocket.new(SERVER_ADDRESS, @port)
|
179
|
+
end
|
180
|
+
|
181
|
+
def stop
|
182
|
+
@port_file.unlink
|
183
|
+
@socket.close
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def initialize(transport)
|
188
|
+
case transport
|
189
|
+
when TRANSPORT_UNIX
|
190
|
+
@transport = ProfileServer::UNIX.new
|
191
|
+
when TRANSPORT_TCP
|
192
|
+
@transport = ProfileServer::TCP.new(AppProfiler::Server.port)
|
193
|
+
else
|
194
|
+
raise "invalid transport #{transport}"
|
195
|
+
end
|
196
|
+
|
197
|
+
@listen_thread = nil
|
198
|
+
|
199
|
+
AppProfiler.logger.info(
|
200
|
+
"[AppProfiler::Server] listening on addr=#{@transport.socket.addr}"
|
201
|
+
)
|
202
|
+
@pid = Process.pid
|
203
|
+
at_exit { stop }
|
204
|
+
end
|
205
|
+
|
206
|
+
def client
|
207
|
+
@transport.client
|
208
|
+
end
|
209
|
+
|
210
|
+
def serve
|
211
|
+
return unless @listen_thread.nil?
|
212
|
+
|
213
|
+
app = ProfileApplication.new
|
214
|
+
|
215
|
+
@listen_thread = Thread.new do
|
216
|
+
loop do
|
217
|
+
Thread.new(@transport.socket.accept) do |session|
|
218
|
+
request = session.gets
|
219
|
+
if request.nil?
|
220
|
+
session.close
|
221
|
+
next
|
222
|
+
end
|
223
|
+
method, path, http_version = request.split(" ")
|
224
|
+
path_info, query_string = path.split("?")
|
225
|
+
env = { # an extremely minimal rack env hash, just enough to get the job done
|
226
|
+
"HTTP_VERSION" => http_version,
|
227
|
+
"REQUEST_METHOD" => method,
|
228
|
+
"PATH_INFO" => path_info,
|
229
|
+
"QUERY_STRING" => query_string,
|
230
|
+
"rack.input" => "",
|
231
|
+
}
|
232
|
+
status, headers, body = app.call(env)
|
233
|
+
|
234
|
+
begin
|
235
|
+
session.print("#{http_version} #{status}\r\n")
|
236
|
+
headers.each do |header, value|
|
237
|
+
session.print("#{header}: #{value}\r\n")
|
238
|
+
end
|
239
|
+
session.print("\r\n")
|
240
|
+
body.each do |part|
|
241
|
+
session.print(part)
|
242
|
+
end
|
243
|
+
rescue => e
|
244
|
+
AppProfiler.logger.error(
|
245
|
+
"[AppProfiler::Server] exception #{e} responding to request #{request}: #{e.message}"
|
246
|
+
)
|
247
|
+
ensure
|
248
|
+
session.close
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def stop
|
256
|
+
return unless @pid == Process.pid
|
257
|
+
|
258
|
+
@listen_thread.kill
|
259
|
+
@transport.stop
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
private_constant :ProfileApplication, :ProfileServer
|
264
|
+
|
265
|
+
class << self
|
266
|
+
def reset
|
267
|
+
profile_servers.clear
|
268
|
+
DEFAULTS.each do |config, value|
|
269
|
+
class_variable_set(:"@@#{config}", value) # rubocop:disable Style/ClassVars
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def start
|
274
|
+
return if profile_server
|
275
|
+
|
276
|
+
profile_servers[Process.pid] = ProfileServer.new(AppProfiler::Server.transport)
|
277
|
+
profile_server.serve
|
278
|
+
profile_server
|
279
|
+
end
|
280
|
+
|
281
|
+
def client
|
282
|
+
return unless profile_server
|
283
|
+
|
284
|
+
profile_server.client
|
285
|
+
end
|
286
|
+
|
287
|
+
def stop
|
288
|
+
return unless profile_server
|
289
|
+
|
290
|
+
profile_server.stop
|
291
|
+
profile_servers.delete(Process.pid)
|
292
|
+
end
|
293
|
+
|
294
|
+
private
|
295
|
+
|
296
|
+
def profile_server
|
297
|
+
profile_servers[Process.pid]
|
298
|
+
end
|
299
|
+
|
300
|
+
# It is possible there will be a server pre-fork, this is to distinguish
|
301
|
+
# the child server from its parent via PID
|
302
|
+
def profile_servers
|
303
|
+
@profile_servers ||= {}
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
data/lib/app_profiler/version.rb
CHANGED
@@ -7,10 +7,12 @@ module AppProfiler
|
|
7
7
|
class SpeedscopeRemoteViewer < BaseViewer
|
8
8
|
class BaseMiddleware
|
9
9
|
class Sanitizer < Rails::Html::SafeListSanitizer
|
10
|
-
self.allowed_tags = Set.new(
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
self.allowed_tags = Set.new([
|
11
|
+
"strong", "em", "b", "i", "p", "code", "pre", "tt", "samp", "kbd", "var", "sub",
|
12
|
+
"sup", "dfn", "cite", "big", "small", "address", "hr", "br", "div", "span", "h1",
|
13
|
+
"h2", "h3", "h4", "h5", "h6", "ul", "ol", "li", "dl", "dt", "dd", "abbr", "acronym",
|
14
|
+
"a", "img", "blockquote", "del", "ins", "script",
|
15
|
+
])
|
14
16
|
end
|
15
17
|
|
16
18
|
private_constant(:Sanitizer)
|
@@ -73,7 +75,7 @@ module AppProfiler
|
|
73
75
|
|
74
76
|
def index(_env)
|
75
77
|
render(
|
76
|
-
|
78
|
+
(+"").tap do |content|
|
77
79
|
content << "<h1>Profiles</h1>"
|
78
80
|
profile_files.each do |file|
|
79
81
|
content << <<~HTML
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module AppProfiler
|
3
4
|
module Yarn
|
4
5
|
module Command
|
@@ -32,7 +33,7 @@ module AppProfiler
|
|
32
33
|
|
33
34
|
def ensure_command_valid(command)
|
34
35
|
unless valid_command?(command)
|
35
|
-
raise YarnError, "Illegal command: #{command.join(
|
36
|
+
raise YarnError, "Illegal command: #{command.join(" ")}."
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
data/lib/app_profiler.rb
CHANGED
@@ -10,6 +10,10 @@ module AppProfiler
|
|
10
10
|
class ConfigurationError < StandardError
|
11
11
|
end
|
12
12
|
|
13
|
+
DefaultProfileFormatter = proc do |upload|
|
14
|
+
"#{AppProfiler.speedscope_host}#profileURL=#{upload.url}"
|
15
|
+
end
|
16
|
+
|
13
17
|
module Storage
|
14
18
|
autoload :BaseStorage, "app_profiler/storage/base_storage"
|
15
19
|
autoload :FileStorage, "app_profiler/storage/file_storage"
|
@@ -26,6 +30,7 @@ module AppProfiler
|
|
26
30
|
require "app_profiler/request_parameters"
|
27
31
|
require "app_profiler/profiler"
|
28
32
|
require "app_profiler/profile"
|
33
|
+
require "app_profiler/server"
|
29
34
|
|
30
35
|
mattr_accessor :logger, default: Logger.new($stdout)
|
31
36
|
mattr_accessor :root
|
@@ -35,11 +40,13 @@ module AppProfiler
|
|
35
40
|
mattr_accessor :autoredirect, default: false
|
36
41
|
mattr_reader :profile_header, default: "X-Profile"
|
37
42
|
mattr_accessor :context, default: nil
|
38
|
-
mattr_reader :profile_url_formatter,
|
43
|
+
mattr_reader :profile_url_formatter,
|
44
|
+
default: DefaultProfileFormatter
|
39
45
|
|
40
46
|
mattr_accessor :storage, default: Storage::FileStorage
|
41
47
|
mattr_accessor :viewer, default: Viewer::SpeedscopeViewer
|
42
48
|
mattr_accessor :middleware, default: Middleware
|
49
|
+
mattr_accessor :server, default: Server
|
43
50
|
|
44
51
|
class << self
|
45
52
|
def run(*args, &block)
|
@@ -62,9 +69,7 @@ module AppProfiler
|
|
62
69
|
end
|
63
70
|
|
64
71
|
def request_profile_header
|
65
|
-
@@request_profile_header ||=
|
66
|
-
profile_header.upcase.gsub("-", "_").prepend("HTTP_")
|
67
|
-
end
|
72
|
+
@@request_profile_header ||= profile_header.upcase.tr("-", "_").prepend("HTTP_") # rubocop:disable Style/ClassVars
|
68
73
|
end
|
69
74
|
|
70
75
|
def profile_data_header
|
@@ -74,5 +79,9 @@ module AppProfiler
|
|
74
79
|
def profile_url_formatter=(block)
|
75
80
|
@@profile_url_formatter = block # rubocop:disable Style/ClassVars
|
76
81
|
end
|
82
|
+
|
83
|
+
def profile_url(upload)
|
84
|
+
AppProfiler.profile_url_formatter.call(upload)
|
85
|
+
end
|
77
86
|
end
|
78
87
|
end
|
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.1.1
|
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: 2022-
|
16
|
+
date: 2022-06-16 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: activesupport
|
@@ -30,33 +30,33 @@ dependencies:
|
|
30
30
|
- !ruby/object:Gem::Version
|
31
31
|
version: '5.2'
|
32
32
|
- !ruby/object:Gem::Dependency
|
33
|
-
name:
|
33
|
+
name: rack
|
34
34
|
requirement: !ruby/object:Gem::Requirement
|
35
35
|
requirements:
|
36
36
|
- - ">="
|
37
37
|
- !ruby/object:Gem::Version
|
38
|
-
version: '
|
38
|
+
version: '0'
|
39
39
|
type: :runtime
|
40
40
|
prerelease: false
|
41
41
|
version_requirements: !ruby/object:Gem::Requirement
|
42
42
|
requirements:
|
43
43
|
- - ">="
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
version: '
|
45
|
+
version: '0'
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
|
-
name:
|
47
|
+
name: railties
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
49
49
|
requirements:
|
50
50
|
- - ">="
|
51
51
|
- !ruby/object:Gem::Version
|
52
|
-
version: '
|
52
|
+
version: '5.2'
|
53
53
|
type: :runtime
|
54
54
|
prerelease: false
|
55
55
|
version_requirements: !ruby/object:Gem::Requirement
|
56
56
|
requirements:
|
57
57
|
- - ">="
|
58
58
|
- !ruby/object:Gem::Version
|
59
|
-
version: '
|
59
|
+
version: '5.2'
|
60
60
|
- !ruby/object:Gem::Dependency
|
61
61
|
name: stackprof
|
62
62
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,7 +86,7 @@ dependencies:
|
|
86
86
|
- !ruby/object:Gem::Version
|
87
87
|
version: '0'
|
88
88
|
- !ruby/object:Gem::Dependency
|
89
|
-
name:
|
89
|
+
name: minitest
|
90
90
|
requirement: !ruby/object:Gem::Requirement
|
91
91
|
requirements:
|
92
92
|
- - ">="
|
@@ -100,19 +100,19 @@ dependencies:
|
|
100
100
|
- !ruby/object:Gem::Version
|
101
101
|
version: '0'
|
102
102
|
- !ruby/object:Gem::Dependency
|
103
|
-
name: minitest
|
103
|
+
name: minitest-stub-const
|
104
104
|
requirement: !ruby/object:Gem::Requirement
|
105
105
|
requirements:
|
106
|
-
- -
|
106
|
+
- - '='
|
107
107
|
- !ruby/object:Gem::Version
|
108
|
-
version: '0'
|
108
|
+
version: '0.6'
|
109
109
|
type: :development
|
110
110
|
prerelease: false
|
111
111
|
version_requirements: !ruby/object:Gem::Requirement
|
112
112
|
requirements:
|
113
|
-
- -
|
113
|
+
- - '='
|
114
114
|
- !ruby/object:Gem::Version
|
115
|
-
version: '0'
|
115
|
+
version: '0.6'
|
116
116
|
- !ruby/object:Gem::Dependency
|
117
117
|
name: mocha
|
118
118
|
requirement: !ruby/object:Gem::Requirement
|
@@ -128,19 +128,19 @@ dependencies:
|
|
128
128
|
- !ruby/object:Gem::Version
|
129
129
|
version: '0'
|
130
130
|
- !ruby/object:Gem::Dependency
|
131
|
-
name:
|
131
|
+
name: rake
|
132
132
|
requirement: !ruby/object:Gem::Requirement
|
133
133
|
requirements:
|
134
|
-
- -
|
134
|
+
- - ">="
|
135
135
|
- !ruby/object:Gem::Version
|
136
|
-
version: '0
|
136
|
+
version: '0'
|
137
137
|
type: :development
|
138
138
|
prerelease: false
|
139
139
|
version_requirements: !ruby/object:Gem::Requirement
|
140
140
|
requirements:
|
141
|
-
- -
|
141
|
+
- - ">="
|
142
142
|
- !ruby/object:Gem::Version
|
143
|
-
version: '0
|
143
|
+
version: '0'
|
144
144
|
description:
|
145
145
|
email:
|
146
146
|
- gems@shopify.com
|
@@ -157,6 +157,7 @@ files:
|
|
157
157
|
- lib/app_profiler/profiler.rb
|
158
158
|
- lib/app_profiler/railtie.rb
|
159
159
|
- lib/app_profiler/request_parameters.rb
|
160
|
+
- lib/app_profiler/server.rb
|
160
161
|
- lib/app_profiler/storage/base_storage.rb
|
161
162
|
- lib/app_profiler/storage/file_storage.rb
|
162
163
|
- lib/app_profiler/storage/google_cloud_storage.rb
|
@@ -180,14 +181,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
180
181
|
requirements:
|
181
182
|
- - ">="
|
182
183
|
- !ruby/object:Gem::Version
|
183
|
-
version: '
|
184
|
+
version: '2.7'
|
184
185
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
185
186
|
requirements:
|
186
187
|
- - ">="
|
187
188
|
- !ruby/object:Gem::Version
|
188
189
|
version: '0'
|
189
190
|
requirements: []
|
190
|
-
rubygems_version: 3.
|
191
|
+
rubygems_version: 3.3.3
|
191
192
|
signing_key:
|
192
193
|
specification_version: 4
|
193
194
|
summary: Collect performance profiles for your Rails application.
|