Skip to content

Commit 29e046f

Browse files
committed
Add flamegraph profiling
1 parent 7b23a96 commit 29e046f

File tree

11 files changed

+125
-10
lines changed

11 files changed

+125
-10
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ __pycache__
99
.vscode
1010
wasm-pack.log
1111
.idea/
12-
tests/snippets/resources
12+
tests/snippets/resources
13+
flame-graph.html

Cargo.lock

+44
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ members = [".", "derive", "vm", "wasm/lib", "parser", "compiler", "bytecode"]
1414
name = "bench"
1515
path = "./benchmarks/bench.rs"
1616

17+
[features]
18+
default = []
19+
flame-it = ["rustpython-vm/flame-it", "flame"]
1720

1821
[dependencies]
1922
log="0.4.1"
@@ -25,5 +28,7 @@ rustpython-vm = {path = "vm", version = "0.1.0"}
2528
rustyline = "4.1.0"
2629
xdg = "2.2.0"
2730

31+
flame = { version = "0.2", optional = true }
32+
2833
[dev-dependencies.cpython]
2934
version = "0.2"

src/main.rs

+41-7
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,18 @@ use rustpython_vm::{
1919

2020
use rustyline::{error::ReadlineError, Editor};
2121
use std::path::PathBuf;
22+
use std::process;
2223

2324
fn main() {
25+
if let Err(err) = run() {
26+
error!("Error: {}", err);
27+
process::exit(1);
28+
}
29+
}
30+
31+
fn run() -> Result<(), Box<std::error::Error>> {
2432
env_logger::init();
25-
let matches = App::new("RustPython")
33+
let app = App::new("RustPython")
2634
.version(crate_version!())
2735
.author(crate_authors!())
2836
.about("Rust implementation of the Python language")
@@ -45,8 +53,18 @@ fn main() {
4553
.takes_value(true)
4654
.help("run library module as script"),
4755
)
48-
.arg(Arg::from_usage("[pyargs] 'args for python'").multiple(true))
49-
.get_matches();
56+
.arg(Arg::from_usage("[pyargs] 'args for python'").multiple(true));
57+
#[cfg(feature = "flame-it")]
58+
let app = app.arg(
59+
Arg::with_name("profile_output")
60+
.long("profile-output")
61+
.takes_value(true)
62+
.help(
63+
"the file to output the profile graph to. present due to being \
64+
built with feature 'flame-it'",
65+
),
66+
);
67+
let matches = app.get_matches();
5068

5169
// Construct vm:
5270
let vm = VirtualMachine::new();
@@ -69,6 +87,22 @@ fn main() {
6987

7088
// See if any exception leaked out:
7189
handle_exception(&vm, result);
90+
91+
#[cfg(feature = "flame-it")]
92+
{
93+
use std::fs::File;
94+
95+
let profile_output = matches
96+
.value_of_os("profile_output")
97+
.unwrap_or_else(|| "flame-graph.html".as_ref());
98+
if profile_output == "-" {
99+
flame::dump_stdout();
100+
} else {
101+
flame::dump_html(&mut File::create(profile_output)?)?;
102+
}
103+
}
104+
105+
Ok(())
72106
}
73107

74108
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
84118
fn handle_exception(vm: &VirtualMachine, result: PyResult) {
85119
if let Err(err) = result {
86120
print_exception(vm, &err);
87-
std::process::exit(1);
121+
process::exit(1);
88122
}
89123
}
90124

@@ -116,14 +150,14 @@ fn run_script(vm: &VirtualMachine, script_file: &str) -> PyResult {
116150
"can't find '__main__' module in '{}'",
117151
file_path.to_str().unwrap()
118152
);
119-
std::process::exit(1);
153+
process::exit(1);
120154
}
121155
} else {
122156
error!(
123157
"can't open file '{}': No such file or directory",
124158
file_path.to_str().unwrap()
125159
);
126-
std::process::exit(1);
160+
process::exit(1);
127161
};
128162

129163
let dir = file_path.parent().unwrap().to_str().unwrap().to_string();
@@ -138,7 +172,7 @@ fn run_script(vm: &VirtualMachine, script_file: &str) -> PyResult {
138172
file_path.to_str().unwrap(),
139173
err.kind()
140174
);
141-
std::process::exit(1);
175+
process::exit(1);
142176
}
143177
}
144178
}

vm/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ include = ["src/**/*.rs", "Cargo.toml", "build.rs", "Lib/**/*.py"]
1111
[features]
1212
default = ["rustpython-parser", "rustpython-compiler"]
1313
vm-tracing-logging = []
14+
flame-it = ["flame", "flamer"]
1415

1516
[dependencies]
1617
# Crypto:
@@ -57,5 +58,8 @@ maplit = "1.0"
5758
proc-macro-hack = "0.5"
5859
bitflags = "1.1"
5960

61+
flame = { version = "0.2", optional = true }
62+
flamer = { version = "0.3", optional = true }
63+
6064
[target.'cfg(all(unix, not(target_os = "android")))'.dependencies]
6165
pwd = "1"

vm/src/frame.rs

+13
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ impl Frame {
268268
}
269269
}
270270

271+
#[cfg_attr(feature = "flame-it", flame("Frame"))]
271272
pub fn run(&self, vm: &VirtualMachine) -> Result<ExecutionResult, PyObjectRef> {
272273
let filename = &self.code.source_path.to_string();
273274

@@ -334,6 +335,7 @@ impl Frame {
334335

335336
/// Execute a single instruction.
336337
#[allow(clippy::cognitive_complexity)]
338+
#[cfg_attr(feature = "flame-it", flame("Frame"))]
337339
fn execute_instruction(&self, vm: &VirtualMachine) -> FrameResult {
338340
let instruction = self.fetch_instruction();
339341

@@ -891,6 +893,7 @@ impl Frame {
891893
}
892894
}
893895

896+
#[cfg_attr(feature = "flame-it", flame("Frame"))]
894897
fn get_elements(
895898
&self,
896899
vm: &VirtualMachine,
@@ -912,6 +915,7 @@ impl Frame {
912915
}
913916
}
914917

918+
#[cfg_attr(feature = "flame-it", flame("Frame"))]
915919
fn import(
916920
&self,
917921
vm: &VirtualMachine,
@@ -938,6 +942,7 @@ impl Frame {
938942
Ok(None)
939943
}
940944

945+
#[cfg_attr(feature = "flame-it", flame("Frame"))]
941946
fn import_star(&self, vm: &VirtualMachine, module: &str, level: usize) -> FrameResult {
942947
let module = vm.import(module, &vm.ctx.new_tuple(vec![]), level)?;
943948

@@ -951,6 +956,7 @@ impl Frame {
951956
}
952957

953958
// Unwind all blocks:
959+
#[cfg_attr(feature = "flame-it", flame("Frame"))]
954960
fn unwind_blocks(&self, vm: &VirtualMachine) -> Option<PyObjectRef> {
955961
while let Some(block) = self.pop_block() {
956962
match block.typ {
@@ -978,6 +984,7 @@ impl Frame {
978984
None
979985
}
980986

987+
#[cfg_attr(feature = "flame-it", flame("Frame"))]
981988
fn unwind_loop(&self, vm: &VirtualMachine) -> Block {
982989
loop {
983990
let block = self.current_block().expect("not in a loop");
@@ -1003,6 +1010,7 @@ impl Frame {
10031010
}
10041011
}
10051012

1013+
#[cfg_attr(feature = "flame-it", flame("Frame"))]
10061014
fn unwind_exception(&self, vm: &VirtualMachine, exc: PyObjectRef) -> Option<PyObjectRef> {
10071015
// unwind block stack on exception and find any handlers:
10081016
while let Some(block) = self.pop_block() {
@@ -1105,6 +1113,7 @@ impl Frame {
11051113
}
11061114
}
11071115

1116+
#[cfg_attr(feature = "flame-it", flame("Frame"))]
11081117
fn load_name(
11091118
&self,
11101119
vm: &VirtualMachine,
@@ -1150,6 +1159,7 @@ impl Frame {
11501159
*self.lasti.borrow_mut() = target_pc;
11511160
}
11521161

1162+
#[cfg_attr(feature = "flame-it", flame("Frame"))]
11531163
fn execute_binop(
11541164
&self,
11551165
vm: &VirtualMachine,
@@ -1194,6 +1204,7 @@ impl Frame {
11941204
Ok(None)
11951205
}
11961206

1207+
#[cfg_attr(feature = "flame-it", flame("Frame"))]
11971208
fn execute_unop(&self, vm: &VirtualMachine, op: &bytecode::UnaryOperator) -> FrameResult {
11981209
let a = self.pop_value();
11991210
let value = match *op {
@@ -1234,6 +1245,7 @@ impl Frame {
12341245
Ok(result)
12351246
}
12361247

1248+
#[cfg_attr(feature = "flame-it", flame("Frame"))]
12371249
fn execute_compare(
12381250
&self,
12391251
vm: &VirtualMachine,
@@ -1326,6 +1338,7 @@ impl Frame {
13261338
stack[stack.len() - depth - 1].clone()
13271339
}
13281340

1341+
#[cfg_attr(feature = "flame-it", flame("Frame"))]
13291342
fn get_exception(&self, vm: &VirtualMachine, none_allowed: bool) -> PyResult {
13301343
let exception = self.pop_value();
13311344
if none_allowed && vm.get_none().is(&exception)

vm/src/import.rs

+2
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ use crate::vm::VirtualMachine;
1111
use rustpython_compiler::compile;
1212

1313
pub fn init_importlib(vm: &VirtualMachine, external: bool) -> PyResult {
14+
flame_guard!("init importlib");
1415
let importlib = import_frozen(vm, "_frozen_importlib")?;
1516
let impmod = import_builtin(vm, "_imp")?;
1617
let install = vm.get_attribute(importlib.clone(), "_install")?;
1718
vm.invoke(install, vec![vm.sys_module.clone(), impmod])?;
1819
vm.import_func
1920
.replace(vm.get_attribute(importlib.clone(), "__import__")?);
2021
if external && cfg!(feature = "rustpython-compiler") {
22+
flame_guard!("install_external");
2123
let install_external =
2224
vm.get_attribute(importlib.clone(), "_install_external_importers")?;
2325
vm.invoke(install_external, vec![])?;

vm/src/lib.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
clippy::implicit_hasher
1313
)]
1414

15+
#[cfg(feature = "flame-it")]
16+
#[macro_use]
17+
extern crate flamer;
18+
1519
#[macro_use]
1620
extern crate bitflags;
1721
#[macro_use]
@@ -30,8 +34,7 @@ extern crate self as rustpython_vm;
3034

3135
pub use rustpython_derive::*;
3236

33-
use proc_macro_hack::proc_macro_hack;
34-
#[proc_macro_hack]
37+
#[proc_macro_hack::proc_macro_hack]
3538
pub use rustpython_derive::py_compile_bytecode;
3639

3740
//extern crate eval; use eval::eval::*;

vm/src/macros.rs

+7
Original file line numberDiff line numberDiff line change
@@ -266,3 +266,10 @@ macro_rules! vm_trace {
266266
trace!($($arg)+);
267267
}
268268
}
269+
270+
macro_rules! flame_guard {
271+
($name:expr) => {
272+
#[cfg(feature = "flame-it")]
273+
let _guard = ::flame::start_guard($name);
274+
};
275+
}

0 commit comments

Comments
 (0)