OSDN Git Service

lockdep_chains: graph visualization
authorMark Salyzyn <salyzyn@google.com>
Thu, 12 May 2016 17:48:25 +0000 (10:48 -0700)
committerMark Salyzyn <salyzyn@google.com>
Fri, 13 May 2016 16:33:59 +0000 (09:33 -0700)
Create the system/extras/tools directory for simple analysis helpers.

Publish a host tool helper script that aids visualization of kernel
runtime collected lock dependencies. Helpful to look for circular
locks, or to get a handle on locking order used in a driver.

Bug: 28310683
Change-Id: Ib52c23f4cdefb37882232eff11b943eeb1b2abfa

tools/graph_lockdep_chains [new file with mode: 0755]

diff --git a/tools/graph_lockdep_chains b/tools/graph_lockdep_chains
new file mode 100755 (executable)
index 0000000..c0c11f4
--- /dev/null
@@ -0,0 +1,284 @@
+#! /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}