Skip to content

Instantly share code, notes, and snippets.

@nicolas-raoul
Forked from rogerleite/tdg.rb
Last active December 2, 2019 10:10
Show Gist options
  • Save nicolas-raoul/3039126 to your computer and use it in GitHub Desktop.
Save nicolas-raoul/3039126 to your computer and use it in GitHub Desktop.
#!/bin/sh
# Usage example: ./dump2pdf log_file output_file.pdf
ruby tdg.rb $1 > /tmp/graph.dot
dot -Tpdf /tmp/output_file.dot -o $2
#!/usr/bin/ruby -w
#
# tdg - Thread Dump Grapher (any better name?)
#
# Given a Java Thread Dump file as argument, it prints out
# a Grapviz (.dot) to generate a graph that shows which threads
# are waiting on others for a lock.
# (tested with a Sun JDK 1.4 thread dump)
#
# Transform the DOT file to PDF:
# dot -Tpdf graph.dot -o graph.pdf
#
# from: http://neuroning.com/2005/11/24/graphical-thread-dumps
# use: http://code.google.com/p/jrfonseca/wiki/XDot (to view the generated dot file)
class ThreadInfo
attr_accessor :stack, :name, :locks, :to_lock, :tid, :runnable
def stack()
@stack
end
def stack=(stack)
@stack = stack
end
def initialize(id, name)
@tid = id
@name = name
@runnable = false
@locks= []
@to_lock= nil
end
end
### Main
filename = ARGV.shift
if filename == nil || ! File.readable?(filename)
fail( "Usage: $0 <thread_dump_file>")
end
# Parse thread dump file
lock_to_thread = {}
threads = []
#thread_to_lock = {}
File.open(filename) do |file|
processing = false
currentThread = nil
file.each_line do |line|
# skip heading lines that are not part of the Thread dump
processing ||= ( line =~ /Full thread dump/ )
next if not processing
case line
when /^"(.*)".*tid=(\w+) +nid=\w+ (runnable)?/
currentThread = ThreadInfo.new($2, $1)
currentThread.runnable = ($3 != nil)
threads << currentThread
when /locked <(\w+)>/
if currentThread != nil then
currentThread.locks << $1
# thread_to_lock[currentThread.tid] ||= []
# thread_to_lock[currentThread.tid] << $1
lock_to_thread[$1] = currentThread.tid
end
#when /waiting to lock <(\w+)>/
when /waiting to lock <(\w+)>/
currentThread.to_lock = $1 if currentThread != nil
when /parking to wait for <(\w+)>/
currentThread.to_lock = $1 if currentThread != nil
end
end
end
## Generate output
print <<EOS
digraph G {
edge [fontname="Helvetica",fontsize=10,labelfontname="Helvetica",labelfontsize=10];
node [fontname="Helvetica",fontsize=10,shape=record, style=filled];
rankdir=LR;
ranksep=1;
bgcolor=white;
EOS
threads.each do |t|
# we ignore threads that are not involved in lockings
next if t.locks.empty? and t.to_lock == nil
if t.runnable
color = "green"
else
color = t.to_lock == nil ? "pink" : "red"
end
print <<-EOS
Thread#{t.tid} [label="#{t.name}|Owns: #{t.locks.join(",") }" fillcolor=#{color} ];
EOS
# print the Lock dependencies between threads (edges of the graph)
if t.to_lock != nil then
lockOwner = lock_to_thread[t.to_lock] || "Unknown"
print <<-EOS
Thread#{t.tid} -> Thread#{lockOwner} [taillabel="#{t.to_lock}" arrowhead=normal,arrowtail=none];
EOS
end
end
print "}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment