To summarize before we begin: use strace-graph
with strace -f
to generate a nice graph of all the programs run by a given command.
The Problem
I have a CLI tool that’s meant to wrap several other tools to make using them together more convenient. It does this by offering a couple commands, each of which encapsulates several other commands. E.g. the full build process is like this:
- Run ‘language-specific-build-tool’ in
dir0
with options x, y, and z - Take the output of the first step and use that with ‘second-lang-specific-build-tool’ in
dir1
with options foo, bar and baz - Etc, etc
Theres a tool for doing all this via more conventient commands since it’s a relatively widespread workflow. Ostensibly, this is awesome, but debuging such a monolithic build process can be painful. In such a case, it would be awesome if we could break down what the build tool is doing to debug the component steps.
When something is automated in a shell script or similar imperative language, it’s easy to reason about the order things happen. However, let’s say that hypothetically this is written in a language with poor asynchronous language features while also requiring the usage of such asynchronous features, like, say, javascript/node. In such a case, figuring out the order of things by inspecting the source may be an agonizing process. Wouldn’t it instead be nice to have a way to say “which programs is this program running, and in which order, and with what options”? My discovery is that this is totally possible!
The Solution
Briefly, you use the program strace -f
to dump all the syscalls made by a program, and all the syscalls of it’s children, recursively. Then, you feed the output of that program into a perl script called strace-graph
to create a nice graph of what programs spawned what processes with which arguments.
To dump to a file, use the -o "OUTPUT_LOG_PATH"
flag with strace -f
to dump to a file.
Also, it seems strace-graph
was written to parse the output of an old strace
that outputs a couple fewer fields than current strace
does. To remove them, I use bash process substitution with sed to remove the troublesome lines:
./strace-graph <(sed '/---/d' process_strace_output.log | sed '/exited with/d')
All together, here are the commands I use:
strace -f -s 100 -o "OUTPUT_LOG_PATH" program_to_run_and_analyze
./strace-graph <(sed '/---/d' "OUTPUT_LOG_PATH" | sed '/exited with/d')
The resulting output looks something like this:
ember cordova:build --platform=android
node /home/leland/.nvm/versions/node/v0.12.7/bin/ember cordova:build --platform=android
+-- (anon)
+-- (anon)
+-- sh(/bin/sh) -c watchman version
+-- sh(/bin/sh) -c ember build --environment development
| `-- ember build --environment development
| node /home/leland/.nvm/versions/node/v0.12.7/bin/ember build --environment development
| +-- (anon)
| `-- sh(/bin/sh) -c watchman version
`-- sh(/bin/sh) -c cordova build android
`-- cordova build android
node /home/leland/.nvm/versions/node/v0.12.7/bin/cordova build android
`-- build(/home/leland/example_project/cordova/platforms/android/cordova/build)
node /home/leland/example_project/cordova/platforms/android/cordova/build
+-- sh(/bin/sh) -c java -version
| `-- java -version
| `-- (anon)
| +-- (anon)
| +-- (anon)
| +-- (anon)
| `-- ...
+-- sh(/bin/sh) -c android list targets --compact
| `-- android list targets --compact
| +-- (anon)
| +-- dirname /home/leland/Android/Sdk//tools/android
| +-- (anon)
| +-- basename /home/leland/Android/Sdk//tools/android
| +-- dirname /home/leland/Android/Sdk/tools
| +-- uname
| +-- uname
| +-- java -jar /home/leland/Android/Sdk/tools/lib/archquery.jar java.ext.dirs
| | `-- (anon)
| | +-- (anon)
| | +-- (anon)
| | +-- (anon)
| | `-- ...
| +-- java -jar /home/leland/Android/Sdk/tools/lib/archquery.jar
| | `-- (anon)
| | +-- (anon)
| | +-- (anon)
| | +-- (anon)
| | `-- ...
| java -Xmx256M -Dcom.android.sdkmanager.toolsdir=/home/le...
| `-- (anon)
| +-- (anon)
| +-- (anon)
| +-- (anon)
| `-- ...
+-- sh(/bin/sh) -c javac -version
| `-- javac -version
| `-- (anon)
| +-- (anon)
| +-- (anon)
| +-- (anon)
| `-- ...
`-- gradlew(/home/leland/example_project/cordova/platforms/android/gra...
bash /home/leland/example_project/cordova/platforms/android/gradle...
+-- basename /home/leland/example_project/cordova/platforms/android/gradlew
+-- uname
+-- (anon)
+-- dirname /home/leland/example_project/cordova/platforms/android/gradlew
+-- (anon)
+-- (anon)
java(/usr/lib/jvm/java-7-openjdk-amd64/bin/java) -Dorg.gradle.app...
`-- (anon)
+-- (anon)
+-- (anon)
+-- (anon)
+-- (anon)
`-- ...