Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP #53

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open

WIP #53

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
334 changes: 226 additions & 108 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cargo-profiler"
version = "0.2.0"
version = "1.1.0"
authors = ["pegasos1", "Sven-Hendrik Haase <svenstaro@gmail.com>"]
description = "Cargo subcommand to profile your applications."
homepage = "http://github.com/svenstaro/cargo-profiler"
Expand All @@ -16,3 +16,7 @@ ndarray = "0.13"
lazy_static = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
structopt = "0.3"
anyhow = "1"
strum = "0.16"
strum_macros = "0.16"
14 changes: 0 additions & 14 deletions src/argparse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,6 @@ pub fn get_binary<'a>(matches: &'a ArgMatches) -> Result<&'a str, ProfError> {
}
}

/// parse the number argument into a usize
pub fn get_num(matches: &ArgMatches) -> Result<usize, ProfError> {
match matches.value_of("n").map(|x| x.parse::<usize>()) {
Some(Ok(z)) => Ok(z),
Some(Err(_)) => Err(ProfError::InvalidNum),
None => Ok(10000), // some arbitrarily large number...
}
}

/// get the cachegrind metric user wants to sort on
pub fn get_sort_metric(matches: &ArgMatches) -> Result<Metric, ProfError> {
match matches.value_of("sort") {
Expand Down Expand Up @@ -73,11 +64,6 @@ mod test {
assert_eq!(1, 1);
}

#[test]
fn test_get_num() {
assert_eq!(1, 1);
}

#[test]
fn test_get_sort_metric() {
assert_eq!(1, 1);
Expand Down
68 changes: 68 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use structopt::StructOpt;
use crate::parse::cachegrind::Metric;

#[derive(Debug, Clone, StructOpt)]
#[structopt(
name = "cargo-profiler",
author,
about,
global_settings = &[structopt::clap::AppSettings::ColoredHelp]
)]
pub enum CargoProfilerConfig {
Profiler {
#[structopt(subcommand)]
profiler_type: ProfilerType,
}
}

#[derive(Debug, Clone, StructOpt)]
pub enum ProfilerType {
/// Run callgrind
Callgrind {
/// Binary you want to profile
#[structopt(name = "BIN", long)]
binary_name: String,

/// Arguments for the binary
#[structopt(name = "ARG")]
binary_args: Vec<String>,

/// Build binary in release mode
#[structopt(long)]
release: bool,

/// Number of functions you want
#[structopt(short, default_value = "10000")]
n_functions: u16,

/// Keep profiler output files
#[structopt(short, long)]
keep: bool,
},
/// Run cachegrind
Cachegrind {
/// Binary you want to profile
#[structopt(name = "BIN", long)]
binary_name: String,

/// Arguments for the binary
#[structopt(name = "ARG")]
binary_args: Vec<String>,

/// Build binary in release mode
#[structopt(long)]
release: bool,

/// Number of functions you want
#[structopt(short, default_value = "10000")]
n_functions: u16,

/// Metric you want to sort by
#[structopt(short, long, default_value = "NAN")]
sort: Metric,

/// Keep profiler output files
#[structopt(short, long)]
keep: bool,
},
}
4 changes: 2 additions & 2 deletions src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ fn fmt_thousands_sep(n: f64, sep: char) -> String {
impl fmt::Display for Profiler {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Profiler::CacheGrind {
Profiler::Cachegrind {
ref ir,
ref i1mr,
ref ilmr,
Expand Down Expand Up @@ -100,7 +100,7 @@ impl fmt::Display for Profiler {
Ok(())
}

Profiler::CallGrind {
Profiler::Callgrind {
ref total_instructions,
ref instructions,
ref functs,
Expand Down
218 changes: 66 additions & 152 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,178 +1,92 @@
pub mod argparse;
pub mod args;
pub mod cargo;
pub mod display;
pub mod err;
pub mod parse;
pub mod profiler;

use crate::argparse::{get_binary, get_num, get_profiler, get_sort_metric};
use crate::argparse::{get_binary, get_profiler, get_sort_metric};
use crate::cargo::build_binary;
use crate::err::ProfError;
use crate::parse::cachegrind::CacheGrindParser;
use crate::parse::callgrind::CallGrindParser;
use crate::profiler::Profiler;
use crate::parse::cachegrind::CachegrindParser;
use crate::parse::callgrind::CallgrindParser;
use crate::args::ProfilerType;
use clap::{App, AppSettings, Arg, SubCommand};
use std::ffi::OsStr;
use std::process;
use std::process::Command;

// macro to try something, but print custom error message and exit upon error.
macro_rules! try_or_exit {
($e:expr) => {
match $e {
Ok(e) => e,
Err(e) => {
println!("{}", e);
process::exit(1);
}
}
};
}

fn main() {
let _ = real_main();
}

// #[cfg(all(unix, any(target_os = "linux", target_os = "macos")))]
#[cfg(unix)]
fn real_main() -> Result<(), ProfError> {
// create binary path argument
let binary_arg = Arg::with_name("binary")
.long("bin")
.value_name("BINARY")
.required(false)
.help("binary you want to profile");

// create binary arguments positional args (aka, everything after a '--')
let binargs_arg = Arg::with_name("binargs")
.multiple(true)
.value_name("BIN_ARGS")
.required(false)
.help("arguments to the binary when executed");

// create release argument
let release = Arg::with_name("release")
.long("release")
.required(false)
.help("whether binary should be built in release mode");

// create function count argument
let fn_count_arg = Arg::with_name("n")
.short("n")
.value_name("NUMBER")
.takes_value(true)
.help("number of functions you want");

// create sort metric argument
let sort_arg = Arg::with_name("sort")
.long("sort")
.value_name("SORT")
.takes_value(true)
.help("metric you want to sort by");

// keep output files
let keep_arg = Arg::with_name("keep")
.long("keep")
.required(false)
.help("keep profiler output files");


// create callgrind subcommand
let callgrind = SubCommand::with_name("callgrind")
.about("gets callgrind features")
.version("1.0")
.author("Suchin Gururangan")
.arg(release.clone())
.arg(binary_arg.clone())
.arg(binargs_arg.clone())
.arg(fn_count_arg.clone())
.arg(keep_arg.clone());

// create cachegrind subcommand
let cachegrind = SubCommand::with_name("cachegrind")
.about("gets cachegrind features")
.version("1.0")
.author("Suchin Gururangan")
.arg(release)
.arg(binary_arg)
.arg(binargs_arg.clone())
.arg(fn_count_arg)
.arg(sort_arg)
.arg(keep_arg);

// create profiler subcommand
let profiler = SubCommand::with_name("profiler")
.about("gets callgrind features")
.version("1.0")
.author("Suchin Gururangan")
.subcommand(callgrind)
.subcommand(cachegrind);

// create profiler application
let matches = App::new("cargo-profiler")
.bin_name("cargo")
.settings(&[AppSettings::SubcommandRequired])
.version("1.0")
.author("Suchin Gururangan")
.about("Profile your binaries")
.subcommand(profiler)
.get_matches();

// parse arguments from cli call
let (m, profiler) = try_or_exit!(get_profiler(&matches));
let binary = {
if m.is_present("binary") {
try_or_exit!(get_binary(&m)).to_string()
} else if m.is_present("release") {
try_or_exit!(build_binary(true))
} else {
try_or_exit!(build_binary(false))
}
use structopt::StructOpt;
use anyhow::Result;

fn main() -> Result<()> {
let args = args::CargoProfilerConfig::from_args();

// The way cargo extension programs are meant to be written requires us to always have a
// variant here that is the only one that'll ever be usd.
let profiler_type = if let args::CargoProfilerConfig::Profiler { profiler_type } = args {
profiler_type
} else {
unreachable!();
};

let binary_name = binary.split('/').collect::<Vec<&str>>().pop().unwrap_or("");
let binargs: Vec<&OsStr> = match m.values_of_os("binargs") {
None => vec![],
Some(raw) => raw.collect(),
};

let num = try_or_exit!(get_num(&m));
let sort_metric = try_or_exit!(get_sort_metric(&m));

match profiler {
Profiler::CallGrind { .. } => println!(
match profiler_type {
ProfilerType::Callgrind { binary_name, .. } => println!(
"\n\x1b[1;33mProfiling \x1b[1;0m{} \x1b[0mwith callgrind\x1b[0m...",
binary_name
),
Profiler::CacheGrind { .. } => println!(
ProfilerType::Cachegrind { binary_name, .. } => println!(
"\n\x1b[1;33mProfiling \x1b[1;0m{} \x1b[0mwith cachegrind\x1b[0m...",
binary_name
),
};

// get the profiler output
let output = match profiler {
Profiler::CallGrind { .. } => profiler.callgrind_cli(&binary, &binargs)?,
Profiler::CacheGrind { .. } => profiler.cachegrind_cli(&binary, &binargs)?,
};

// parse the output into struct
let parsed = match profiler {
Profiler::CallGrind { .. } => try_or_exit!(profiler.callgrind_parse(&output, num)),
Profiler::CacheGrind { .. } => {
try_or_exit!(profiler.cachegrind_parse(&output, num, sort_metric))
}
};

// pretty-print
println!("{}", parsed);

if !m.is_present("keep") {
// remove files generated while profiling
Command::new("rm").arg("cachegrind.out").output()?;

Command::new("rm").arg("callgrind.out").output()?;
}
// // parse arguments from cli call
// let (m, profiler) = try_or_exit!(get_profiler(&matches));
// let binary = {
// if m.is_present("binary") {
// try_or_exit!(get_binary(&m)).to_string()
// } else if m.is_present("release") {
// try_or_exit!(build_binary(true))
// } else {
// try_or_exit!(build_binary(false))
// }
// };
//
// let binary_name = binary.split('/').collect::<Vec<&str>>().pop().unwrap_or("");
// let binargs: Vec<&OsStr> = match m.values_of_os("binargs") {
// None => vec![],
// Some(raw) => raw.collect(),
// };
//
// let num = try_or_exit!(get_num(&m));
// let sort_metric = try_or_exit!(get_sort_metric(&m));
//
//
// // get the profiler output
// let output = match profiler {
// Profiler::CallGrind { .. } => profiler.callgrind_cli(&binary, &binargs)?,
// Profiler::CacheGrind { .. } => profiler.cachegrind_cli(&binary, &binargs)?,
// };
//
// // parse the output into struct
// let parsed = match profiler {
// Profiler::CallGrind { .. } => try_or_exit!(profiler.callgrind_parse(&output, num)),
// Profiler::CacheGrind { .. } => {
// try_or_exit!(profiler.cachegrind_parse(&output, num, sort_metric))
// }
// };
//
//
//
// // pretty-print
// println!("{}", parsed);
//
// if !args.keep {
// // remove files generated while profiling
// Command::new("rm").arg("cachegrind.out").output()?;
// Command::new("rm").arg("callgrind.out").output()?;
// }

Ok(())
}
Loading