app_profiler 0.1.0 → 0.1.2

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: 8e7279a1707f2425058eb5db7cb7e4fca673f1a3634ff76542d2703ce5fdfefc
4
- data.tar.gz: 8002f14694a26a43c87725ac54c44161401dc116b35dd793dcdf9b3f8f2e85c7
3
+ metadata.gz: e5e9b631f4b4b68fb8a2f6f2882ee0e4259cf6151d37388b299bd55c5a0ac6c3
4
+ data.tar.gz: a31ef0b50de69b81c84d5d8ec411e82db94a9aa2690fe633239e6c01f2bd2165
5
5
  SHA512:
6
- metadata.gz: ed3f2daae5d8a9729c2aee42179927e6f311fd0b1ea39f8934ad6811be19075debebb985660984e8b15ce93f4ab7c8812fde0e3fd63f9b972341147606a9fb23
7
- data.tar.gz: 336ac8b880934ef7078347ba1b6a1df046ee58147f91d387ef581b2071a3bcb2acbc50c8cd78e4c465585407bf74babb8b5147ce461936e71ca1993c6682d06b
6
+ metadata.gz: 4829d7292b8cc680c6936763b8fed26c5ab8f8661ada68f6c3b6a1890cee01d3ad7fb01bf377a3ce4cd3083f8b3b554d5b048c74ac594c540096724083adfb37
7
+ data.tar.gz: 20798219dc410c7b2ef0096e437ba8e7d8793a3dabac2a66246a765c8db06fca195c5d05981cff4adb28c6df40456876174e5f1d2b0c292fdff4f9922a1c059a
@@ -21,26 +21,19 @@ 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
- if response[0].to_i < 500
31
- response[1]["Location"] = profile_url(upload)
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
35
36
 
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
37
  def profile_data_url(upload)
45
38
  upload.url.to_s
46
39
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module AppProfiler
4
4
  class Profile
5
- INTERNAL_METADATA_KEYS = %i(id context)
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("[Profiler] uploaded profile profile_url=#{upload.url}")
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
@@ -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
@@ -16,6 +17,12 @@ module AppProfiler
16
17
  AppProfiler.middleware = app.config.app_profiler.middleware || Middleware
17
18
  AppProfiler.middleware.action = app.config.app_profiler.middleware_action || default_middleware_action
18
19
  AppProfiler.middleware.disabled = app.config.app_profiler.middleware_disabled || false
20
+ AppProfiler.server.enabled = app.config.app_profiler.server_enabled || false
21
+ AppProfiler.server.transport = app.config.app_profiler.server_transport || default_appprofiler_transport
22
+ AppProfiler.server.port = app.config.app_profiler.server_port || 0
23
+ AppProfiler.server.duration = app.config.app_profiler.server_duration || 30
24
+ AppProfiler.server.cors = app.config.app_profiler.server_cors || true
25
+ AppProfiler.server.cors_host = app.config.app_profiler.server_cors_host || "*"
19
26
  AppProfiler.autoredirect = app.config.app_profiler.autoredirect || false
20
27
  AppProfiler.speedscope_host = app.config.app_profiler.speedscope_host || ENV.fetch(
21
28
  "APP_PROFILER_SPEEDSCOPE_URL", "https://speedscope.app"
@@ -37,6 +44,15 @@ module AppProfiler
37
44
  end
38
45
  end
39
46
 
47
+ initializer "app_profiler.enable_server" do
48
+ if AppProfiler.server.enabled
49
+ AppProfiler::Server.start
50
+ ActiveSupport::ForkTracker.after_fork do
51
+ AppProfiler::Server.start
52
+ end
53
+ end
54
+ end
55
+
40
56
  private
41
57
 
42
58
  def default_middleware_action
@@ -46,5 +62,15 @@ module AppProfiler
46
62
  Middleware::UploadAction
47
63
  end
48
64
  end
65
+
66
+ def default_appprofiler_transport
67
+ if Rails.env.development?
68
+ # default to TCP server in development so that if wanted users are able to target
69
+ # the server with speedscope
70
+ AppProfiler::Server::TRANSPORT_TCP
71
+ else
72
+ AppProfiler::Server::TRANSPORT_UNIX
73
+ end
74
+ end
49
75
  end
50
76
  end
@@ -81,6 +81,7 @@ module AppProfiler
81
81
 
82
82
  def header(name)
83
83
  return unless @request.has_header?(name)
84
+
84
85
  @request.get_header(name)
85
86
  end
86
87
 
@@ -0,0 +1,360 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require "rack"
5
+ require "tempfile"
6
+ require "json"
7
+ require "active_support/core_ext/hash"
8
+ require "active_support/core_ext/module"
9
+
10
+ # This module provides a means to start a golang-inspired profile server
11
+ # it is implemented using stdlib and Rack to avoid additional dependencies
12
+
13
+ module AppProfiler
14
+ module Server
15
+ HTTP_OK = 200
16
+ HTTP_BAD_REQUEST = 400
17
+ HTTP_NOT_FOUND = 404
18
+ HTTP_NOT_ALLOWED = 405
19
+ HTTP_CONFLICT = 409
20
+
21
+ TRANSPORT_UNIX = "unix"
22
+ TRANSPORT_TCP = "tcp"
23
+
24
+ DEFAULTS = {
25
+ enabled: false,
26
+ transport: TRANSPORT_UNIX,
27
+ cors: true,
28
+ cors_host: "*",
29
+ port: 0,
30
+ duration: 30,
31
+ }
32
+
33
+ mattr_accessor :enabled, default: DEFAULTS[:enabled]
34
+ mattr_accessor :transport, default: DEFAULTS[:transport]
35
+ mattr_accessor :cors, default: DEFAULTS[:cors]
36
+ mattr_accessor :cors_host, default: DEFAULTS[:cors_host]
37
+ mattr_accessor :port, default: DEFAULTS[:port]
38
+ mattr_accessor :duration, default: DEFAULTS[:duration]
39
+
40
+ class ProfileApplication
41
+ class InvalidProfileArgsError < StandardError; end
42
+
43
+ def initialize
44
+ @semaphore = Thread::Mutex.new
45
+ @profile_running = false
46
+ end
47
+
48
+ def call(env)
49
+ handle(Rack::Request.new(env)).finish
50
+ end
51
+
52
+ private
53
+
54
+ def handle(request)
55
+ return handle_not_allowed(request) if request.request_method != "GET"
56
+
57
+ case request.path
58
+ when "/profile"
59
+ handle_profile(request)
60
+ else
61
+ handle_not_found(request)
62
+ end
63
+ end
64
+
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)
100
+ end
101
+ else
102
+ response.status = HTTP_CONFLICT
103
+ response.write("A profile is already running")
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
+
124
+ response
125
+ end
126
+
127
+ def validate_profile_params(params)
128
+ params = params.symbolize_keys
129
+ stackprof_args = {}
130
+
131
+ begin
132
+ duration = Float(params.key?(:duration) ? params[:duration] : AppProfiler::Server.duration)
133
+ rescue ArgumentError
134
+ raise InvalidProfileArgsError, "duration: #{params[:duration]}"
135
+ end
136
+
137
+ if params.key?(:mode)
138
+ if ["cpu", "wall", "object"].include?(params[:mode])
139
+ stackprof_args[:mode] = params[:mode].to_sym
140
+ else
141
+ raise InvalidProfileArgsError, "mode: #{params[:mode]}"
142
+ end
143
+ end
144
+
145
+ if params.key?(:interval)
146
+ stackprof_args[:interval] = params[:interval].to_i
147
+
148
+ raise InvalidProfileArgsError, "interval: #{params[:interval]}" if stackprof_args[:interval] <= 0
149
+ end
150
+
151
+ [stackprof_args, duration]
152
+ end
153
+
154
+ # Prevent multiple concurrent profiles by synchronizing between threads
155
+ def start_running(stackprof_args)
156
+ @semaphore.synchronize do
157
+ return false if @profile_running
158
+
159
+ @profile_running = true
160
+
161
+ AppProfiler.start(**stackprof_args)
162
+ end
163
+ end
164
+
165
+ def stop_running
166
+ @semaphore.synchronize do
167
+ AppProfiler.stop.tap do
168
+ @profile_running = false
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ # This is a minimal, non-compliant "HTTP" server.
175
+ # It will create an extremely minimal rack environment hash and hand it off
176
+ # to our application to process
177
+ class ProfileServer
178
+ PROFILER_TEMPFILE_PATH = "/tmp/app_profiler" # for tempfile that indicates port in filename or unix sockets
179
+
180
+ class Transport
181
+ attr_reader :socket
182
+
183
+ def client
184
+ raise(NotImplementedError)
185
+ end
186
+
187
+ def stop
188
+ raise(NotImplementedError)
189
+ end
190
+ end
191
+
192
+ class UNIX < Transport
193
+ def initialize
194
+ super
195
+
196
+ FileUtils.mkdir_p(PROFILER_TEMPFILE_PATH)
197
+ @socket_file = File.join(PROFILER_TEMPFILE_PATH, "app-profiler-#{Process.pid}.sock")
198
+ File.unlink(@socket_file) if File.exist?(@socket_file) && File.socket?(@socket_file)
199
+ @socket = UNIXServer.new(@socket_file)
200
+ end
201
+
202
+ def client
203
+ UNIXSocket.new(@socket_file)
204
+ end
205
+
206
+ def stop
207
+ File.unlink(@socket_file) if File.exist?(@socket_file) && File.socket?(@socket_file)
208
+ @socket.close
209
+ end
210
+ end
211
+
212
+ class TCP < Transport
213
+ SERVER_ADDRESS = "127.0.0.1" # it is ONLY safe to run this bound to localhost
214
+
215
+ def initialize(port = 0)
216
+ super()
217
+ FileUtils.mkdir_p(PROFILER_TEMPFILE_PATH)
218
+ @socket = TCPServer.new(SERVER_ADDRESS, port)
219
+ @port = @socket.addr[1]
220
+ @port_file = Tempfile.new("profileserver-#{Process.pid}-port-#{@port}-", PROFILER_TEMPFILE_PATH)
221
+ end
222
+
223
+ def client
224
+ TCPSocket.new(SERVER_ADDRESS, @port)
225
+ end
226
+
227
+ def stop
228
+ @port_file.unlink
229
+ @socket.close
230
+ end
231
+ end
232
+
233
+ def initialize(transport)
234
+ case transport
235
+ when TRANSPORT_UNIX
236
+ @transport = ProfileServer::UNIX.new
237
+ when TRANSPORT_TCP
238
+ @transport = ProfileServer::TCP.new(AppProfiler::Server.port)
239
+ else
240
+ raise "invalid transport #{transport}"
241
+ end
242
+
243
+ @listen_thread = nil
244
+
245
+ AppProfiler.logger.info(
246
+ "[AppProfiler::Server] listening on addr=#{@transport.socket.addr}"
247
+ )
248
+ @pid = Process.pid
249
+ at_exit { stop }
250
+ end
251
+
252
+ def client
253
+ @transport.client
254
+ end
255
+
256
+ def serve
257
+ return unless @listen_thread.nil?
258
+
259
+ app = ProfileApplication.new
260
+
261
+ @listen_thread = Thread.new do
262
+ loop do
263
+ Thread.new(@transport.socket.accept) do |session|
264
+ request = session.gets
265
+
266
+ if request.nil?
267
+ session.close
268
+
269
+ next
270
+ end
271
+
272
+ method, path, http_version = request.split(" ")
273
+ path_info, query_string = path.split("?")
274
+ env = { # an extremely minimal rack env hash, just enough to get the job done
275
+ "HTTP_VERSION" => http_version,
276
+ "REQUEST_METHOD" => method,
277
+ "PATH_INFO" => path_info,
278
+ "QUERY_STRING" => query_string,
279
+ "rack.input" => "",
280
+ }
281
+ status, headers, body = app.call(env)
282
+
283
+ begin
284
+ session.print("#{http_version} #{status}\r\n")
285
+
286
+ headers.each do |header, value|
287
+ session.print("#{header}: #{value}\r\n")
288
+ end
289
+
290
+ session.print("\r\n")
291
+
292
+ body.each do |part|
293
+ session.print(part)
294
+ end
295
+ rescue => e
296
+ AppProfiler.logger.error(
297
+ "[AppProfiler::Server] exception #{e} responding to request #{request}: #{e.message}"
298
+ )
299
+ ensure
300
+ session.close
301
+ end
302
+ end
303
+ end
304
+ end
305
+ end
306
+
307
+ def stop
308
+ return unless @pid == Process.pid
309
+
310
+ @listen_thread.kill
311
+ @transport.stop
312
+ end
313
+ end
314
+
315
+ private_constant :ProfileApplication, :ProfileServer
316
+
317
+ class << self
318
+ def reset
319
+ profile_servers.clear
320
+
321
+ DEFAULTS.each do |config, value|
322
+ class_variable_set(:"@@#{config}", value) # rubocop:disable Style/ClassVars
323
+ end
324
+ end
325
+
326
+ def start
327
+ return if profile_server
328
+
329
+ profile_servers[Process.pid] = ProfileServer.new(AppProfiler::Server.transport)
330
+ profile_server.serve
331
+ profile_server
332
+ end
333
+
334
+ def client
335
+ return unless profile_server
336
+
337
+ profile_server.client
338
+ end
339
+
340
+ def stop
341
+ return unless profile_server
342
+
343
+ profile_server.stop
344
+ profile_servers.delete(Process.pid)
345
+ end
346
+
347
+ private
348
+
349
+ def profile_server
350
+ profile_servers[Process.pid]
351
+ end
352
+
353
+ # It is possible there will be a server pre-fork, this is to distinguish
354
+ # the child server from its parent via PID
355
+ def profile_servers
356
+ @profile_servers ||= {}
357
+ end
358
+ end
359
+ end
360
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AppProfiler
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
@@ -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(%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))
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
- String.new.tap do |content|
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
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module AppProfiler
3
4
  module Yarn
4
5
  module WithSpeedscope
data/lib/app_profiler.rb CHANGED
@@ -4,12 +4,15 @@ 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
11
10
  end
12
11
 
12
+ DefaultProfileFormatter = proc do |upload|
13
+ "#{AppProfiler.speedscope_host}#profileURL=#{upload.url}"
14
+ end
15
+
13
16
  module Storage
14
17
  autoload :BaseStorage, "app_profiler/storage/base_storage"
15
18
  autoload :FileStorage, "app_profiler/storage/file_storage"
@@ -26,6 +29,7 @@ module AppProfiler
26
29
  require "app_profiler/request_parameters"
27
30
  require "app_profiler/profiler"
28
31
  require "app_profiler/profile"
32
+ require "app_profiler/server"
29
33
 
30
34
  mattr_accessor :logger, default: Logger.new($stdout)
31
35
  mattr_accessor :root
@@ -35,11 +39,13 @@ module AppProfiler
35
39
  mattr_accessor :autoredirect, default: false
36
40
  mattr_reader :profile_header, default: "X-Profile"
37
41
  mattr_accessor :context, default: nil
38
- mattr_reader :profile_url_formatter, default: nil
42
+ mattr_reader :profile_url_formatter,
43
+ default: DefaultProfileFormatter
39
44
 
40
45
  mattr_accessor :storage, default: Storage::FileStorage
41
46
  mattr_accessor :viewer, default: Viewer::SpeedscopeViewer
42
47
  mattr_accessor :middleware, default: Middleware
48
+ mattr_accessor :server, default: Server
43
49
 
44
50
  class << self
45
51
  def run(*args, &block)
@@ -62,9 +68,7 @@ module AppProfiler
62
68
  end
63
69
 
64
70
  def request_profile_header
65
- @@request_profile_header ||= begin # rubocop:disable Style/ClassVars
66
- profile_header.upcase.gsub("-", "_").prepend("HTTP_")
67
- end
71
+ @@request_profile_header ||= profile_header.upcase.tr("-", "_").prepend("HTTP_") # rubocop:disable Style/ClassVars
68
72
  end
69
73
 
70
74
  def profile_data_header
@@ -74,5 +78,13 @@ module AppProfiler
74
78
  def profile_url_formatter=(block)
75
79
  @@profile_url_formatter = block # rubocop:disable Style/ClassVars
76
80
  end
81
+
82
+ def profile_url(upload)
83
+ return unless AppProfiler.profile_url_formatter
84
+
85
+ AppProfiler.profile_url_formatter.call(upload)
86
+ end
77
87
  end
88
+
89
+ require "app_profiler/railtie" if defined?(Rails::Railtie)
78
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.0
4
+ version: 0.1.2
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-04-26 00:00:00.000000000 Z
16
+ date: 2022-09-23 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: railties
33
+ name: rack
34
34
  requirement: !ruby/object:Gem::Requirement
35
35
  requirements:
36
36
  - - ">="
37
37
  - !ruby/object:Gem::Version
38
- version: '5.2'
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: '5.2'
45
+ version: '0'
46
46
  - !ruby/object:Gem::Dependency
47
- name: rack
47
+ name: railties
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  requirements:
50
50
  - - ">="
51
51
  - !ruby/object:Gem::Version
52
- version: '0'
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: '0'
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: rake
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: minitest-stub-const
131
+ name: rake
132
132
  requirement: !ruby/object:Gem::Requirement
133
133
  requirements:
134
- - - '='
134
+ - - ">="
135
135
  - !ruby/object:Gem::Version
136
- version: '0.6'
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.6'
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: '0'
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.2.20
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.