├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── docs ├── cargo-sccache-build.json ├── chart.js ├── index.html └── webviewer.png └── src ├── errors.rs ├── lib.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *~ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | os: 4 | - linux 5 | dist: trusty 6 | 7 | addons: 8 | apt: 9 | packages: 10 | - musl-tools 11 | 12 | rust: 13 | - stable 14 | - beta 15 | - nightly 16 | 17 | 18 | # Add a release build. 19 | matrix: 20 | include: 21 | - rust: stable 22 | env: TARGET=x86_64-unknown-linux-musl DEPLOY=1 23 | 24 | cache: cargo 25 | 26 | script: 27 | - | 28 | if [[ "$DEPLOY" = 1 ]]; then 29 | rustup target add $TARGET && 30 | cargo build --release --target $TARGET 31 | else 32 | cargo build -v && 33 | cargo test -v 34 | fi 35 | 36 | before_deploy: 37 | - strip target/$TARGET/release/tracetree 38 | - tar czvf tracetree-$TRAVIS_TAG-$TARGET.tar.gz -C target/$TARGET/release/ tracetree 39 | - ls -l tracetree-$TRAVIS_TAG-$TARGET.tar.gz 40 | 41 | env: 42 | global: 43 | - RUST_BACKTRACE=1 44 | deploy: 45 | provider: releases 46 | api_key: 47 | secure: kImz2Be7NJAjGofluPUZGAB/fG/fscr+0kr9KVQLqY36qx4+tueLk9+WrzVXqkWS+a1V0MwHgTpu0wTbXYPcpeDWkonKgihgb3LTTAsYcs4GRPV/u3dvNFJDYnrn96Sk81ysD6nq3PZSx7RVT8rqIkP30Sh1jovrq0iOBOuABKqPujRi/Wozc15Y5wPxgHAXcZpdKmeZpG46rIEM2+PFZxEV2mdHEQSIhy/Q7vmDea4UJGdKiXmUvrtTuFzBcHZ0ZEZu59vzZuwrZS2Kie05hj405M1ITe6eNwaNbvY+9Li+8vymbepOEIpXCpwyGr49ELimPz6FNU+Fi6kPORMgjoIll5og4XRaYq8gsJ69Ie64CC8QXYDrAaY5kHLKrMg46HMHmQoYdUgjwnAaTFpxhwAOwD+Ya5tHlOr4Yb+JQVVGNgQeDfSOZCyIzwu0Wwfugu60bBH548ZIj3ibnkyPFPxIrcekAxwP8PyC8oIxQ04DXQ3KSkZErD6syWcPw2dWtQZzGG6LRRT5fn3bJm53ugfi1pz6/e4n486qGzacJVekyHJWaxdA3oZjXsQVTcGjvkiYMr+wWAj5HhZwVWskCReefKRHw6NR5YYg7nVg2WhP5qrwcB6Nc7XFuiOU3koslUnT40pAnya33IwhFAQBQzWh/G0gFOzHmfhlMjr/N+g= 48 | file: tracetree-$TRAVIS_TAG-$TARGET.tar.gz 49 | on: 50 | repo: luser/tracetree 51 | condition: $DEPLOY = 1 52 | tags: true 53 | skip_cleanup: true 54 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "aho-corasick" 3 | version = "0.5.3" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "ansi_term" 11 | version = "0.9.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | dependencies = [ 19 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 20 | "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", 21 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 22 | ] 23 | 24 | [[package]] 25 | name = "backtrace" 26 | version = "0.3.2" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | dependencies = [ 29 | "backtrace-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 30 | "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 31 | "dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 33 | "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", 34 | "rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 36 | ] 37 | 38 | [[package]] 39 | name = "backtrace-sys" 40 | version = "0.1.11" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | dependencies = [ 43 | "gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", 45 | ] 46 | 47 | [[package]] 48 | name = "bitflags" 49 | version = "0.4.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | 52 | [[package]] 53 | name = "bitflags" 54 | version = "0.7.0" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | 57 | [[package]] 58 | name = "bitflags" 59 | version = "0.9.1" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | 62 | [[package]] 63 | name = "cfg-if" 64 | version = "0.1.0" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | 67 | [[package]] 68 | name = "chrono" 69 | version = "0.3.1" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | dependencies = [ 72 | "num 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 73 | "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 74 | "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", 75 | ] 76 | 77 | [[package]] 78 | name = "clap" 79 | version = "2.25.0" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | dependencies = [ 82 | "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 83 | "atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 85 | "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 86 | "term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 87 | "textwrap 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 88 | "unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 89 | "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 90 | "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 91 | ] 92 | 93 | [[package]] 94 | name = "dbghelp-sys" 95 | version = "0.2.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | dependencies = [ 98 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 99 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 100 | ] 101 | 102 | [[package]] 103 | name = "dtoa" 104 | version = "0.4.1" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | 107 | [[package]] 108 | name = "env_logger" 109 | version = "0.3.5" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | dependencies = [ 112 | "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 113 | "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", 114 | ] 115 | 116 | [[package]] 117 | name = "error-chain" 118 | version = "0.10.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | dependencies = [ 121 | "backtrace 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 122 | ] 123 | 124 | [[package]] 125 | name = "gcc" 126 | version = "0.3.51" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | 129 | [[package]] 130 | name = "getopts" 131 | version = "0.2.14" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | 134 | [[package]] 135 | name = "indextree" 136 | version = "1.0.1" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | 139 | [[package]] 140 | name = "itoa" 141 | version = "0.3.1" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | 144 | [[package]] 145 | name = "kernel32-sys" 146 | version = "0.2.2" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | dependencies = [ 149 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 150 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 151 | ] 152 | 153 | [[package]] 154 | name = "libc" 155 | version = "0.2.23" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | 158 | [[package]] 159 | name = "log" 160 | version = "0.3.7" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | 163 | [[package]] 164 | name = "memchr" 165 | version = "0.1.11" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | dependencies = [ 168 | "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", 169 | ] 170 | 171 | [[package]] 172 | name = "nix" 173 | version = "0.7.0" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | dependencies = [ 176 | "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 177 | "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 178 | "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", 179 | "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 180 | "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", 181 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 182 | ] 183 | 184 | [[package]] 185 | name = "nix" 186 | version = "0.8.1" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | dependencies = [ 189 | "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 190 | "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 191 | "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", 192 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 193 | ] 194 | 195 | [[package]] 196 | name = "num" 197 | version = "0.1.39" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | dependencies = [ 200 | "num-integer 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", 201 | "num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", 202 | "num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 203 | ] 204 | 205 | [[package]] 206 | name = "num-integer" 207 | version = "0.1.34" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | dependencies = [ 210 | "num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 211 | ] 212 | 213 | [[package]] 214 | name = "num-iter" 215 | version = "0.1.33" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | dependencies = [ 218 | "num-integer 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", 219 | "num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 220 | ] 221 | 222 | [[package]] 223 | name = "num-traits" 224 | version = "0.1.39" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | 227 | [[package]] 228 | name = "pulldown-cmark" 229 | version = "0.0.3" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | dependencies = [ 232 | "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 233 | ] 234 | 235 | [[package]] 236 | name = "rand" 237 | version = "0.3.15" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | dependencies = [ 240 | "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", 241 | ] 242 | 243 | [[package]] 244 | name = "redox_syscall" 245 | version = "0.1.18" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | 248 | [[package]] 249 | name = "regex" 250 | version = "0.1.80" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | dependencies = [ 253 | "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 254 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 255 | "regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 256 | "thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", 257 | "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 258 | ] 259 | 260 | [[package]] 261 | name = "regex-syntax" 262 | version = "0.3.9" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | 265 | [[package]] 266 | name = "rustc-demangle" 267 | version = "0.1.4" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | 270 | [[package]] 271 | name = "rustc_version" 272 | version = "0.1.7" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | dependencies = [ 275 | "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", 276 | ] 277 | 278 | [[package]] 279 | name = "semver" 280 | version = "0.1.20" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | 283 | [[package]] 284 | name = "serde" 285 | version = "1.0.8" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | 288 | [[package]] 289 | name = "serde_json" 290 | version = "1.0.2" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | dependencies = [ 293 | "dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 294 | "itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 295 | "num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 296 | "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 297 | ] 298 | 299 | [[package]] 300 | name = "skeptic" 301 | version = "0.5.0" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | dependencies = [ 304 | "pulldown-cmark 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 305 | "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 306 | ] 307 | 308 | [[package]] 309 | name = "spawn-ptrace" 310 | version = "0.1.0" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | dependencies = [ 313 | "nix 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 314 | "skeptic 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 315 | ] 316 | 317 | [[package]] 318 | name = "strsim" 319 | version = "0.6.0" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | 322 | [[package]] 323 | name = "tempdir" 324 | version = "0.3.5" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | dependencies = [ 327 | "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 328 | ] 329 | 330 | [[package]] 331 | name = "term_size" 332 | version = "0.3.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | dependencies = [ 335 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 336 | "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", 337 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 338 | ] 339 | 340 | [[package]] 341 | name = "textwrap" 342 | version = "0.6.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | dependencies = [ 345 | "term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 346 | "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 347 | ] 348 | 349 | [[package]] 350 | name = "thread-id" 351 | version = "2.0.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | dependencies = [ 354 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 355 | "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", 356 | ] 357 | 358 | [[package]] 359 | name = "thread_local" 360 | version = "0.2.7" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | dependencies = [ 363 | "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 364 | ] 365 | 366 | [[package]] 367 | name = "time" 368 | version = "0.1.37" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | dependencies = [ 371 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 372 | "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", 373 | "redox_syscall 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", 374 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 375 | ] 376 | 377 | [[package]] 378 | name = "tracetree" 379 | version = "0.1.6-pre" 380 | dependencies = [ 381 | "chrono 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 382 | "clap 2.25.0 (registry+https://github.com/rust-lang/crates.io-index)", 383 | "env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 384 | "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 385 | "indextree 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 386 | "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", 387 | "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", 388 | "nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 389 | "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 390 | "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 391 | "spawn-ptrace 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 392 | ] 393 | 394 | [[package]] 395 | name = "unicode-segmentation" 396 | version = "1.1.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | 399 | [[package]] 400 | name = "unicode-width" 401 | version = "0.1.4" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | 404 | [[package]] 405 | name = "utf8-ranges" 406 | version = "0.1.3" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | 409 | [[package]] 410 | name = "vec_map" 411 | version = "0.8.0" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | 414 | [[package]] 415 | name = "void" 416 | version = "1.0.2" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | 419 | [[package]] 420 | name = "winapi" 421 | version = "0.2.8" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | 424 | [[package]] 425 | name = "winapi-build" 426 | version = "0.1.1" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | 429 | [metadata] 430 | "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" 431 | "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" 432 | "checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159" 433 | "checksum backtrace 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "72f9b4182546f4b04ebc4ab7f84948953a118bd6021a1b6a6c909e3e94f6be76" 434 | "checksum backtrace-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3a0d842ea781ce92be2bf78a9b38883948542749640b8378b3b2f03d1fd9f1ff" 435 | "checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" 436 | "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" 437 | "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" 438 | "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" 439 | "checksum chrono 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d9123be86fd2a8f627836c235ecdf331fdd067ecf7ac05aa1a68fbcf2429f056" 440 | "checksum clap 2.25.0 (registry+https://github.com/rust-lang/crates.io-index)" = "867a885995b4184be051b70a592d4d70e32d7a188db6e8dff626af286a962771" 441 | "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" 442 | "checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90" 443 | "checksum env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f" 444 | "checksum error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8" 445 | "checksum gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)" = "120d07f202dcc3f72859422563522b66fe6463a4c513df062874daad05f85f0a" 446 | "checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685" 447 | "checksum indextree 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2f0a8fdca6bf08fff97457dc3c148bd4fca5a47cfbdf5ebe1dcaee130d704e1e" 448 | "checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" 449 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 450 | "checksum libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)" = "e7eb6b826bfc1fdea7935d46556250d1799b7fe2d9f7951071f4291710665e3e" 451 | "checksum log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5141eca02775a762cc6cd564d8d2c50f67c0ea3a372cbf1c51592b3e029e10ad" 452 | "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" 453 | "checksum nix 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a0d95c5fa8b641c10ad0b8887454ebaafa3c92b5cd5350f8fc693adafd178e7b" 454 | "checksum nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "47e49f6982987135c5e9620ab317623e723bd06738fd85377e8d55f57c8b6487" 455 | "checksum num 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "2c3a3dc9f30bf824141521b30c908a859ab190b76e20435fcd89f35eb6583887" 456 | "checksum num-integer 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "ef1a4bf6f9174aa5783a9b4cc892cacd11aebad6c69ad027a0b65c6ca5f8aa37" 457 | "checksum num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d1891bd7b936f12349b7d1403761c8a0b85a18b148e9da4429d5d102c1a41e" 458 | "checksum num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "1708c0628602a98b52fad936cf3edb9a107af06e52e49fdf0707e884456a6af6" 459 | "checksum pulldown-cmark 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8361e81576d2e02643b04950e487ec172b687180da65c731c03cf336784e6c07" 460 | "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" 461 | "checksum redox_syscall 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "3041aeb6000db123d2c9c751433f526e1f404b23213bd733167ab770c3989b4d" 462 | "checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" 463 | "checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" 464 | "checksum rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3058a43ada2c2d0b92b3ae38007a2d0fa5e9db971be260e0171408a4ff471c95" 465 | "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" 466 | "checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" 467 | "checksum serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c2f530d36fb84ec48fb7146936881f026cdbf4892028835fd9398475f82c1bb4" 468 | "checksum serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "48b04779552e92037212c3615370f6bd57a40ebba7f20e554ff9f55e41a69a7b" 469 | "checksum skeptic 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "061203a849117b0f7090baf8157aa91dac30545208fbb85166ac58b4ca33d89c" 470 | "checksum spawn-ptrace 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd3952bf090b2ea6bc1c0b52ad7d3307078002dafae587d483dea939f41000d" 471 | "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" 472 | "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" 473 | "checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209" 474 | "checksum textwrap 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f86300c3e7416ee233abd7cda890c492007a3980f941f79185c753a701257167" 475 | "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" 476 | "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" 477 | "checksum time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "ffd7ccbf969a892bf83f1e441126968a07a3941c24ff522a26af9f9f4585d1a3" 478 | "checksum unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18127285758f0e2c6cf325bb3f3d138a12fee27de4f23e146cd6a179f26c2cf3" 479 | "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" 480 | "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" 481 | "checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" 482 | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 483 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 484 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 485 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tracetree" 3 | version = "0.1.6-pre" 4 | authors = ["Ted Mielczarek "] 5 | license = "MIT" 6 | description = "Trace the execution of an entire process tree." 7 | repository = "https://github.com/luser/tracetree/" 8 | readme = "README.md" 9 | categories = ["command-line-utilities"] 10 | keywords = ["ptrace"] 11 | 12 | [dependencies] 13 | chrono = { version = "0.3", features = ["serde"] } 14 | clap = "2.25.0" 15 | env_logger = "0.3.5" 16 | error-chain = "0.10.0" 17 | indextree = "1.0.1" 18 | libc = "0.2.21" 19 | log = "0.3.6" 20 | nix = "0.8.1" 21 | serde = "1.0.8" 22 | serde_json = "1.0.2" 23 | spawn-ptrace = "0.1" 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Mozilla 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/luser/tracetree.svg?branch=master)](https://travis-ci.org/luser/tracetree) [![crates.io](https://img.shields.io/crates/v/tracetree.svg)](https://crates.io/crates/tracetree) [![](https://docs.rs/tracetree/badge.svg)](https://docs.rs/tracetree) 2 | 3 | tracetree 4 | ========= 5 | Run a process, ptrace'ing it and all of its children, and print the entire process tree at the end. 6 | 7 | Currently only Linux support is implemented. 8 | 9 | Examples 10 | ======== 11 | 12 | Print a process tree in text format to stdout: 13 | 14 | $ tracetree /bin/bash -c '/bin/sleep 1; /bin/sleep 1' 15 | 16665 bash -c /bin/sleep 1; /bin/sleep 1 [2.004s] 16 | 16666 sleep 1 [1.001s] 17 | 16667 sleep 1 [1.000s] 18 | 19 | 20 | Print a process tree in JSON format to `output.json`: 21 | 22 | $ tracetree -f json -o output.json /bin/bash -c '/bin/sleep 1; /bin/sleep 1' 23 | $ python -mjson.tool output.json 24 | { 25 | "children": [ 26 | { 27 | "children": [], 28 | "cmdline": [ 29 | "/bin/sleep", 30 | "1" 31 | ], 32 | "ended": "2017-06-22T06:28:58.960384212-04:00", 33 | "pid": 16727, 34 | "started": "2017-06-22T06:28:57.959636824-04:00" 35 | }, 36 | { 37 | "children": [], 38 | "cmdline": [ 39 | "/bin/sleep", 40 | "1" 41 | ], 42 | "ended": "2017-06-22T06:28:59.961990469-04:00", 43 | "pid": 16728, 44 | "started": "2017-06-22T06:28:58.960849779-04:00" 45 | } 46 | ], 47 | "cmdline": [ 48 | "/bin/bash", 49 | "-c", 50 | "/bin/sleep 1; /bin/sleep 1" 51 | ], 52 | "ended": "2017-06-22T06:28:59.962407641-04:00", 53 | "pid": 16726, 54 | "started": "2017-06-22T06:28:57.958836370-04:00" 55 | } 56 | 57 | 58 | JSON output can be viewed with this [web visualizer](https://luser.github.io/tracetree/): 59 | ![The web viewer displaying the JSON output from the previous command](docs/webviewer.png) 60 | -------------------------------------------------------------------------------- /docs/chart.js: -------------------------------------------------------------------------------- 1 | /*global fetch, FileReader */ 2 | 3 | function elapsed_secs(ms) { 4 | return (ms / 1000).toFixed(3) + 's'; 5 | } 6 | 7 | function process_to_rows(data) { 8 | var cmd = data.cmdline.length > 0 ? data.cmdline[0].split('/').pop() : ''; 9 | var start = new Date(data.started); 10 | var end = new Date(data.ended); 11 | var rows = [{'pid': data.pid, 12 | 'cmd': cmd, 13 | 'cmdline': data.cmdline.join(' '), 14 | 'start': start, 15 | 'end': end, 16 | 'elapsed': elapsed_secs(end.getTime() - start.getTime()) 17 | }]; 18 | for (var child of data.children) { 19 | rows.push(...process_to_rows(child)); 20 | } 21 | return rows; 22 | } 23 | 24 | function make_handler(data) { 25 | return (ev) => { 26 | console.log(`clicked ${data.pid}`); 27 | for (var prop of Object.getOwnPropertyNames(data)) { 28 | var el = document.getElementById('panel-' + prop); 29 | if (el == null) 30 | continue; 31 | el.innerText = data[prop].toString(); 32 | } 33 | var panel = document.getElementById('panel'); 34 | panel.style.left = ev.pageX + 'px'; 35 | panel.style.top = ev.pageY + 'px'; 36 | panel.style.display = 'block'; 37 | ev.stopPropagation(); 38 | }; 39 | } 40 | 41 | function draw_chart(rows) { 42 | console.log(`${rows.length} rows`); 43 | // Figure out how much space we have to work with. 44 | var available = parseInt(window.getComputedStyle(document.getElementById('execution')).width.slice(0, -2)); 45 | var start = rows[0].start.getTime(); 46 | var total = rows[0].end.getTime() - start; 47 | //TODO: allow scrolling for really long profiles. 48 | var factor = available / total; 49 | console.log(`available: ${available}, total: ${total}, factor: ${factor}`); 50 | function scalemin(val) { 51 | return Math.max(4, scale(val)); 52 | } 53 | function scale(val) { 54 | return val * factor; 55 | } 56 | rows.sort((a, b) => a.start.getTime() - b.start.getTime()); 57 | var table = document.getElementById('chart'); 58 | var tb = table.tBodies[0]; 59 | while (tb.rows.length > 0) { 60 | tb.rows[0].remove(); 61 | } 62 | 63 | for (var r of rows) { 64 | var tr = tb.insertRow(); 65 | var c = tr.insertCell(); 66 | c.className = 'label'; 67 | c.innerText = r.cmd; 68 | c = tr.insertCell(); 69 | r.startpretty = elapsed_secs(r.start.getTime() - start); 70 | r.endpretty = elapsed_secs(r.end.getTime() - start); 71 | var offset = scale(r.start.getTime() - start); 72 | var length = scalemin(r.end.getTime() - r.start.getTime()); 73 | c.innerHTML = `
`; 74 | c.firstChild.addEventListener('click', make_handler(r)); 75 | } 76 | } 77 | 78 | function chart_file() { 79 | console.log('chart_file'); 80 | var file = document.getElementById('input').files[0]; 81 | var reader = new FileReader(); 82 | reader.onload = () => { 83 | console.log('loaded'); 84 | var json = JSON.parse(reader.result); 85 | var rows = process_to_rows(json); 86 | draw_chart(rows); 87 | }; 88 | reader.readAsText(file); 89 | } 90 | 91 | function load_demo() { 92 | fetch('cargo-sccache-build.json') 93 | .then((res) => res.json()) 94 | .then((data) => draw_chart(process_to_rows(data))); 95 | } 96 | 97 | window.addEventListener('DOMContentLoaded', () => { 98 | document.getElementById('input').onchange = chart_file; 99 | document.getElementById('demo').onclick = load_demo; 100 | document.body.addEventListener('click', () => { 101 | console.log('clicked body'); 102 | document.getElementById('panel').style.display = 'none'; 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tracetree chart generator 6 | 7 | 47 | 48 | 49 |
50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 |
ProgramTime
58 |
59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 |
PID:
Commandline:
Start time:
End time:
Total time:
67 |
68 | 69 | 70 | -------------------------------------------------------------------------------- /docs/webviewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luser/tracetree/3bf0e2d17c1ae7bb9259ab96f0e9c4b86f64a6d7/docs/webviewer.png -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | error_chain! { 4 | foreign_links { 5 | Io(io::Error); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate chrono; 2 | #[macro_use] extern crate log; 3 | #[macro_use] 4 | extern crate error_chain; 5 | extern crate indextree; 6 | extern crate libc; 7 | extern crate nix; 8 | extern crate serde; 9 | extern crate spawn_ptrace; 10 | 11 | mod errors; 12 | 13 | pub use errors::*; 14 | 15 | use chrono::{Duration, Local, DateTime}; 16 | use indextree::{Arena, NodeId}; 17 | pub use indextree::NodeEdge; 18 | use libc::{c_long, pid_t}; 19 | use nix::c_void; 20 | use nix::sys::ptrace::{ptrace, ptrace_setoptions}; 21 | use nix::sys::ptrace::ptrace::{PTRACE_EVENT_FORK, PTRACE_EVENT_VFORK, PTRACE_EVENT_CLONE, 22 | PTRACE_EVENT_EXEC}; 23 | use nix::sys::ptrace::ptrace::{PTRACE_O_TRACECLONE, PTRACE_O_TRACEEXEC, PTRACE_O_TRACEFORK, 24 | PTRACE_O_TRACEVFORK, PTRACE_GETEVENTMSG, PTRACE_CONT}; 25 | use nix::sys::signal; 26 | use nix::sys::wait::{waitpid, WaitStatus}; 27 | use serde::{Serialize, Serializer}; 28 | use serde::ser::{SerializeSeq, SerializeStruct}; 29 | use spawn_ptrace::CommandPtraceSpawn; 30 | use std::collections::HashMap; 31 | use std::fs::File; 32 | use std::io::Read; 33 | use std::process::Command; 34 | use std::ptr; 35 | use std::time::Instant; 36 | 37 | /// Information about a spawned process. 38 | pub struct ProcessInfo { 39 | /// The process ID. 40 | pub pid: pid_t, 41 | /// When the process was started. 42 | pub started: Instant, 43 | /// When the process ended, or `None` if it is still running. 44 | pub ended: Option, 45 | /// The commandline with which this process was executed. 46 | pub cmdline: Vec, 47 | /// The working directory where the command was invoked. 48 | pub cwd: Option, 49 | } 50 | 51 | impl Default for ProcessInfo { 52 | fn default() -> ProcessInfo { 53 | ProcessInfo { 54 | pid: 0, 55 | started: Instant::now(), 56 | ended: None, 57 | cmdline: vec!(), 58 | cwd: None, 59 | } 60 | } 61 | } 62 | 63 | /// A tree of processes. 64 | pub struct ProcessTree { 65 | arena: Arena, 66 | pids: HashMap, 67 | root: NodeId, 68 | started: DateTime, 69 | } 70 | 71 | impl ProcessTree { 72 | /// Execute `cmd`, tracking all child processes it spawns, and return a `ProcessTree` listing 73 | /// them. 74 | pub fn spawn(mut cmd: Command, cmdline: &[T]) -> Result 75 | where T: AsRef 76 | { 77 | let started = Local::now(); 78 | let child = cmd.spawn_ptrace().chain_err(|| "Error spawning process")?; 79 | let pid = child.id() as pid_t; 80 | trace!("Spawned process {}", pid); 81 | // Setup our ptrace options 82 | ptrace_setoptions(pid, PTRACE_O_TRACEEXEC | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE).chain_err(|| "Error setting ptrace options")?; 83 | let mut arena = Arena::new(); 84 | let mut pids = HashMap::new(); 85 | let root = get_or_insert_pid(pid, &mut arena, &mut pids); 86 | arena[root].data.cmdline = cmdline.iter().map(|s| s.as_ref().to_string()).collect(); 87 | arena[root].data.cwd = get_cwd(pid); 88 | continue_process(pid, None).chain_err(|| "Error continuing process")?; 89 | loop { 90 | if !root.descendants(&arena).any(|node| arena[node].data.ended.is_none()) { 91 | break 92 | } 93 | match waitpid(-1, None) { 94 | Ok(WaitStatus::Exited(pid, ret)) => { 95 | trace!("Process {} exited with status {}", pid, ret); 96 | let node = get_or_insert_pid(pid, &mut arena, &mut pids); 97 | arena[node].data.ended = Some(Instant::now()); 98 | } 99 | Ok(WaitStatus::Signaled(pid, sig, _)) => { 100 | trace!("Process {} exited with signal {:?}", pid, sig); 101 | let node = get_or_insert_pid(pid, &mut arena, &mut pids); 102 | arena[node].data.ended = Some(Instant::now()); 103 | } 104 | Ok(WaitStatus::PtraceEvent(pid, _sig, event)) => { 105 | match event { 106 | PTRACE_EVENT_FORK | PTRACE_EVENT_VFORK | PTRACE_EVENT_CLONE => { 107 | let mut new_pid: pid_t = 0; 108 | ptrace(PTRACE_GETEVENTMSG, pid, ptr::null_mut(), 109 | &mut new_pid as *mut pid_t as *mut c_void) 110 | .chain_err(|| "Failed to get pid of forked process")?; 111 | let name = match event { 112 | PTRACE_EVENT_FORK => "fork", 113 | PTRACE_EVENT_VFORK => "vfork", 114 | PTRACE_EVENT_CLONE => "clone", 115 | _ => unreachable!(), 116 | }; 117 | trace!("[{}] {} new process {}", pid, name, new_pid); 118 | match pids.get(&pid) { 119 | Some(&parent) => { 120 | let cmdline = { 121 | let parent_data = &arena[parent].data; 122 | if parent_data.cmdline.len() > 1 { 123 | parent_data.cmdline[..1].to_vec() 124 | } else { 125 | vec![] 126 | } 127 | }; 128 | let child = get_or_insert_pid(new_pid, &mut arena, &mut pids); 129 | arena[child].data.cmdline = cmdline; 130 | arena[child].data.cwd = get_cwd(new_pid); 131 | parent.append(child, &mut arena); 132 | } 133 | None => bail!("Got an {:?} event for unknown parent pid {}", event, 134 | pid), 135 | } 136 | } 137 | PTRACE_EVENT_EXEC => { 138 | let mut buf = vec!(); 139 | match pids.get(&pid) { 140 | Some(&node) => { 141 | File::open(format!("/proc/{}/cmdline", pid)) 142 | .and_then(|mut f| f.read_to_end(&mut buf)) 143 | .and_then(|_| { 144 | let mut cmdline = buf.split(|&b| b == 0).map(|bytes| String::from_utf8_lossy(bytes).into_owned()).collect::>(); 145 | cmdline.pop(); 146 | debug!("[{}] exec {:?}", pid, cmdline); 147 | arena[node].data.cmdline = cmdline; 148 | Ok(()) 149 | }) 150 | .chain_err(|| "Couldn't read cmdline")?; 151 | } 152 | None => bail!("Got an exec event for unknown pid {}", pid), 153 | } 154 | } 155 | _ => panic!("Unexpected ptrace event: {:?}", event), 156 | } 157 | continue_process(pid, None).chain_err(|| "Error continuing process")?; 158 | } 159 | Ok(WaitStatus::Stopped(pid, sig)) => { 160 | trace!("[{}] stopped with {:?}", pid, sig); 161 | // Sometimes we get the SIGSTOP+exit from a child before we get the clone 162 | // stop from the parent, so insert any unknown pids here so we have a better 163 | // approximation of the process start time. 164 | get_or_insert_pid(pid, &mut arena, &mut pids); 165 | let continue_sig = if sig == signal::Signal::SIGSTOP { None } else { Some(sig) }; 166 | continue_process(pid, continue_sig).chain_err(|| "Error continuing process")?; 167 | } 168 | Ok(s) => bail!("Unexpected process status: {:?}", s), 169 | Err(e) => { 170 | match e { 171 | nix::Error::Sys(nix::Errno::EINTR) => { 172 | /*FIXME 173 | if SIGNAL_DELIVERED.swap(false, Ordering::Relaxed) { 174 | println!("Active processes:"); 175 | print_process_tree(root, arena, |info| info.ended.is_none()); 176 | } 177 | */ 178 | } 179 | _ => bail!("ptrace error: {:?}", e), 180 | } 181 | } 182 | } 183 | } 184 | Ok(ProcessTree { 185 | arena: arena, 186 | pids: pids, 187 | root: root, 188 | started: started, 189 | }) 190 | } 191 | 192 | /// Iterate over processes in the tree in tree order. 193 | pub fn traverse<'a>(&'a self) -> Traverse<'a> { 194 | Traverse { 195 | inner: self.root.traverse(&self.arena), 196 | arena: &self.arena, 197 | } 198 | } 199 | 200 | /// Look up a process in the tree by pid. 201 | pub fn get(&self, pid: pid_t) -> Option<&ProcessInfo> { 202 | match self.pids.get(&pid) { 203 | None => None, 204 | Some(&node) => Some(&self.arena[node].data), 205 | } 206 | } 207 | } 208 | 209 | pub struct Traverse<'a> { 210 | inner: indextree::Traverse<'a, ProcessInfo>, 211 | arena: &'a Arena, 212 | } 213 | 214 | impl<'a> Iterator for Traverse<'a> { 215 | type Item = NodeEdge<&'a ProcessInfo>; 216 | 217 | fn next(&mut self) -> Option> { 218 | match self.inner.next() { 219 | None => None, 220 | Some(NodeEdge::Start(node)) => { 221 | Some(NodeEdge::Start(&self.arena[node].data)) 222 | } 223 | Some(NodeEdge::End(node)) => { 224 | Some(NodeEdge::End(&self.arena[node].data)) 225 | } 226 | } 227 | } 228 | } 229 | 230 | struct ProcessInfoSerializable<'a>(NodeId, &'a Arena, Instant, DateTime); 231 | struct ChildrenSerializable<'a>(NodeId, &'a Arena, Instant, DateTime); 232 | 233 | fn dt(a: Instant, b: Instant, c: DateTime) -> String { 234 | let d = c + Duration::from_std(a - b).unwrap(); 235 | d.to_rfc3339() 236 | } 237 | 238 | impl<'a> Serialize for ProcessInfoSerializable<'a> { 239 | fn serialize(&self, serializer: S) -> ::std::result::Result 240 | where S: Serializer 241 | { 242 | let mut state = serializer.serialize_struct("ProcessInfo", 5)?; 243 | { 244 | let info = &self.1[self.0].data; 245 | state.serialize_field("pid", &info.pid)?; 246 | state.serialize_field("started", &dt(info.started, self.2, self.3))?; 247 | state.serialize_field("ended", &info.ended.map(|i| dt(i, self.2, self.3)))?; 248 | state.serialize_field("cmdline", &info.cmdline)?; 249 | state.serialize_field("cwd", &info.cwd)?; 250 | } 251 | state.serialize_field("children", &ChildrenSerializable(self.0, self.1, self.2, self.3))?; 252 | state.end() 253 | } 254 | } 255 | 256 | impl<'a> Serialize for ChildrenSerializable<'a> { 257 | fn serialize(&self, serializer: S) -> ::std::result::Result 258 | where S: Serializer 259 | { 260 | let len = self.0.children(self.1).count(); 261 | let mut seq = serializer.serialize_seq(Some(len))?; 262 | for c in self.0.children(self.1) { 263 | seq.serialize_element(&ProcessInfoSerializable(c, self.1, self.2, self.3))?; 264 | } 265 | seq.end() 266 | } 267 | } 268 | 269 | impl Serialize for ProcessTree { 270 | fn serialize(&self, serializer: S) -> ::std::result::Result 271 | where S: Serializer 272 | { 273 | let started = self.arena[self.root].data.started; 274 | let root_pi = ProcessInfoSerializable(self.root, &self.arena, started, self.started); 275 | root_pi.serialize(serializer) 276 | } 277 | } 278 | 279 | fn get_or_insert_pid(pid: pid_t, arena: &mut Arena, map: &mut HashMap) -> NodeId { 280 | *map.entry(pid).or_insert_with(|| { 281 | arena.new_node(ProcessInfo { pid: pid, .. ProcessInfo::default() }) 282 | }) 283 | } 284 | 285 | fn continue_process(pid: pid_t, signal: Option) -> nix::Result { 286 | let data = signal.map(|s| s as i32 as *mut c_void).unwrap_or(ptr::null_mut()); 287 | ptrace(PTRACE_CONT, pid, ptr::null_mut(), data) 288 | } 289 | 290 | fn get_cwd(pid: i32) -> Option { 291 | let txt = format!("/proc/{}/cwd", pid); 292 | let path = std::path::Path::new(&txt); 293 | let abspath = std::fs::canonicalize(path); 294 | match abspath { 295 | Ok(abspath) => abspath.into_os_string().into_string().ok(), 296 | _ => None, 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate clap; 3 | #[macro_use] extern crate log; 4 | extern crate env_logger; 5 | extern crate libc; 6 | extern crate nix; 7 | extern crate serde_json; 8 | extern crate tracetree; 9 | 10 | use clap::{Arg, App, AppSettings}; 11 | use nix::sys::signal; 12 | use std::borrow::Cow; 13 | use std::fs::File; 14 | use std::io::{self, Write}; 15 | use std::path::Path; 16 | use std::process::Command; 17 | use std::str; 18 | use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT}; 19 | use std::time::Duration; 20 | use tracetree::{NodeEdge, ProcessInfo, ProcessTree}; 21 | 22 | static SIGNAL_DELIVERED: AtomicBool = ATOMIC_BOOL_INIT; 23 | 24 | extern fn handle_signal(_:i32) { 25 | SIGNAL_DELIVERED.store(true, Ordering::Relaxed); 26 | } 27 | 28 | fn fmt_duration(duration: Duration) -> String { 29 | format!("{}.{:03}s", duration.as_secs(), duration.subsec_nanos() / 1000_000) 30 | } 31 | 32 | fn print_process_tree(tree: &ProcessTree, filter: F) 33 | where F: Fn(&ProcessInfo) -> bool, 34 | { 35 | let mut depth = 0; 36 | for i in tree.traverse() { 37 | match i { 38 | NodeEdge::Start(info) => { 39 | if filter(info) { 40 | let p = info.cmdline.first() 41 | .and_then(|b| Path::new(b) 42 | .file_name() 43 | .map(|s| s.to_string_lossy())) 44 | .unwrap_or(Cow::Borrowed("")); 45 | let cmdline = if info.cmdline.len() > 1 { 46 | let mut c = info.cmdline[1..].join(" "); 47 | c.push(' '); 48 | c 49 | } else { 50 | "".to_string() 51 | }; 52 | println!("{}{} {} {}[{}]", "\t".repeat(depth), info.pid, p, cmdline, info.ended.map(|e| fmt_duration(e - info.started)).unwrap_or("?".to_owned())); 53 | } 54 | depth += 1; 55 | } 56 | NodeEdge::End(_) => { 57 | depth -= 1; 58 | } 59 | } 60 | } 61 | } 62 | 63 | arg_enum!{ 64 | #[derive(Debug)] 65 | #[allow(non_camel_case_types)] 66 | pub enum OutputFormat { 67 | text, 68 | json 69 | } 70 | } 71 | 72 | fn main() { 73 | env_logger::init().unwrap(); 74 | 75 | let sig_action = signal::SigAction::new(signal::SigHandler::Handler(handle_signal), 76 | signal::SaFlags::empty(), 77 | signal::SigSet::empty()); 78 | unsafe { 79 | signal::sigaction(signal::SIGUSR1, &sig_action).expect("Failed to install signal handler!"); 80 | } 81 | 82 | trace!("This pid: {}", nix::unistd::getpid()); 83 | 84 | let matches = App::new(env!("CARGO_PKG_NAME")) 85 | .version(env!("CARGO_PKG_VERSION")) 86 | .setting(AppSettings::TrailingVarArg) 87 | .arg(Arg::with_name("out") 88 | .short("o") 89 | .long("out") 90 | .value_name("OUTPUT") 91 | .help("Write output to this file") 92 | .takes_value(true)) 93 | .arg(Arg::with_name("format") 94 | .short("f") 95 | .long("format") 96 | .value_name("FORMAT") 97 | .help("Output format") 98 | .possible_values(&OutputFormat::variants()) 99 | .default_value("text")) 100 | .arg(Arg::with_name("cmd") 101 | .help("Command to run") 102 | .multiple(true) 103 | .required(true) 104 | .use_delimiter(false)) 105 | .after_help("You can visualize the JSON output with this web viewer: https://luser.github.io/tracetree/") 106 | .get_matches(); 107 | 108 | let args = matches.values_of("cmd").unwrap().collect::>(); 109 | let fmt = value_t_or_exit!(matches, "format", OutputFormat); 110 | let mut cmd = Command::new(&args[0]); 111 | cmd.args(&args[1..]); 112 | let tree = ProcessTree::spawn(cmd, &args) 113 | .expect("Failed to spawn process"); 114 | let stdout = io::stdout(); 115 | let out: Box = matches.value_of_os("out") 116 | .and_then(|o| File::create(o).map(|f| Box::new(f) as Box).ok()) 117 | .unwrap_or_else(|| Box::new(stdout.lock()) as Box); 118 | match fmt { 119 | OutputFormat::text => print_process_tree(&tree, |_| true), 120 | OutputFormat::json => serde_json::to_writer(out, &tree) 121 | .expect("Failed to serialize process tree"), 122 | } 123 | } 124 | --------------------------------------------------------------------------------