diff --git a/.gitignore b/.gitignore index e9dd6d220a..6fd8d56f91 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ __pycache__ .vscode wasm-pack.log .idea/ -tests/snippets/resources \ No newline at end of file +tests/snippets/resources +flame-graph.html diff --git a/Cargo.lock b/Cargo.lock index 4a68ab8987..d040acd56f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -346,6 +346,28 @@ name = "fixedbitset" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "flame" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "thread-id 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "flamer" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "flame 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "fnv" version = "1.0.6" @@ -484,6 +506,11 @@ name = "lalrpop-util" version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "lazy_static" version = "1.3.0" @@ -956,6 +983,7 @@ dependencies = [ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "cpython 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", + "flame 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "rustpython-compiler 0.1.0", "rustpython-parser 0.1.0", @@ -1023,6 +1051,8 @@ dependencies = [ "caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "flame 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "flamer 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "hexf 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1347,6 +1377,16 @@ dependencies = [ "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "thread-id" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "thread_local" version = "0.3.6" @@ -1903,6 +1943,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" "checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" +"checksum flame 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1fc2706461e1ee94f55cab2ed2e3d34ae9536cfa830358ef80acff1a3dacab30" +"checksum flamer 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f2add1a5e84b1ed7b5d00cdc21789a28e0a8f4e427b677313c773880ba3c4dac" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)" = "a2037ec1c6c1c4f79557762eab1f7eae1f64f6cb418ace90fae88f0942b60139" @@ -1921,6 +1963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lalrpop 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4e2e80bee40b22bca46665b4ef1f3cd88ed0fb043c971407eac17a0712c02572" "checksum lalrpop-util 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)" = "33b27d8490dbe1f9704b0088d61e8d46edc10d5673a8829836c6ded26a9912c7" +"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum lexical 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c106ed999697325a540c43d66a8d5175668cd96d7eb0bdba03a3bd98256cd698" "checksum lexical-core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e82e023e062f1d25f807ad182008fba1b46538e999f908a08cc0c29e084462e" @@ -2010,6 +2053,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" "checksum termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dde0593aeb8d47accea5392b39350015b5eccb12c0d98044d856983d89548dea" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum thread-id 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" diff --git a/Cargo.toml b/Cargo.toml index 681f665283..01ed413a42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,9 @@ members = [".", "derive", "vm", "wasm/lib", "parser", "compiler", "bytecode"] name = "bench" path = "./benchmarks/bench.rs" +[features] +default = [] +flame-it = ["rustpython-vm/flame-it", "flame"] [dependencies] log="0.4.1" @@ -25,5 +28,7 @@ rustpython-vm = {path = "vm", version = "0.1.0"} rustyline = "4.1.0" xdg = "2.2.0" +flame = { version = "0.2", optional = true } + [dev-dependencies.cpython] version = "0.2" diff --git a/src/main.rs b/src/main.rs index c9af77e491..f9aab60417 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,10 +19,18 @@ use rustpython_vm::{ use rustyline::{error::ReadlineError, Editor}; use std::path::PathBuf; +use std::process; fn main() { + if let Err(err) = run() { + error!("Error: {}", err); + process::exit(1); + } +} + +fn run() -> Result<(), Box> { env_logger::init(); - let matches = App::new("RustPython") + let app = App::new("RustPython") .version(crate_version!()) .author(crate_authors!()) .about("Rust implementation of the Python language") @@ -45,8 +53,18 @@ fn main() { .takes_value(true) .help("run library module as script"), ) - .arg(Arg::from_usage("[pyargs] 'args for python'").multiple(true)) - .get_matches(); + .arg(Arg::from_usage("[pyargs] 'args for python'").multiple(true)); + #[cfg(feature = "flame-it")] + let app = app.arg( + Arg::with_name("profile_output") + .long("profile-output") + .takes_value(true) + .help( + "the file to output the profile graph to. present due to being \ + built with feature 'flame-it'", + ), + ); + let matches = app.get_matches(); // Construct vm: let vm = VirtualMachine::new(); @@ -69,6 +87,22 @@ fn main() { // See if any exception leaked out: handle_exception(&vm, result); + + #[cfg(feature = "flame-it")] + { + use std::fs::File; + + let profile_output = matches + .value_of_os("profile_output") + .unwrap_or_else(|| "flame-graph.html".as_ref()); + if profile_output == "-" { + flame::dump_stdout(); + } else { + flame::dump_html(&mut File::create(profile_output)?)?; + } + } + + Ok(()) } fn _run_string(vm: &VirtualMachine, source: &str, source_path: String) -> PyResult { @@ -84,7 +118,7 @@ fn _run_string(vm: &VirtualMachine, source: &str, source_path: String) -> PyResu fn handle_exception(vm: &VirtualMachine, result: PyResult) { if let Err(err) = result { print_exception(vm, &err); - std::process::exit(1); + process::exit(1); } } @@ -116,14 +150,14 @@ fn run_script(vm: &VirtualMachine, script_file: &str) -> PyResult { "can't find '__main__' module in '{}'", file_path.to_str().unwrap() ); - std::process::exit(1); + process::exit(1); } } else { error!( "can't open file '{}': No such file or directory", file_path.to_str().unwrap() ); - std::process::exit(1); + process::exit(1); }; let dir = file_path.parent().unwrap().to_str().unwrap().to_string(); @@ -138,7 +172,7 @@ fn run_script(vm: &VirtualMachine, script_file: &str) -> PyResult { file_path.to_str().unwrap(), err.kind() ); - std::process::exit(1); + process::exit(1); } } } diff --git a/vm/Cargo.toml b/vm/Cargo.toml index e53ff4d28e..3c4ec4da57 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -11,6 +11,7 @@ include = ["src/**/*.rs", "Cargo.toml", "build.rs", "Lib/**/*.py"] [features] default = ["rustpython-parser", "rustpython-compiler"] vm-tracing-logging = [] +flame-it = ["flame", "flamer"] [dependencies] # Crypto: @@ -57,5 +58,8 @@ maplit = "1.0" proc-macro-hack = "0.5" bitflags = "1.1" +flame = { version = "0.2", optional = true } +flamer = { version = "0.3", optional = true } + [target.'cfg(all(unix, not(target_os = "android")))'.dependencies] pwd = "1" diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 2c45164e88..27df11114a 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -268,7 +268,15 @@ impl Frame { } } + // #[cfg_attr(feature = "flame-it", flame("Frame"))] pub fn run(&self, vm: &VirtualMachine) -> Result { + flame_guard!(format!("Frame::run({})", self.code.obj_name)); + // flame doesn't include notes in the html graph :( + flame_note!( + flame::StrCow::from("CodeObj name"), + self.code.obj_name.clone().into() + ); + let filename = &self.code.source_path.to_string(); // This is the name of the object being run: @@ -334,6 +342,7 @@ impl Frame { /// Execute a single instruction. #[allow(clippy::cognitive_complexity)] + #[cfg_attr(feature = "flame-it", flame("Frame"))] fn execute_instruction(&self, vm: &VirtualMachine) -> FrameResult { let instruction = self.fetch_instruction(); @@ -891,6 +900,7 @@ impl Frame { } } + #[cfg_attr(feature = "flame-it", flame("Frame"))] fn get_elements( &self, vm: &VirtualMachine, @@ -912,6 +922,7 @@ impl Frame { } } + #[cfg_attr(feature = "flame-it", flame("Frame"))] fn import( &self, vm: &VirtualMachine, @@ -938,6 +949,7 @@ impl Frame { Ok(None) } + #[cfg_attr(feature = "flame-it", flame("Frame"))] fn import_star(&self, vm: &VirtualMachine, module: &str, level: usize) -> FrameResult { let module = vm.import(module, &vm.ctx.new_tuple(vec![]), level)?; @@ -951,6 +963,7 @@ impl Frame { } // Unwind all blocks: + #[cfg_attr(feature = "flame-it", flame("Frame"))] fn unwind_blocks(&self, vm: &VirtualMachine) -> Option { while let Some(block) = self.pop_block() { match block.typ { @@ -978,6 +991,7 @@ impl Frame { None } + #[cfg_attr(feature = "flame-it", flame("Frame"))] fn unwind_loop(&self, vm: &VirtualMachine) -> Block { loop { let block = self.current_block().expect("not in a loop"); @@ -1003,6 +1017,7 @@ impl Frame { } } + #[cfg_attr(feature = "flame-it", flame("Frame"))] fn unwind_exception(&self, vm: &VirtualMachine, exc: PyObjectRef) -> Option { // unwind block stack on exception and find any handlers: while let Some(block) = self.pop_block() { @@ -1105,6 +1120,7 @@ impl Frame { } } + #[cfg_attr(feature = "flame-it", flame("Frame"))] fn load_name( &self, vm: &VirtualMachine, @@ -1150,6 +1166,7 @@ impl Frame { *self.lasti.borrow_mut() = target_pc; } + #[cfg_attr(feature = "flame-it", flame("Frame"))] fn execute_binop( &self, vm: &VirtualMachine, @@ -1194,6 +1211,7 @@ impl Frame { Ok(None) } + #[cfg_attr(feature = "flame-it", flame("Frame"))] fn execute_unop(&self, vm: &VirtualMachine, op: &bytecode::UnaryOperator) -> FrameResult { let a = self.pop_value(); let value = match *op { @@ -1234,6 +1252,7 @@ impl Frame { Ok(result) } + #[cfg_attr(feature = "flame-it", flame("Frame"))] fn execute_compare( &self, vm: &VirtualMachine, @@ -1326,6 +1345,7 @@ impl Frame { stack[stack.len() - depth - 1].clone() } + #[cfg_attr(feature = "flame-it", flame("Frame"))] fn get_exception(&self, vm: &VirtualMachine, none_allowed: bool) -> PyResult { let exception = self.pop_value(); if none_allowed && vm.get_none().is(&exception) diff --git a/vm/src/import.rs b/vm/src/import.rs index d6e3847db4..2b231e5936 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -11,6 +11,7 @@ use crate::vm::VirtualMachine; use rustpython_compiler::compile; pub fn init_importlib(vm: &VirtualMachine, external: bool) -> PyResult { + flame_guard!("init importlib"); let importlib = import_frozen(vm, "_frozen_importlib")?; let impmod = import_builtin(vm, "_imp")?; let install = vm.get_attribute(importlib.clone(), "_install")?; @@ -18,6 +19,7 @@ pub fn init_importlib(vm: &VirtualMachine, external: bool) -> PyResult { vm.import_func .replace(vm.get_attribute(importlib.clone(), "__import__")?); if external && cfg!(feature = "rustpython-compiler") { + flame_guard!("install_external"); let install_external = vm.get_attribute(importlib.clone(), "_install_external_importers")?; vm.invoke(install_external, vec![])?; diff --git a/vm/src/lib.rs b/vm/src/lib.rs index f520ea254d..37e2334471 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -12,6 +12,10 @@ clippy::implicit_hasher )] +#[cfg(feature = "flame-it")] +#[macro_use] +extern crate flamer; + #[macro_use] extern crate bitflags; #[macro_use] @@ -30,8 +34,7 @@ extern crate self as rustpython_vm; pub use rustpython_derive::*; -use proc_macro_hack::proc_macro_hack; -#[proc_macro_hack] +#[proc_macro_hack::proc_macro_hack] pub use rustpython_derive::py_compile_bytecode; //extern crate eval; use eval::eval::*; diff --git a/vm/src/macros.rs b/vm/src/macros.rs index 617729674f..d12e41a7cd 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -266,3 +266,21 @@ macro_rules! vm_trace { trace!($($arg)+); } } + +macro_rules! flame_guard { + ($name:expr) => { + #[cfg(feature = "flame-it")] + let _guard = ::flame::start_guard($name); + }; +} + +macro_rules! flame_note { + ($name:expr, $desc:expr$(,)?) => { + #[cfg(feature = "flame-it")] + ::flame::note($name, ::std::option::Option::Some($desc)); + }; + ($name:expr$(,)?) => { + #[cfg(feature = "flame-it")] + ::flame::note($name, ::std::option::Option::None); + }; +} diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 82eb6e5287..ffb471f990 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -250,6 +250,7 @@ fn init_type_hierarchy() -> (PyClassRef, PyClassRef) { // Basic objects: impl PyContext { pub fn new() -> Self { + flame_guard!("init PyContext"); let (type_type, object_type) = init_type_hierarchy(); let dict_type = create_type("dict", &type_type, &object_type); diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 6e11dbd3d1..35107f876b 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -61,6 +61,7 @@ pub struct VirtualMachine { impl VirtualMachine { /// Create a new `VirtualMachine` structure. pub fn new() -> VirtualMachine { + flame_guard!("init VirtualMachine"); let ctx = PyContext::new(); // Hard-core modules: