app_profiler 0.1.1 → 0.1.3
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 +3 -2
- data/lib/app_profiler/railtie.rb +3 -2
- data/lib/app_profiler/server.rb +96 -42
- data/lib/app_profiler/version.rb +1 -1
- data/lib/app_profiler.rb +4 -1
- metadata +2 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 69d1e8d957672c105e954b64a547955e4e1c499d3d70b529e1de1b56b2fb7d51
|
4
|
+
data.tar.gz: d7f022da1ebf4ead06ebadae4ded97260276afb12cb885866d76f53e520171c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 831da45ae0c02c629fc0e1788a6b1a220872b4c449291d72df9adbfdbfada6bf0ab815fe41e5034ad5376176b6235cfcbb83d00e4688a1ba4150faedb5ed5820
|
7
|
+
data.tar.gz: 0177d93977123800440539da265fac443023d8f654f74d64ccd7c4f9a74ed2750c83c2472a7e8285b065a79589fb6024280b4767ee6df148722d34339396cb72
|
@@ -27,8 +27,9 @@ module AppProfiler
|
|
27
27
|
return unless autoredirect
|
28
28
|
|
29
29
|
# Automatically redirect to profile if autoredirect is true.
|
30
|
-
|
31
|
-
|
30
|
+
location = AppProfiler.profile_url(upload)
|
31
|
+
if response[0].to_i < 500 && location
|
32
|
+
response[1]["Location"] = location
|
32
33
|
response[0] = 303
|
33
34
|
end
|
34
35
|
end
|
data/lib/app_profiler/railtie.rb
CHANGED
@@ -5,6 +5,7 @@ require "rails"
|
|
5
5
|
module AppProfiler
|
6
6
|
class Railtie < Rails::Railtie
|
7
7
|
config.app_profiler = ActiveSupport::OrderedOptions.new
|
8
|
+
config.app_profiler.profile_url_formatter = DefaultProfileFormatter
|
8
9
|
|
9
10
|
initializer "app_profiler.configs" do |app|
|
10
11
|
AppProfiler.logger = app.config.app_profiler.logger || Rails.logger
|
@@ -45,9 +46,9 @@ module AppProfiler
|
|
45
46
|
|
46
47
|
initializer "app_profiler.enable_server" do
|
47
48
|
if AppProfiler.server.enabled
|
48
|
-
AppProfiler::Server.start
|
49
|
+
AppProfiler::Server.start(AppProfiler.logger)
|
49
50
|
ActiveSupport::ForkTracker.after_fork do
|
50
|
-
AppProfiler::Server.start
|
51
|
+
AppProfiler::Server.start(AppProfiler.logger)
|
51
52
|
end
|
52
53
|
end
|
53
54
|
end
|
data/lib/app_profiler/server.rb
CHANGED
@@ -3,6 +3,9 @@
|
|
3
3
|
require "socket"
|
4
4
|
require "rack"
|
5
5
|
require "tempfile"
|
6
|
+
require "json"
|
7
|
+
require "active_support/core_ext/hash"
|
8
|
+
require "active_support/core_ext/module"
|
6
9
|
|
7
10
|
# This module provides a means to start a golang-inspired profile server
|
8
11
|
# it is implemented using stdlib and Rack to avoid additional dependencies
|
@@ -49,80 +52,122 @@ module AppProfiler
|
|
49
52
|
private
|
50
53
|
|
51
54
|
def handle(request)
|
52
|
-
|
53
|
-
|
54
|
-
response.status = HTTP_NOT_ALLOWED
|
55
|
-
response.write("Only GET requests are supported")
|
56
|
-
return response
|
57
|
-
end
|
55
|
+
return handle_not_allowed(request) if request.request_method != "GET"
|
56
|
+
|
58
57
|
case request.path
|
59
58
|
when "/profile"
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
return response
|
66
|
-
end
|
59
|
+
handle_profile(request)
|
60
|
+
else
|
61
|
+
handle_not_found(request)
|
62
|
+
end
|
63
|
+
end
|
67
64
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
65
|
+
def handle_not_allowed(request)
|
66
|
+
response = Rack::Response.new
|
67
|
+
|
68
|
+
response.status = HTTP_NOT_ALLOWED
|
69
|
+
response.write("Only GET requests are supported")
|
70
|
+
|
71
|
+
response
|
72
|
+
end
|
73
|
+
|
74
|
+
def handle_profile(request)
|
75
|
+
begin
|
76
|
+
stackprof_args, duration = validate_profile_params(request.params)
|
77
|
+
rescue InvalidProfileArgsError => e
|
78
|
+
return handle_bad_request(request, e.message)
|
79
|
+
end
|
80
|
+
|
81
|
+
response = Rack::Response.new
|
82
|
+
|
83
|
+
if start_running(stackprof_args)
|
84
|
+
start_time = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
|
85
|
+
|
86
|
+
sleep(duration)
|
87
|
+
|
88
|
+
profile = stop_running
|
89
|
+
|
90
|
+
response.status = HTTP_OK
|
91
|
+
response.set_header("Content-Type", "application/json")
|
92
|
+
|
93
|
+
profile_hash = profile.to_h
|
94
|
+
profile_hash["start_time_nsecs"] = start_time # NOTE: this is not part of the stackprof profile spec
|
95
|
+
|
96
|
+
response.write(JSON.dump(profile_hash))
|
97
|
+
|
98
|
+
if AppProfiler::Server.cors
|
99
|
+
response.set_header("Access-Control-Allow-Origin", AppProfiler::Server.cors_host)
|
85
100
|
end
|
86
101
|
else
|
87
|
-
response.status =
|
88
|
-
response.write("
|
102
|
+
response.status = HTTP_CONFLICT
|
103
|
+
response.write("A profile is already running")
|
89
104
|
end
|
105
|
+
|
106
|
+
response
|
107
|
+
end
|
108
|
+
|
109
|
+
def handle_not_found(request)
|
110
|
+
response = Rack::Response.new
|
111
|
+
|
112
|
+
response.status = HTTP_NOT_FOUND
|
113
|
+
response.write("Unsupported endpoint #{request.path}")
|
114
|
+
|
115
|
+
response
|
116
|
+
end
|
117
|
+
|
118
|
+
def handle_bad_request(request, message)
|
119
|
+
response = Rack::Response.new
|
120
|
+
|
121
|
+
response.status = HTTP_BAD_REQUEST
|
122
|
+
response.write("Invalid argument #{message}")
|
123
|
+
|
90
124
|
response
|
91
125
|
end
|
92
126
|
|
93
127
|
def validate_profile_params(params)
|
94
128
|
params = params.symbolize_keys
|
95
129
|
stackprof_args = {}
|
130
|
+
|
96
131
|
begin
|
97
132
|
duration = Float(params.key?(:duration) ? params[:duration] : AppProfiler::Server.duration)
|
98
133
|
rescue ArgumentError
|
99
|
-
raise InvalidProfileArgsError, "
|
134
|
+
raise InvalidProfileArgsError, "duration: #{params[:duration]}"
|
100
135
|
end
|
136
|
+
|
101
137
|
if params.key?(:mode)
|
102
138
|
if ["cpu", "wall", "object"].include?(params[:mode])
|
103
139
|
stackprof_args[:mode] = params[:mode].to_sym
|
104
140
|
else
|
105
|
-
raise InvalidProfileArgsError, "
|
141
|
+
raise InvalidProfileArgsError, "mode: #{params[:mode]}"
|
106
142
|
end
|
107
143
|
end
|
144
|
+
|
108
145
|
if params.key?(:interval)
|
109
146
|
stackprof_args[:interval] = params[:interval].to_i
|
110
|
-
|
147
|
+
|
148
|
+
raise InvalidProfileArgsError, "interval: #{params[:interval]}" if stackprof_args[:interval] <= 0
|
111
149
|
end
|
150
|
+
|
112
151
|
[stackprof_args, duration]
|
113
152
|
end
|
114
153
|
|
115
154
|
# Prevent multiple concurrent profiles by synchronizing between threads
|
116
|
-
def start_running
|
155
|
+
def start_running(stackprof_args)
|
117
156
|
@semaphore.synchronize do
|
118
157
|
return false if @profile_running
|
119
158
|
|
120
159
|
@profile_running = true
|
160
|
+
|
161
|
+
AppProfiler.start(**stackprof_args)
|
121
162
|
end
|
122
163
|
end
|
123
164
|
|
124
165
|
def stop_running
|
125
|
-
@semaphore.synchronize
|
166
|
+
@semaphore.synchronize do
|
167
|
+
AppProfiler.stop.tap do
|
168
|
+
@profile_running = false
|
169
|
+
end
|
170
|
+
end
|
126
171
|
end
|
127
172
|
end
|
128
173
|
|
@@ -147,6 +192,7 @@ module AppProfiler
|
|
147
192
|
class UNIX < Transport
|
148
193
|
def initialize
|
149
194
|
super
|
195
|
+
|
150
196
|
FileUtils.mkdir_p(PROFILER_TEMPFILE_PATH)
|
151
197
|
@socket_file = File.join(PROFILER_TEMPFILE_PATH, "app-profiler-#{Process.pid}.sock")
|
152
198
|
File.unlink(@socket_file) if File.exist?(@socket_file) && File.socket?(@socket_file)
|
@@ -184,7 +230,8 @@ module AppProfiler
|
|
184
230
|
end
|
185
231
|
end
|
186
232
|
|
187
|
-
def initialize(transport)
|
233
|
+
def initialize(transport, logger)
|
234
|
+
@logger = logger
|
188
235
|
case transport
|
189
236
|
when TRANSPORT_UNIX
|
190
237
|
@transport = ProfileServer::UNIX.new
|
@@ -196,7 +243,7 @@ module AppProfiler
|
|
196
243
|
|
197
244
|
@listen_thread = nil
|
198
245
|
|
199
|
-
|
246
|
+
@logger.info(
|
200
247
|
"[AppProfiler::Server] listening on addr=#{@transport.socket.addr}"
|
201
248
|
)
|
202
249
|
@pid = Process.pid
|
@@ -216,10 +263,13 @@ module AppProfiler
|
|
216
263
|
loop do
|
217
264
|
Thread.new(@transport.socket.accept) do |session|
|
218
265
|
request = session.gets
|
266
|
+
|
219
267
|
if request.nil?
|
220
268
|
session.close
|
269
|
+
|
221
270
|
next
|
222
271
|
end
|
272
|
+
|
223
273
|
method, path, http_version = request.split(" ")
|
224
274
|
path_info, query_string = path.split("?")
|
225
275
|
env = { # an extremely minimal rack env hash, just enough to get the job done
|
@@ -233,15 +283,18 @@ module AppProfiler
|
|
233
283
|
|
234
284
|
begin
|
235
285
|
session.print("#{http_version} #{status}\r\n")
|
286
|
+
|
236
287
|
headers.each do |header, value|
|
237
288
|
session.print("#{header}: #{value}\r\n")
|
238
289
|
end
|
290
|
+
|
239
291
|
session.print("\r\n")
|
292
|
+
|
240
293
|
body.each do |part|
|
241
294
|
session.print(part)
|
242
295
|
end
|
243
296
|
rescue => e
|
244
|
-
|
297
|
+
@logger.error(
|
245
298
|
"[AppProfiler::Server] exception #{e} responding to request #{request}: #{e.message}"
|
246
299
|
)
|
247
300
|
ensure
|
@@ -265,15 +318,16 @@ module AppProfiler
|
|
265
318
|
class << self
|
266
319
|
def reset
|
267
320
|
profile_servers.clear
|
321
|
+
|
268
322
|
DEFAULTS.each do |config, value|
|
269
323
|
class_variable_set(:"@@#{config}", value) # rubocop:disable Style/ClassVars
|
270
324
|
end
|
271
325
|
end
|
272
326
|
|
273
|
-
def start
|
327
|
+
def start(logger = Logger.new(IO::NULL))
|
274
328
|
return if profile_server
|
275
329
|
|
276
|
-
profile_servers[Process.pid] = ProfileServer.new(AppProfiler::Server.transport)
|
330
|
+
profile_servers[Process.pid] = ProfileServer.new(AppProfiler::Server.transport, logger)
|
277
331
|
profile_server.serve
|
278
332
|
profile_server
|
279
333
|
end
|
data/lib/app_profiler/version.rb
CHANGED
data/lib/app_profiler.rb
CHANGED
@@ -4,7 +4,6 @@ require "active_support/core_ext/class"
|
|
4
4
|
require "active_support/core_ext/module"
|
5
5
|
require "logger"
|
6
6
|
require "app_profiler/version"
|
7
|
-
require "app_profiler/railtie" if defined?(Rails::Railtie)
|
8
7
|
|
9
8
|
module AppProfiler
|
10
9
|
class ConfigurationError < StandardError
|
@@ -81,7 +80,11 @@ module AppProfiler
|
|
81
80
|
end
|
82
81
|
|
83
82
|
def profile_url(upload)
|
83
|
+
return unless AppProfiler.profile_url_formatter
|
84
|
+
|
84
85
|
AppProfiler.profile_url_formatter.call(upload)
|
85
86
|
end
|
86
87
|
end
|
88
|
+
|
89
|
+
require "app_profiler/railtie" if defined?(Rails::Railtie)
|
87
90
|
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.3
|
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-09-23 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: activesupport
|
@@ -43,20 +43,6 @@ dependencies:
|
|
43
43
|
- - ">="
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '0'
|
46
|
-
- !ruby/object:Gem::Dependency
|
47
|
-
name: railties
|
48
|
-
requirement: !ruby/object:Gem::Requirement
|
49
|
-
requirements:
|
50
|
-
- - ">="
|
51
|
-
- !ruby/object:Gem::Version
|
52
|
-
version: '5.2'
|
53
|
-
type: :runtime
|
54
|
-
prerelease: false
|
55
|
-
version_requirements: !ruby/object:Gem::Requirement
|
56
|
-
requirements:
|
57
|
-
- - ">="
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
version: '5.2'
|
60
46
|
- !ruby/object:Gem::Dependency
|
61
47
|
name: stackprof
|
62
48
|
requirement: !ruby/object:Gem::Requirement
|