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