app_profiler 0.1.0 → 0.1.2

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 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.