Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bin/steep-check for profiling #1198

Merged
merged 2 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ gemspec
gem "rake"
gem "minitest", "~> 5.21"
gem "minitest-hooks"
group :stackprof, optional: true do
gem 'minitest-slow_test'

group :development, optional: true do
gem "stackprof"
gem "debug", require: false, platform: :mri
gem "vernier", "~> 1.0", require: false, platform: :mri
gem "memory_profiler"
gem "majo"
end
gem 'minitest-slow_test'

gem "debug", require: false, platform: :mri
gem "rbs"
7 changes: 7 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ GEM
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
logger (1.6.0)
majo (1.0.0)
memory_profiler (1.0.2)
minitest (5.24.1)
minitest-hooks (1.5.1)
minitest (> 5.3)
Expand Down Expand Up @@ -86,18 +88,23 @@ GEM
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0)
vernier (1.1.1)

PLATFORMS
ruby

DEPENDENCIES
debug
majo
memory_profiler
minitest (~> 5.21)
minitest-hooks
minitest-slow_test
rake
rbs
stackprof
steep!
vernier (~> 1.0)

BUNDLED WITH
2.3.8
67 changes: 67 additions & 0 deletions bin/mem_graph.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
class MemGraph
attr_reader :edges

attr_reader :checked

attr_reader :generation

def initialize(generation)
@generation = generation
@edges = []
@checked = Set.new.compare_by_identity
@checked << self
@checked << edges
@checked << checked
end

IVARS = Object.instance_method(:instance_variables)
IVGET = Object.instance_method(:instance_variable_get)

def traverse(object)
return if checked.include?(object)
checked << object

case object
when Array
object.each do |value|
insert_edge(object, value)
traverse(value)
end
when Hash
object.each do |key, value|
insert_edge(object, key)
insert_edge(object, value)

traverse(key)
traverse(value)
end
else
IVARS.bind_call(object).each do |name|
if name.is_a?(Symbol)
value = IVGET.bind_call(object, name)
traverse(value)
insert_edge(object, value)
else
STDERR.puts "Unexpected instance variable name: #{name} in #{object.class}"
end
end
end
end

def insert_edge(source, dest)
case dest
when Integer, Symbol, nil, true, false, Float
else
edges << [
"#{source.class}(#{source.__id__})",
"#{dest.class}(#{dest.__id__})",
]
end
end

def dot
"digraph G {\n" + edges.uniq.map do |source, dest|
" #{source} -> #{dest};"
end.join("\n") + "}"
end
end
102 changes: 102 additions & 0 deletions bin/mem_prof.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
require "objspace"

class MemProf
attr_reader :generation

def initialize
end

def self.trace(io: STDOUT, &block)
profiler = MemProf.new
profiler.start

begin
ret = yield
rescue
ObjectSpace.trace_object_allocations_stop
ObjectSpace.trace_object_allocations_clear
raise
end

allocated, retained, collected = profiler.stop

counts = {}
collected.each do |id, entry|
counts[entry] ||= 0
counts[entry] += 1
end

counts.keys.sort_by {|entry| -counts[entry] }.take(200).each do |entry|
count = counts.fetch(entry)
io.puts "#{entry[0]},#{entry[1]},#{entry[2]},#{count}"
end

STDERR.puts "Total allocated: #{allocated.size}"
STDERR.puts "Total retained: #{retained.size}"
STDERR.puts "Total collected: #{collected.size}"

ret
end

def start
GC.disable
3.times { GC.start }
GC.start

@generation = GC.count
ObjectSpace.trace_object_allocations_start
end

def stop
ObjectSpace.trace_object_allocations_stop

allocated = objects()
retained = {}

GC.enable
GC.start
GC.start
GC.start

ObjectSpace.each_object do |obj|
next unless ObjectSpace.allocation_generation(obj) == generation
if o = allocated[obj.__id__]
retained[obj.__id__] = o
end
end

# ObjectSpace.trace_object_allocations_clear

collected = {}
allocated.each do |id, state|
collected[id] = state unless retained.key?(id)
end

[allocated, retained, collected]
end

def objects(hash = {})
ObjectSpace.each_object do |obj|
next unless ObjectSpace.allocation_generation(obj) == generation

file = ObjectSpace.allocation_sourcefile(obj) || "(no name)"
line = ObjectSpace.allocation_sourceline(obj)
klass = object_class(obj)

hash[obj.__id__] = [file, line, klass]
end

hash
end

KERNEL_CLASS_METHOD = Kernel.instance_method(:class)
def object_class(obj)
klass = obj.class rescue nil

unless Class === klass
# attempt to determine the true Class when .class returns something other than a Class
klass = KERNEL_CLASS_METHOD.bind_call(obj)
end
klass
end
end
Loading