1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
|
module Sigdump
VERSION = "0.2.4"
def self.setup(signal=ENV['SIGDUMP_SIGNAL'] || 'SIGCONT', path=ENV['SIGDUMP_PATH'])
Kernel.trap(signal) do
begin
dump(path)
rescue
end
end
end
def self.dump(path=ENV['SIGDUMP_PATH'])
_open_dump_path(path) do |io|
io.write "Sigdump at #{Time.now} process #{Process.pid} (#{$0})\n"
dump_all_thread_backtrace(io)
dump_gc_stat(io)
dump_object_count(io)
dump_gc_profiler_result(io)
end
end
def self.dump_all_thread_backtrace(io)
use_java_bean = defined?(Thread.current.to_java.getNativeThread.getId)
if use_java_bean
begin
bean = java.lang.management.ManagementFactory.getThreadMXBean
java_stacktrace_map = Hash[bean.getThreadInfo(bean.getAllThreadIds, true, true).map {|t| [t.getThreadId, t.toString] }]
rescue
# security error may happen
end
end
Thread.list.each do |thread|
dump_backtrace(thread, io)
if java_stacktrace_map
io.write " In Java " + java_stacktrace_map[thread.to_java.getNativeThread.getId]
io.flush
end
end
nil
end
def self.dump_backtrace(thread, io)
status = thread.status
if status == nil
status = "finished"
elsif status == false
status = "error"
end
io.write " Thread #{thread} status=#{status} priority=#{thread.priority}\n"
thread.backtrace.each {|bt|
io.write " #{bt}\n"
}
io.flush
nil
end
def self.dump_object_count(io)
if defined?(ObjectSpace.count_objects)
# ObjectSpace doesn't work in JRuby
io.write " Built-in objects:\n"
ObjectSpace.count_objects.sort_by {|k,v| -v }.each {|k,v|
io.write "%10s: %s\n" % [_fn(v), k]
}
string_size = 0
array_size = 0
hash_size = 0
cmap = {}
ObjectSpace.each_object {|o|
c = o.class
cmap[c] = (cmap[c] || 0) + 1
if c == String
string_size += o.bytesize
elsif c == Array
array_size = o.size
elsif c == Hash
hash_size = o.size
end
}
io.write " All objects:\n"
cmap.sort_by {|k,v| -v }.each {|k,v|
io.write "%10s: %s\n" % [_fn(v), k]
}
io.write " String #{_fn(string_size)} bytes\n"
io.write " Array #{_fn(array_size)} elements\n"
io.write " Hash #{_fn(hash_size)} pairs\n"
io.flush
end
nil
end
def self.dump_gc_stat(io)
io.write " GC stat:\n"
GC.stat.each do |key, val|
io.write " #{key}: #{val}\n"
end
io.flush
nil
end
def self.dump_gc_profiler_result(io)
return unless defined?(GC::Profiler) && GC::Profiler.enabled?
io.write " GC profiling result:\n"
io.write " Total garbage collection time: %f\n" % GC::Profiler.total_time
io.write GC::Profiler.result
GC::Profiler.clear
io.flush
nil
end
def self._fn(num)
s = num.to_s
if formatted = s.gsub!(/(\d)(?=(?:\d{3})+(?!\d))/, "\\1,")
formatted
else
s
end
end
private_class_method :_fn
def self._open_dump_path(path, &block)
case path
when nil, ""
path = "/tmp/sigdump-#{Process.pid}.log"
File.open(path, "a", &block)
when IO
yield path
when "-"
yield STDOUT
when "+"
yield STDERR
else
File.open(path, "a", &block)
end
end
private_class_method :_open_dump_path
end
|