Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Made the middleware store the objectspace to the data store specified
- Loading branch information
1 parent
8511a13
commit f6228be
Showing
6 changed files
with
171 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,60 @@ | ||
require 'objspace' | ||
require 'yajl' | ||
require 'securerandom' | ||
|
||
module Rack | ||
class Profiler | ||
def initialize(app) | ||
def initialize(app, store:, async: true, object_space: ObjectSpace) | ||
@app = app | ||
@store = store | ||
@async = async | ||
@object_space = object_space | ||
@object_space.trace_object_allocations_start | ||
end | ||
|
||
def call(env) | ||
@app.call(env) | ||
result = @app.call(env) | ||
|
||
dumper = Thread.new { dump_allocations } | ||
dumper.join unless @async | ||
|
||
result | ||
end | ||
|
||
def dump_allocations | ||
read, write = IO.pipe | ||
|
||
if pid = fork | ||
# parent | ||
read.close | ||
@object_space.dump_all(output: write) | ||
write.close | ||
Process.wait(pid) unless @async | ||
else | ||
# child | ||
write.close | ||
parse_dump(input: read) | ||
read.close | ||
end | ||
end | ||
|
||
def parse_dump(input:, key: request_id, run: ->(id, obj) { store_object(id, obj) }) | ||
parser = Yajl::Parser.new | ||
parser.on_parse_complete = lambda do |obj| | ||
run.call(key, obj) | ||
end | ||
parser.parse(input) | ||
end | ||
|
||
def store_object(request_id, obj) | ||
object_id = obj.delete('address') | ||
@store["#{request_id}-#{object_id}"] = obj if object_id | ||
end | ||
|
||
def request_id | ||
thread_id = Thread.current.object_id | ||
random_salt = SecureRandom.hex(4) | ||
"#{thread_id}-#{random_salt}" | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,90 @@ | ||
require 'spec_helper' | ||
require 'support/rack_helpers' | ||
require 'support/object_space_helpers' | ||
require 'support/file_store_helpers' | ||
require 'support/rack_matchers' | ||
require 'rack/mock' | ||
require 'securerandom' | ||
|
||
describe Rack::Profiler do | ||
include RackHelpers | ||
include ObjectSpaceHelpers | ||
include FileStoreHelpers | ||
|
||
it 'does not modify the response from the app' do | ||
env = [418, { 'Content-Type' => 'text/plain', 'Content-Length' => '12' }, ["I'm a teapot"]] | ||
app = ->(_) { env } | ||
profiler = profiler(app) | ||
expect(request(profiler, '/')).to match_response(response(env)) | ||
let(:env) { [418, { 'Content-Type' => 'text/plain', 'Content-Length' => '12' }, ["I'm a teapot"]] } | ||
let(:app) { ->(_) { env } } | ||
let(:store) { Hash.new } | ||
let(:middleware) { described_class.new(app, store: store, async: false, object_space: objects) } | ||
let(:server) { rack(middleware) } | ||
|
||
let(:object_data) do | ||
<<-STR | ||
{"address":"0x7f930a8203a8", "type":"ARRAY"} | ||
STR | ||
end | ||
let(:objects) { object_space(object_data) } | ||
|
||
describe '#call' do | ||
it 'does not modify the response from the app' do | ||
expect(request(server, '/')).to match_response(response(env)) | ||
end | ||
|
||
context 'with real data' do | ||
let(:object_data) do | ||
<<-STR | ||
{"address":"0x7f930a820650", "type":"ARRAY"} | ||
{"address":"0x7f930a8206c8", "type":"OBJECT"} | ||
STR | ||
end | ||
let(:store_id) { SecureRandom.uuid } | ||
let(:store) { file_store(store_id) } | ||
|
||
it 'saves every object in the object space to the store' do | ||
expect { | ||
request(server, '/') | ||
}.to change { | ||
file_store_keys(store_id).map { |k| store[k] } | ||
}.from([]).to([{ 'type' => 'ARRAY' }, { 'type' => 'OBJECT' }]) | ||
end | ||
end | ||
end | ||
|
||
describe '#request_id' do | ||
it 'returns a unique string every time' do | ||
expect(middleware.request_id).to_not eq(middleware.request_id) | ||
end | ||
end | ||
|
||
describe '#store_object' do | ||
it 'stores the hash by the address property' do | ||
expect { | ||
middleware.store_object('key', 'address' => '0x7f930a820010', 'type' => 'OBJECT') | ||
}.to change { | ||
store['key-0x7f930a820010'] | ||
}.from(nil).to('type' => 'OBJECT') | ||
end | ||
|
||
it 'stores nothing if there is no address property' do | ||
expect { | ||
middleware.store_object('key', 'type' => 'OBJECT') | ||
}.to_not change { | ||
store.keys | ||
}.from([]) | ||
end | ||
end | ||
|
||
describe '#parse_dump' do | ||
it 'parses the input io stream and calls run for each line (parsed as json)' do | ||
json = <<-STR | ||
{"address":"0x7f930a8203a8", "type":"ARRAY"} | ||
{"address":"0x7f930a820420", "type":"ARRAY"} | ||
STR | ||
io = StringIO.new(json) | ||
calls = [] | ||
processor = ->(id, obj) { calls << [id, obj] } | ||
middleware.parse_dump(input: io, key: 'key', run: processor) | ||
expect(calls).to eq([['key', { 'address' => '0x7f930a8203a8', 'type' => 'ARRAY' } ], | ||
['key', { 'address' => '0x7f930a820420', 'type' => 'ARRAY' } ]]) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
require 'moneta' | ||
|
||
module FileStoreHelpers | ||
def file_store(unique_id) | ||
Moneta.new(:File, dir: path(unique_id)) | ||
end | ||
|
||
def file_store_keys(unique_id) | ||
Dir.glob(File.join(path(unique_id), '*')).map { |path| File.basename(path) } | ||
end | ||
|
||
private | ||
|
||
def path(unique_id) | ||
File.join('tmp', unique_id) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
module ObjectSpaceHelpers | ||
def object_space(object_data) | ||
FakeObjectSpace.new(object_data) | ||
end | ||
|
||
private | ||
|
||
class FakeObjectSpace | ||
def initialize(object_data) | ||
@object_data = object_data | ||
end | ||
|
||
def trace_object_allocations_start | ||
end | ||
|
||
def dump_all(output:) | ||
output << @object_data | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters