Skip to content

Commit

Permalink
Add bin/steep-check for profiling
Browse files Browse the repository at this point in the history
  • Loading branch information
soutaro committed Aug 5, 2024
1 parent 68e85ce commit 17e1f52
Show file tree
Hide file tree
Showing 9 changed files with 432 additions and 2 deletions.
5 changes: 5 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ end
gem 'minitest-slow_test'

gem "debug", require: false, platform: :mri
gem "vernier", "~> 1.0"
gem "memory_profiler"

gem "rbs"
gem "majo"
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 (0.0.4)
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.0)

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

0 comments on commit 17e1f52

Please sign in to comment.