blob: c0c11f459c4644aaf110aebb48330cf89abb633b [file] [log] [blame]
#! /bin/sh
progname="${0##*/}"
progname="${progname%.sh}"
usage() {
echo "Host side filter pipeline tool to convert kernel /proc/lockdep_chains via"
echo "graphviz into dependency chart for visualization. Watch out for any up-arrows"
echo "as they signify a circular dependency."
echo
echo "Usage: ${progname} [flags...] [regex...] < input-file > output-file"
echo
echo "flags:"
echo " --format={png|ps|svg|fig|imap|cmapx} | -T<format>"
echo " Output format, default png"
echo " --debug | -d"
echo " Leave intermediate files /tmp/${progname}.*"
echo " --verbose | -v"
echo " Do not strip address from lockname"
echo " --focus | -f"
echo " Show only primary references for regex matches"
echo " --cluster"
echo " Cluster the primary references for regex matches"
echo " --serial=<serial> | -s <serial>"
echo " Input from 'adb -s <serial> shell su 0 cat /proc/lockdep_chains'"
echo " --input=<filename> | -i <filename>"
echo " Input lockdeps from filename, otherwise from standard in"
echo " --output=<filename> | -o <filename>"
echo " Output formatted graph to filename, otherwise to standard out"
echo
echo "Chart is best viewed in portrait. ps or pdf formats tend to pixelate. png tends"
echo "to hit a bug in cairo rendering at scale. Not having a set of regex matches for"
echo "locknames will probably give you what you deserve ..."
echo
echo "Kernel Prerequisite to get /proc/lockdep_chains:"
echo " CONFIG_PROVE_LOCKING=y"
echo " CONFIG_LOCK_STAT=y"
echo " CONFIG_DEBUG_LOCKDEP=y"
}
rm -f /tmp/${progname}.*
# Indent rules and strip out address (may be overridden below)
beautify() {
sed 's/^./ &/
s/"[[][0-9a-f]*[]] /"/g'
}
input="cat -"
output="cat -"
dot_format="-Tpng"
filter=
debug=
focus=
cluster=
while [ ${#} -gt 0 ]; do
case ${1} in
-T | --format)
dot_format="-T${2}"
shift
;;
-T*)
dot_format="${1}"
;;
--format=*)
dot_format="-T${1#--format=}"
;;
--debug | -d)
debug=1
;;
--verbose | -v)
# indent, but do _not_ strip out addresses
beautify() {
sed 's/^./ &/'
}
;;
--focus | -f | --primary) # reserving --primary
focus=1
;;
--secondary) # reserving --secondary
focus=
;;
--cluster) # reserve -c for dot (configure plugins)
cluster=1
;;
--serial | -s)
if [ "${input}" != "cat -" ]; then
usage >&2
echo "ERROR: --input or --serial can only be specified once" >&2
exit 1
fi
input="adb -s ${2} shell su 0 cat /proc/lockdep_chains"
shift
;;
--serial=*)
input="adb -s ${1#--serial=} shell su 0 cat /proc/lockdep_chains"
;;
--input | -i)
if [ "${input}" != "cat -" ]; then
usage >&2
echo "ERROR: --input or --serial can only be specified once" >&2
exit 1
fi
input="cat ${2}"
shift
;;
--input=*)
if [ "${input}" != "cat -" ]; then
usage >&2
echo "ERROR: --input or --serial can only be specified once" >&2
exit 1
fi
input="cat ${1#--input=}"
;;
--output | -o)
if [ "${output}" != "cat -" ]; then
usage >&2
echo "ERROR: --output can only be specified once" >&2
exit 1
fi
output="cat - > ${2}" # run through eval
shift
;;
--output=*)
if [ "${output}" != "cat -" ]; then
usage >&2
echo "ERROR: --output can only be specified once" >&2
exit 1
fi
output="cat - > ${1#--output=}" # run through eval
;;
--help | -h | -\?)
usage
exit
;;
*)
# Everything else is a filter, which will also hide bad option flags,
# which is an as-designed price we pay to allow "->rwlock" for instance.
if [ X"${1}" = X"${1#* }" ]; then
if [ -z "${filter}" ]; then
filter="${1}"
else
filter="${filter}|${1}"
fi
else
if [ -z "${filter}" ]; then
filter=" ${1}"
else
filter="${filter}| ${1}"
fi
fi
;;
esac
shift
done
if [ -z "${filter}" ]; then
echo "WARNING: no regex specified will give you what you deserve!" >&2
fi
if [ -n "${focus}" -a -z "${filter}" ]; then
echo "WARNING: --focus without regex, ignored" >&2
fi
if [ -n "${cluster}" -a -z "${filter}" ]; then
echo "WARNING: --cluster without regex, ignored" >&2
fi
if [ -n "${cluster}" -a -n "${focus}" -a -n "${filter}" ]; then
echo "WARNING: orthogonal options --cluster & --focus, ignoring --cluster" >&2
cluster=
fi
# convert to dot digraph series
${input} |
sed '/^all lock chains:$/d
/ [&]__lockdep_no_validate__$/d
/irq_context: 0/d
s/irq_context: [1-9]/irq_context/
s/..*/"&" ->/
s/^$/;/' |
sed ': loop
N
s/ ->\n;$/ ;/
t
s/ ->\n/ -> /
b loop' > /tmp/${progname}.formed
if [ ! -s /tmp/${progname}.formed ]; then
echo "ERROR: no input" >&2
if [ -z "${debug}" ]; then
rm -f /tmp/${progname}.*
fi
exit 2
fi
if [ -n "${filter}" ]; then
grep "${filter}" /tmp/${progname}.formed |
sed 's/ ;//
s/ -> /|/g' |
tr '|' '\n' |
sort -u > /tmp/${progname}.symbols
fi
(
echo 'digraph G {'
(
echo 'remincross="true";'
echo 'concentrate="true";'
echo
if [ -s /tmp/${progname}.symbols ]; then
if [ -n "${cluster}" ]; then
echo 'subgraph cluster_symbols {'
(
grep "${filter}" /tmp/${progname}.symbols |
sed 's/.*/& [shape=box] ;/'
grep -v "${filter}" /tmp/${progname}.symbols |
sed 's/.*/& [shape=diamond] ;/'
) | beautify
echo '}'
else
grep "${filter}" /tmp/${progname}.symbols |
sed 's/.*/& [shape=box] ;/'
grep -v "${filter}" /tmp/${progname}.symbols |
sed 's/.*/& [shape=diamond] ;/'
fi
echo
fi
) | beautify
if [ -s /tmp/${progname}.symbols ]; then
if [ -z "${focus}" ]; then
# Secondary relationships
fgrep -f /tmp/${progname}.symbols /tmp/${progname}.formed
else
# Focus only on primary relationships
grep "${filter}" /tmp/${progname}.formed
fi
else
cat /tmp/${progname}.formed
fi |
# optimize int A -> B ; single references
sed 's/\("[^"]*"\) -> \("[^"]*"\) ->/\1 -> \2 ;|\2 ->/g' |
sed 's/\("[^"]*"\) -> \("[^"]*"\) ->/\1 -> \2 ;|\2 ->/g' |
tr '|' '\n' |
beautify |
grep ' -> ' |
sort -u |
if [ -s /tmp/${progname}.symbols ]; then
beautify < /tmp/${progname}.symbols |
sed 's/^ */ /' > /tmp/${progname}.short
tee /tmp/${progname}.split |
fgrep -f /tmp/${progname}.short |
sed 's/ ;$/ [color=red] ;/'
fgrep -v -f /tmp/${progname}.short /tmp/${progname}.split
rm -f /tmp/${progname}.short /tmp/${progname}.split
else
cat -
fi
echo '}'
) |
tee /tmp/${progname}.input |
if dot ${dot_format} && [ -z "${debug}" ]; then
rm -f /tmp/${progname}.*
fi |
eval ${output}