mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-30 10:58:31 -05:00
Add basic snapshot comparison for tracking memory leaks
This commit is contained in:
parent
1d99f5c9c0
commit
d5405eebde
2 changed files with 116 additions and 2 deletions
|
@ -230,7 +230,7 @@ GEM
|
||||||
metaclass (~> 0.0.1)
|
metaclass (~> 0.0.1)
|
||||||
mock_redis (0.13.2)
|
mock_redis (0.13.2)
|
||||||
moneta (0.8.0)
|
moneta (0.8.0)
|
||||||
msgpack (0.5.10)
|
msgpack (0.5.11)
|
||||||
multi_json (1.10.1)
|
multi_json (1.10.1)
|
||||||
multi_xml (0.5.5)
|
multi_xml (0.5.5)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
|
|
|
@ -3,7 +3,25 @@ class Admin::DiagnosticsController < Admin::AdminController
|
||||||
skip_before_filter :check_xhr
|
skip_before_filter :check_xhr
|
||||||
|
|
||||||
def memory_stats
|
def memory_stats
|
||||||
render text: memory_report(class_report: params.key?(:full)), content_type: Mime::TEXT
|
text = nil
|
||||||
|
|
||||||
|
if params.key?(:diff)
|
||||||
|
if !File.exists?(snapshot_filename)
|
||||||
|
text = "No initial snapshot exists"
|
||||||
|
else
|
||||||
|
filename = snapshot_filename + ".new"
|
||||||
|
snapshot_current_process(filename)
|
||||||
|
|
||||||
|
text = compare(snapshot_filename, filename)
|
||||||
|
end
|
||||||
|
elsif params.key?(:snapshot)
|
||||||
|
snapshot_current_process
|
||||||
|
text = "Writing snapshot to: #{snapshot_filename}\n\nTo get a diff use ?diff=1"
|
||||||
|
else
|
||||||
|
text = memory_report(class_report: params.key?(:full))
|
||||||
|
end
|
||||||
|
|
||||||
|
render text: text, content_type: Mime::TEXT
|
||||||
end
|
end
|
||||||
|
|
||||||
def dump_heap
|
def dump_heap
|
||||||
|
@ -24,6 +42,80 @@ class Admin::DiagnosticsController < Admin::AdminController
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
def compare(from, to)
|
||||||
|
from = Marshal::load(IO.binread(from));
|
||||||
|
to = Marshal::load(IO.binread(to));
|
||||||
|
|
||||||
|
diff = from - to
|
||||||
|
|
||||||
|
report = "#{diff.length} objects have leaked\n"
|
||||||
|
|
||||||
|
|
||||||
|
require 'objspace'
|
||||||
|
diff = diff.map do |id|
|
||||||
|
ObjectSpace._id2ref(id) rescue nil
|
||||||
|
end.compact!
|
||||||
|
|
||||||
|
report << "Summary:\n"
|
||||||
|
|
||||||
|
summary = {}
|
||||||
|
diff.each do |obj|
|
||||||
|
begin
|
||||||
|
summary[obj.class] ||= 0
|
||||||
|
summary[obj.class] += 1
|
||||||
|
rescue
|
||||||
|
# don't care
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
report << summary.sort{|a,b| b[1] <=> a[1]}[0..50].map{|k,v|
|
||||||
|
"#{k}: #{v}"
|
||||||
|
}.join("\n")
|
||||||
|
|
||||||
|
report << "\nSample Items:\n"
|
||||||
|
|
||||||
|
diff[0..5000].each do |v|
|
||||||
|
report << "#{v.class}: #{String === v ? v[0..300] : (40 + ObjectSpace.memsize_of(v)).to_s + " bytes"}\n" rescue nil
|
||||||
|
end
|
||||||
|
|
||||||
|
report
|
||||||
|
end
|
||||||
|
|
||||||
|
def snapshot_path
|
||||||
|
"#{Rails.root}/tmp/mem_snapshots"
|
||||||
|
end
|
||||||
|
|
||||||
|
def snapshot_filename
|
||||||
|
"#{snapshot_path}/#{Process.pid}.snapshot"
|
||||||
|
end
|
||||||
|
|
||||||
|
def snapshot_current_process(filename=nil)
|
||||||
|
filename ||= snapshot_filename
|
||||||
|
pid=fork do
|
||||||
|
snapshot(filename)
|
||||||
|
end
|
||||||
|
|
||||||
|
Process.wait(pid)
|
||||||
|
end
|
||||||
|
|
||||||
|
def snapshot(filename)
|
||||||
|
require 'objspace'
|
||||||
|
FileUtils.mkdir_p snapshot_path
|
||||||
|
object_ids = []
|
||||||
|
|
||||||
|
full_gc
|
||||||
|
|
||||||
|
ObjectSpace.each_object do |o|
|
||||||
|
begin
|
||||||
|
object_ids << o.object_id
|
||||||
|
rescue
|
||||||
|
# skip
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
IO.binwrite(filename, Marshal::dump(object_ids))
|
||||||
|
end
|
||||||
|
|
||||||
def memory_report(opts={})
|
def memory_report(opts={})
|
||||||
begin
|
begin
|
||||||
# ruby 2.1
|
# ruby 2.1
|
||||||
|
@ -74,4 +166,26 @@ Classes:
|
||||||
TEXT
|
TEXT
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def full_gc
|
||||||
|
# gc start may not collect everything
|
||||||
|
GC.start while new_count = decreased_count(new_count)
|
||||||
|
end
|
||||||
|
|
||||||
|
def decreased_count(old)
|
||||||
|
count = count_objects
|
||||||
|
if !old || count < old
|
||||||
|
count
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def count_objects
|
||||||
|
i = 0
|
||||||
|
ObjectSpace.each_object do |obj|
|
||||||
|
i += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue