diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..29b897c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +sudo: false + +os: linux +dist: trusty + +language: rust + +rust: + - stable + - beta + - nightly + +matrix: + allow_failures: + - rust: nightly + +cache: cargo + +env: + global: + - CARGO_BUILD_JOBS="2" diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..fc15c14 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,215 @@ +[[package]] +name = "aho-corasick" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "atty" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bytesize" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "clap" +version = "2.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.43" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memchr" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "strace-analyzer" +version = "0.3.0" +dependencies = [ + "bytesize 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "strsim" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "termion" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ucd-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "utf8-ranges" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "version_check" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum aho-corasick 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "68f56c7353e5a9547cbd76ed90f7bb5ffc3ba09d4ea9bd1d8c06c8b1142eeb5a" +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum bytesize 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "716960a18f978640f25101b5cbf1c6f6b0d3192fab36a2d98ca96f0ecbe41010" +"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" +"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" +"checksum memchr 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a3b4142ab8738a78c51896f704f83c11df047ff1bda9a92a661aa6361552d93d" +"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum regex 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "67d0301b0c6804eca7e3c275119d0b01ff3b7ab9258a65709e608a66312a1025" +"checksum regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "747ba3b235651f6e2f67dfa8bcdcd073ddb7c243cb21c442fc12395dfcac212d" +"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" +"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum utf8-ranges 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd70f467df6810094968e2fce0ee1bd0e87157aceb026a8c083bcf5e25b9efe4" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051" +"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d2423bd --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "strace-analyzer" +version = "0.3.0" +authors = ["Christian Krause "] +description = "analyze strace output" +license = "GPL-3.0-or-later" +readme = "README.md" +documentation = "https://github.com/wookietreiber/strace-analyzer" +homepage = "https://github.com/wookietreiber/strace-analyzer" +repository = "https://github.com/wookietreiber/strace-analyzer" +keywords = ["strace"] +categories = ["command-line-utilities"] + +[dependencies] +bytesize = "^1" +clap = "^2" +lazy_static = "^1" +regex = "^1" diff --git a/README.md b/README.md index daaa12b..b7fa865 100644 --- a/README.md +++ b/README.md @@ -3,5 +3,96 @@ strace analyzer Analyzes [strace][] output. +Pre-built packages can be downloaded from the [GitHub releases page][releases]. + +building +-------- + +build dependencies: + +- [rust][] + +```bash +git clone https://github.com/wookietreiber/strace-analyzer.git +cd strace-analyzer +cargo build --release +install -Dm755 target/release/strace-analyzer ~/bin/strace-analyzer +``` + + +usage +----- + +Create logs: + +```bash +strace -s 0 -ff -o cmd.strace cmd +``` + +Analyze logs, with `xxx` being the first process ID, the analysis will follow +forked processes automatically: + +``` +strace-analyzer cmd.strace.xxx +``` + + +caveats +------- + +- does only work with traces created with the usage example above, there is + no support for logs that contain output of multiple process ids + + +issues, features, use-cases, wish list +-------------------------------------- + +- If you think of a new (possibly high-level) analysis use case or how to + improve an existing one, please [open an issue][newissue]. If you have an + idea on how the output should look like, feel free to include a sketch of + an example. + +- If you recognize missing file associations in the output, i.e. bare file + descriptor numbers without a note as to why it could not be identified, + please [open an issue][newissue] and provide access to that particular, + complete strace log, so I am able to identify the problem. + + If you know that a particular file should be included, because you know + that file has been opened, it would be of great help if you would name + these files in the issue. + + +features that will not be implemented +------------------------------------- + +In the spirit of the Unix philosohpy of **do one thing and do it well**, +strace-analyzer will **not** do any of the following: + +- *filtering*, use tools like [grep][] or [awk][], e.g.: + + ```bash + strace-analyzer cmd.strace.1835 | grep pattern + strace-analyzer cmd.strace.1835 | awk '/pattern/' + ``` + +- *sorting*, use the [sort][] command line utility, e.g.: + + ```bash + strace-analyzer cmd.strace.27049 | sort -k9 + ``` + +- pretty *tabular output*, use the [column][] command line utility, e.g.: + + ```bash + strace-analyzer read strace.log.27049 | column -t + ``` + + +[awk]: http://man7.org/linux/man-pages/man1/gawk.1.html "gawk man page" +[column]: http://man7.org/linux/man-pages/man1/column.1.html "column man page" +[grep]: http://man7.org/linux/man-pages/man1/grep.1.html "grep man page" +[releases]: https://github.com/wookietreiber/strace-analyzer/releases "pre-built strace-analyzer releases" +[rust]: https://www.rust-lang.org/ "rust programming language home page" +[sort]: http://man7.org/linux/man-pages/man1/sort.1.html "sort man page" [strace]: http://sourceforge.net/projects/strace/ "strace home page" diff --git a/src/analysis.rs b/src/analysis.rs new file mode 100644 index 0000000..b38a7eb --- /dev/null +++ b/src/analysis.rs @@ -0,0 +1,377 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015-2018 Christian Krause * + * * + * Christian Krause * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * This file is part of strace-analyzer. * + * * + * strace-analyzer is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the license, or any * + * later version. * + * * + * strace-analyzer is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along * + * with strace-analyzer. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +use config::Config; +use log::*; +use summary::Summary; + +use regex::Regex; +use std::collections::HashMap; +use std::fs::File; +use std::io::{self, BufRead, BufReader}; +use std::path::{Path, PathBuf}; + +fn dup(fds: &mut HashMap, + syscall: &str, + oldfd: &u32, + newfd: u32, + config: &Config) { + + let summary = if let Some(summary_old) = fds.get(&oldfd) { + let old_file = &summary_old.file; + + debug( + format!("[{}] {} -> {} => {}", syscall, oldfd, &newfd, old_file), + config + ); + + Summary::new(old_file.clone()) + } else { + debug(format!("[{}] couldn't find oldfd {}", syscall, oldfd), config); + + Summary::new(String::from("DUP")) + }; + + insert(fds, newfd, summary, syscall, config); +} + +fn finish(fds: &mut HashMap, + fd: u32, + syscall: &str, + config: &Config) { + if let Some(summary) = fds.remove(&fd) { + debug( + format!("[{}] {} => {}", syscall, fd, summary.file), + config + ); + + summary.show(config); + } else { + verbose(format!("[{}] unknown fd {}", syscall, fd), config); + } +} + +fn insert(fds: &mut HashMap, + fd: u32, + summary: Summary, + syscall: &str, + config: &Config) { + if let Some(summary) = fds.insert(fd, summary) { + debug(format!( + "[{}] dropping {} without explicit close", + syscall, summary.file + ), config); + + summary.show(config) + }; +} + +fn join_paths(fds: &HashMap, + dirfd: &str, + pathname: &str) -> String { + match dirfd { + "AT_FDCWD" => { + String::from(pathname) + }, + fd_str => { + let dirfd: u32 = fd_str.parse().unwrap(); + + if let Some(dir_summary) = fds.get(&dirfd) { + let mut path = PathBuf::new(); + path.push(dir_summary.file.clone()); + path.push(pathname.clone()); + + if let Some(path) = path.to_str() { + String::from(path) + } else { + String::from(pathname) + } + } else { + String::from(pathname) + } + }, + } +} + +pub fn analyze(fds: &mut HashMap, + input: &Path, + config: &Config) -> io::Result<()> { + let file = File::open(input)?; + + lazy_static! { + static ref RE_CLONE: Regex = Regex::new( + r#"^clone\(.*\)\s+= (\d+)$"# + ).unwrap(); + } + + lazy_static! { + static ref RE_CLOSE: Regex = Regex::new( + r#"^close\((\d+)\)\s+= (-?\d+)\s*([A-Z]*).*$"# + ).unwrap(); + } + + lazy_static! { + static ref RE_CREAT: Regex = Regex::new( + r#"^creat\("([^"]+)", .+\)\s+= (\d+)$"# + ).unwrap(); + } + + lazy_static! { + static ref RE_DUP: Regex = Regex::new( + r#"^dup\((\d+)\)\s+= (\d+)$"# + ).unwrap(); + } + + lazy_static! { + static ref RE_DUP2: Regex = Regex::new( + r#"^dup2\((\d+), \d+\)\s+= (\d+)$"# + ).unwrap(); + } + + lazy_static! { + static ref RE_FCNTL_DUP: Regex = Regex::new( + r#"^fcntl\((\d+), F_DUPFD, \d+\)\s+= (\d+)$"# + ).unwrap(); + } + + lazy_static! { + static ref RE_OPEN: Regex = Regex::new( + // we're ignoring failures on purpose because they don't open fd + r#"^open\("([^"]+)", .+\)\s+= (\d+)$"# + ).unwrap(); + } + + lazy_static! { + static ref RE_OPENAT: Regex = Regex::new( + r#"^openat\((\d+|AT_FDCWD), "([^"]+)", .+\)\s+= (\d+)$"# + ).unwrap(); + } + + lazy_static! { + static ref RE_PIPE: Regex = Regex::new( + r#"^pipe\(\[(\d+), (\d+)\]\)\s+= (\d+)$"# + ).unwrap(); + } + + lazy_static! { + static ref RE_PREAD: Regex = Regex::new( + r#"^pread\((\d+),.*, (\d+), \d+\)\s+= (\d+)$"# + ).unwrap(); + } + + lazy_static! { + static ref RE_PWRITE: Regex = Regex::new( + r#"^pwrite\((\d+),.*, (\d+), \d+\)\s+= (\d+)$"# + ).unwrap(); + } + + lazy_static! { + static ref RE_READ: Regex = Regex::new( + r#"^read\((\d+),.*, (\d+)\)\s+= (\d+)$"# + ).unwrap(); + } + + lazy_static! { + static ref RE_SOCKET: Regex = Regex::new( + r#"^socket\(.*\)\s+= (\d+)$"# + ).unwrap(); + } + + lazy_static! { + static ref RE_WRITE: Regex = Regex::new( + r#"^write\((\d+),.*, (\d+)\)\s+= (\d+)$"# + ).unwrap(); + } + + for l in BufReader::new(file).lines() { + let line = l?; + + for cap in RE_CREAT.captures_iter(&line) { + let file = String::from(&cap[1]); + let fd: u32 = cap[2].parse().unwrap(); + + debug(format!("[creat] {} => {}", fd, file), config); + + let syscall = "creat"; + insert(fds, fd, Summary::new(file), syscall, config); + } + + for cap in RE_CLOSE.captures_iter(&line) { + let fd: u32 = cap[1].parse().unwrap(); + let status: i32 = cap[2].parse().unwrap(); + let error = &cap[3]; + let syscall = "close"; + + match (status, error) { + (0, _) => + finish(fds, fd, syscall, config), + + (_, "EBADF") => + debug(format!("[close] {} => bad fd", fd), config), + + (_, error) => { + verbose(format!("[close] {} => {}", fd, error), config); + finish(fds, fd, syscall, config) + }, + } + } + + for cap in RE_CLONE.captures_iter(&line) { + let pid = &cap[1]; + + let trace = Path::new(&input).with_extension(pid); + + verbose( + format!("[clone] tracing pid {} in {:?} ...", pid, trace), + config + ); + + let mut cfds = fds.clone(); + + for (_, summary) in cfds.iter_mut() { + summary.reset(); + } + + analyze(&mut cfds, &trace, config)?; + + verbose(format!("[clone] tracing pid {} finished", pid), config); + } + + for cap in RE_DUP.captures_iter(&line) { + let oldfd: u32 = cap[1].parse().unwrap(); + let newfd: u32 = cap[2].parse().unwrap(); + + dup(fds, "dup", &oldfd, newfd, config); + } + + for cap in RE_DUP2.captures_iter(&line) { + let oldfd: u32 = cap[1].parse().unwrap(); + let newfd: u32 = cap[2].parse().unwrap(); + + dup(fds, "dup2", &oldfd, newfd, config); + } + + for cap in RE_FCNTL_DUP.captures_iter(&line) { + let oldfd: u32 = cap[1].parse().unwrap(); + let newfd: u32 = cap[2].parse().unwrap(); + + dup(fds, "fcntl-dup", &oldfd, newfd, config); + } + + for cap in RE_OPEN.captures_iter(&line) { + let file = String::from(&cap[1]); + let fd: u32 = cap[2].parse().unwrap(); + + debug(format!("[open] {} => {}", fd, file), config); + + let syscall = "open"; + insert(fds, fd, Summary::new(file), syscall, config); + } + + for cap in RE_OPENAT.captures_iter(&line) { + let dirfd = &cap[1]; + let pathname = &cap[2]; + let fd: u32 = cap[3].parse().unwrap(); + + let file = join_paths(fds, dirfd, pathname); + + debug(format!("[openat] {} => {}", fd, file), config); + + let syscall = "openat"; + insert(fds, fd, Summary::new(file), syscall, config); + } + + for cap in RE_PIPE.captures_iter(&line) { + let readend = cap[1].parse().unwrap(); + let writeend = cap[2].parse().unwrap(); + + debug(format!("[pipe] {} => {}", readend, writeend), config); + + let syscall = "pipe"; + insert(fds, readend, Summary::pipe(), syscall, config); + insert(fds, writeend, Summary::pipe(), syscall, config); + } + + for cap in RE_PREAD.captures_iter(&line) { + let fd: u32 = cap[1].parse().unwrap(); + + if let Some(summary) = fds.get_mut(&fd) { + let bytes: u64 = cap[2].parse().unwrap(); + summary.update_read(bytes); + } else { + verbose(format!("[pread] unknown fd {}", fd), config); + } + } + + for cap in RE_PWRITE.captures_iter(&line) { + let fd: u32 = cap[1].parse().unwrap(); + + if let Some(summary) = fds.get_mut(&fd) { + let bytes: u64 = cap[2].parse().unwrap(); + summary.update_write(bytes); + } else { + verbose(format!("[pwrite] unknown fd {}", fd), config); + } + } + + for cap in RE_READ.captures_iter(&line) { + let fd: u32 = cap[1].parse().unwrap(); + + if let Some(summary) = fds.get_mut(&fd) { + let bytes: u64 = cap[2].parse().unwrap(); + summary.update_read(bytes); + } else { + verbose(format!("[read] unknown fd {}", fd), config); + } + } + + for cap in RE_SOCKET.captures_iter(&line) { + let fd: u32 = cap[1].parse().unwrap(); + + debug(format!("[socket] {}", fd), config); + + let syscall = "socket"; + insert(fds, fd, Summary::socket(), syscall, config); + } + + for cap in RE_WRITE.captures_iter(&line) { + let fd: u32 = cap[1].parse().unwrap(); + + if let Some(summary) = fds.get_mut(&fd) { + let bytes: u64 = cap[2].parse().unwrap(); + summary.update_write(bytes); + } else { + verbose(format!("[write] unknown fd {}", fd), config); + } + } + } + + for (_, summary) in fds.iter() { + summary.show(config); + } + + Ok(()) +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..689cfde --- /dev/null +++ b/src/config.rs @@ -0,0 +1,30 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015-2018 Christian Krause * + * * + * Christian Krause * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * This file is part of strace-analyzer. * + * * + * strace-analyzer is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the license, or any * + * later version. * + * * + * strace-analyzer is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along * + * with strace-analyzer. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +pub struct Config { + pub debug: bool, + pub verbose: bool, +} diff --git a/src/log.rs b/src/log.rs new file mode 100644 index 0000000..09b0548 --- /dev/null +++ b/src/log.rs @@ -0,0 +1,39 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015-2018 Christian Krause * + * * + * Christian Krause * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * This file is part of strace-analyzer. * + * * + * strace-analyzer is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the license, or any * + * later version. * + * * + * strace-analyzer is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along * + * with strace-analyzer. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +use config::Config; + +pub fn debug(message: String, config: &Config) { + if config.debug { + eprintln!("[debug] {}", message); + } +} + +pub fn verbose(message: String, config: &Config) { + if config.debug || config.verbose { + eprintln!("{}", message); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..cb26727 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,82 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015-2018 Christian Krause * + * * + * Christian Krause * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * This file is part of strace-analyzer. * + * * + * strace-analyzer is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the license, or any * + * later version. * + * * + * strace-analyzer is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along * + * with strace-analyzer. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +mod analysis; +mod config; +mod log; +mod summary; + +use analysis::analyze; +use config::Config; +use summary::Summary; + +extern crate bytesize; +#[macro_use] +extern crate clap; +#[macro_use] +extern crate lazy_static; +extern crate regex; + +use clap::{Arg, App}; +use std::collections::HashMap; +use std::io; +use std::path::Path; + +fn main() -> io::Result<()> { + let matches = App::new("strace-analyzer") + .version(crate_version!()) + .about("analyze strace output") + .arg(Arg::with_name("file") + .help("strace log") + .required(true)) + .arg(Arg::with_name("debug") + .long("debug") + .help("debug output")) + .arg(Arg::with_name("verbose") + .short("v") + .long("verbose") + .help("verbose output")) + .get_matches(); + + let input = Path::new(matches.value_of("file").unwrap()); + + let config = Config { + debug: matches.is_present("debug"), + verbose: matches.is_present("verbose"), + }; + + let stdin = Summary::new(String::from("STDIN")); + let stdout = Summary::new(String::from("STDOUT")); + let stderr = Summary::new(String::from("STDERR")); + + let mut fds: HashMap = HashMap::new(); + + fds.insert(0, stdin); + fds.insert(1, stdout); + fds.insert(2, stderr); + + analyze(&mut fds, input, &config) +} diff --git a/src/summary.rs b/src/summary.rs new file mode 100644 index 0000000..007cd4b --- /dev/null +++ b/src/summary.rs @@ -0,0 +1,125 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015-2018 Christian Krause * + * * + * Christian Krause * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * This file is part of strace-analyzer. * + * * + * strace-analyzer is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the license, or any * + * later version. * + * * + * strace-analyzer is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along * + * with strace-analyzer. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +use config::Config; +use log::*; + +use bytesize::ByteSize; + +#[derive(Clone)] +#[derive(Debug)] +pub struct Summary { + pub file: String, + read_ops: u64, + write_ops: u64, + read_bytes: u64, + write_bytes: u64, +} + +impl Summary { + pub fn new(file: String) -> Summary { + Summary { + file, + read_ops: 0, + write_ops: 0, + read_bytes: 0, + write_bytes: 0, + } + } + + pub fn pipe() -> Summary { + Summary::new(String::from("PIPE")) + } + + pub fn socket() -> Summary { + Summary::new(String::from("SOCKET")) + } + + pub fn reset(&mut self) { + self.read_ops = 0; + self.write_ops = 0; + self.read_bytes = 0; + self.write_bytes = 0; + } + + pub fn update_read(&mut self, bytes: u64) { + self.read_ops += 1; + self.read_bytes += bytes; + } + + pub fn update_write(&mut self, bytes: u64) { + self.write_ops += 1; + self.write_bytes += bytes; + } + + pub fn show(&self, config: &Config) { + if !config.verbose && + (self.file == "/dev/null" || + self.file.starts_with("/etc") || + self.file.starts_with("/usr") || + self.file == "STDOUT" || + self.file == "STDERR" || + self.file == "STDIN" || + self.file == "SOCKET" || + self.file == "DUP" || + self.file == "PIPE") { + return; + } + + if self.read_ops == 0 && self.write_ops == 0 { + debug(format!("no I/O with {}", self.file), config); + return; + } + + if self.read_ops > 0 { + let read = ByteSize(self.read_bytes).to_string_as(true); + let op_size = self.read_bytes / self.read_ops; + let mean = ByteSize(op_size).to_string_as(true); + + println!( + "read {} with {} ops ({} / op) {}", + read, + self.read_ops, + mean, + self.file, + ); + } + + if self.write_ops > 0 { + let write = ByteSize(self.write_bytes).to_string_as(true); + let op_size = self.write_bytes / self.write_ops; + let mean = ByteSize(op_size).to_string_as(true); + + println!( + "write {} with {} ops ({} / op) {}", + write, + self.write_ops, + mean, + self.file, + ); + } + } +}