├── .gitignore ├── .travis.yml ├── Cargo.toml ├── README.md ├── examples ├── macro.rs ├── real_world_scenario.rs └── simple.rs ├── src └── lib.rs └── tests └── profile.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | profile.json 4 | **.json 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | rust: 4 | - nightly 5 | - beta 6 | - stable 7 | script: 8 | - cargo test --all --verbose 9 | - cargo test --all --verbose --features thread_profiler 10 | - cargo run --example macro --verbose 11 | - cargo run --example simple --verbose 12 | - cargo run --example real_world_scenario --verbose --features thread_profiler 13 | - cargo build --all --verbose 14 | matrix: 15 | allow_failures: 16 | - rust: nightly 17 | fast_finish: true 18 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thread_profiler" 3 | version = "0.3.0" 4 | authors = ["Glenn Watson "] 5 | description = "A thread profiling library that outputs profiles in the chromium trace format." 6 | repository = "https://github.com/glennw/thread_profiler" 7 | documentation = "https://github.com/glennw/thread_profiler" 8 | homepage = "https://github.com/glennw/thread_profiler" 9 | keywords = ["profiler", "trace"] 10 | license = "Apache-2.0/MIT" 11 | 12 | [dependencies] 13 | time = { version = "0.1.36" } 14 | serde_json = { version = "1.0" } 15 | lazy_static = { version = "1" } 16 | 17 | 18 | [features] 19 | # Required for testing using the profile_scope!() macro 20 | thread_profiler = [] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/glennw/thread_profiler.svg)](https://travis-ci.org/glennw/thread_profiler) 2 | [![](http://meritbadge.herokuapp.com/thread_profiler)](https://crates.io/crates/thread_profiler) 3 | [![Documentation](https://docs.rs/thread_profiler/badge.svg)](https://docs.rs/thread_profiler) 4 | 5 | # Thread Profiler 6 | 7 | This is a simple CPU profiler for [WebRender](). It can write out the resutls in [Trace Event Format](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit). 8 | 9 | Read more about the usage and associated tools at https://aras-p.info/blog/2017/01/23/Chrome-Tracing-as-Profiler-Frontend/ 10 | 11 | ## Hookup 12 | 13 | Call `register_thread_with_profiler` for each thread. 14 | 15 | Call `write_profile` when you need to save the results. 16 | 17 | ## View results 18 | 19 | With Chrome: go to `chrome://tracing` and click on "Load". 20 | 21 | Standalone: check out and compile [catapult](https://github.com/catapult-project/catapult/tree/master/tracing), then call `trace2html`. 22 | -------------------------------------------------------------------------------- /examples/macro.rs: -------------------------------------------------------------------------------- 1 | /// A simple example showing how 2 | /// to use a thread_profiler 3 | /// to profile a long running function 4 | /// in a single thread. 5 | /// 6 | /// The outputted profile will be something like 7 | /// 8 | /// 'simple: covering function' 9 | /// | 10 | /// ____V________________________ 11 | /// |____________________________| 12 | /// |____________|_____________| 13 | /// ^ ^ 14 | /// | 'simple: section 2' 15 | /// 'simple: section_1' 16 | 17 | #[macro_use] 18 | extern crate thread_profiler; 19 | 20 | use std::vec::Vec; 21 | 22 | fn main() { 23 | println!( 24 | "Running with thread_profler. Names prepended by '{}'", 25 | module_path!() 26 | ); 27 | 28 | // Register this thread with the profiler. 29 | thread_profiler::register_thread_with_profiler(); 30 | long_complex_function(); 31 | // Write the profile to a file 32 | let output = "./profile.json"; 33 | println!("Writing output to {}", output); 34 | thread_profiler::write_profile(output); 35 | } 36 | 37 | fn long_complex_function() { 38 | // This will create a profile scope called 39 | // 'simple' as this is the name of the module. 40 | profile_scope!("covering function"); 41 | let mut v = Vec::new(); 42 | { 43 | // This will create a profile scope called 44 | // 'simple: section_1', i.e. the name of the 45 | // module with a comment describing the profile. 46 | 47 | profile_scope!("section 1"); 48 | // Do complex work 49 | v.push(1); 50 | // Drop is called on 51 | // 'simple: section_1' 52 | } 53 | { 54 | // This will create a profile scope called 55 | // 'simple: section_2' 56 | profile_scope!("section 2"); 57 | // Do complex work 58 | v.push(2); 59 | // Drop is called on 60 | // 'simple: section_2' 61 | } 62 | v.push(3) 63 | // Drop is called on 64 | // 'simple' 65 | } 66 | -------------------------------------------------------------------------------- /examples/real_world_scenario.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate thread_profiler; 3 | 4 | #[cfg(feature = "thread_profiler")] 5 | use thread_profiler::ProfileScope; 6 | 7 | use std::string::String; 8 | use std::thread; 9 | use std::vec::Vec; 10 | 11 | fn main() { 12 | if !cfg!(feature = "thread_profiler") { 13 | panic!("This example must be run with the 'thread_profiler' feature enabled"); 14 | } 15 | let workers = 10; 16 | let mut tasks = Vec::new(); 17 | for i in 0..workers { 18 | tasks.push(thread::spawn(move || { 19 | #[cfg(feature = "thread_profiler")] 20 | thread_profiler::register_thread_with_profiler(); 21 | 22 | // Here we use the profile_scope!() macro to profile this scope. 23 | // This simplifies everything by automatically feature gating 24 | // itself. 25 | // It also provides a nice naming format by specifying the module 26 | // path in the name of the profile. 27 | profile_scope!(format!("worker: thread_{}", i)); 28 | let result = { 29 | // Here we call ProfileScope::new() directly instead of using the macro 30 | // this gives us more control over the naming, however we have 31 | // to feature gate it ourself 32 | #[cfg(feature = "thread_profiler")] 33 | ProfileScope::new(format!("worker: thread_{}::calculation", i)); 34 | perform_complex_calculation() 35 | }; 36 | { 37 | #[cfg(feature = "thread_profiler")] 38 | ProfileScope::new(format!("worker: thread_{}::analyse", i)); 39 | if analyse_complex_result(result) { 40 | println!("Worker {} completed OK", i); 41 | } else { 42 | println!("Worker {} did not complete OK", i); 43 | } 44 | } 45 | })); 46 | } 47 | 48 | for task in tasks.drain(..) { 49 | match task.join() { 50 | Ok(_) => println!("Task completed OK"), 51 | _ => println!("Task did not complete!"), 52 | } 53 | } 54 | #[cfg(feature = "thread_profiler")] 55 | { 56 | let output_file = format!( 57 | "{}/{}", 58 | env!("CARGO_MANIFEST_DIR"), 59 | "real_world_scenario.profile.json" 60 | ); 61 | println!( 62 | "Writing profile to {}, try loading this using chome 'about:tracing'", 63 | output_file 64 | ); 65 | thread_profiler::write_profile(output_file.as_str()); 66 | } 67 | } 68 | 69 | fn perform_complex_calculation() -> String { 70 | "Hello I'm a worker result".to_string() 71 | } 72 | 73 | fn analyse_complex_result(data: String) -> bool { 74 | data == "Hello I'm a worker result" 75 | } 76 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | /// A simple example showing how 2 | /// to use a thread_profiler 3 | /// to profile a long running function 4 | /// in a single thread. 5 | /// 6 | /// The outputted profile will be something like 7 | /// 8 | /// 'simple: covering function' 9 | /// | 10 | /// ____V________________________ 11 | /// |____________________________| 12 | /// |____________|_____________| 13 | /// ^ ^ 14 | /// | 'simple: section 2' 15 | /// 'simple: section_1' 16 | extern crate thread_profiler; 17 | 18 | use thread_profiler::ProfileScope; 19 | 20 | use std::vec::Vec; 21 | 22 | fn main() { 23 | println!( 24 | "Running with thread_profler. Names prepended by '{}'", 25 | module_path!() 26 | ); 27 | 28 | // Register this thread with the profiler. 29 | thread_profiler::register_thread_with_profiler(); 30 | long_complex_function(); 31 | // Write the profile to a file 32 | let output = "./profile.json"; 33 | println!("Writing output to {}", output); 34 | thread_profiler::write_profile(output); 35 | } 36 | 37 | fn long_complex_function() { 38 | // This will create a profile scope called 39 | // 'simple' as this is the name of the module. 40 | ProfileScope::new(format!("{}: {}", module_path!(), "covering function")); 41 | let mut v = Vec::new(); 42 | { 43 | // This will create a profile scope called 44 | // 'simple: section_1', i.e. the name of the 45 | // module with a comment describing the profile. 46 | 47 | ProfileScope::new(format!("{}: {}", module_path!(), "section 1")); 48 | // Do complex work 49 | v.push(1); 50 | // Drop is called on 51 | // 'simple: section_1' 52 | } 53 | { 54 | // This will create a profile scope called 55 | // 'simple: section_2' 56 | ProfileScope::new(format!("{}: {}", module_path!(), "section 2")); 57 | // Do complex work 58 | v.push(2); 59 | // Drop is called on 60 | // 'simple: section_2' 61 | } 62 | v.push(3) 63 | // Drop is called on 64 | // 'simple' 65 | } 66 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate time; 2 | #[macro_use] 3 | extern crate serde_json; 4 | #[macro_use] 5 | extern crate lazy_static; 6 | 7 | use std::cell::RefCell; 8 | use std::fs::File; 9 | use std::io::BufWriter; 10 | use std::string::String; 11 | use std::sync::Mutex; 12 | use std::sync::mpsc::{channel, Receiver, Sender}; 13 | use std::thread; 14 | use time::precise_time_ns; 15 | 16 | lazy_static! { 17 | static ref GLOBAL_PROFILER: Mutex = Mutex::new(Profiler::new()); 18 | } 19 | 20 | #[macro_export] 21 | macro_rules! profile_scope { 22 | ($string:expr) => { 23 | #[cfg(feature = "thread_profiler")] 24 | let _profile_scope = 25 | $crate::ProfileScope::new(format!("{}: {}", module_path!(), $string)); 26 | }; 27 | } 28 | 29 | thread_local!(static THREAD_PROFILER: RefCell> = RefCell::new(None)); 30 | 31 | #[derive(Copy, Clone)] 32 | struct ThreadId(usize); 33 | 34 | struct ThreadInfo { 35 | name: String, 36 | } 37 | 38 | struct Sample { 39 | tid: ThreadId, 40 | name: String, 41 | t0: u64, 42 | t1: u64, 43 | } 44 | 45 | struct ThreadProfiler { 46 | id: ThreadId, 47 | tx: Sender, 48 | } 49 | 50 | impl ThreadProfiler { 51 | fn push_sample(&self, name: String, t0: u64, t1: u64) { 52 | let sample = Sample { 53 | tid: self.id, 54 | name: name, 55 | t0: t0, 56 | t1: t1, 57 | }; 58 | self.tx.send(sample).ok(); 59 | } 60 | } 61 | 62 | struct Profiler { 63 | rx: Receiver, 64 | tx: Sender, 65 | threads: Vec, 66 | } 67 | 68 | impl Profiler { 69 | fn new() -> Profiler { 70 | let (tx, rx) = channel(); 71 | 72 | Profiler { 73 | rx: rx, 74 | tx: tx, 75 | threads: Vec::new(), 76 | } 77 | } 78 | 79 | fn register_thread(&mut self) { 80 | let id = ThreadId(self.threads.len()); 81 | let name = match thread::current().name() { 82 | Some(s) => s.to_string(), 83 | None => format!("", id.0), 84 | }; 85 | 86 | self.threads.push(ThreadInfo { name }); 87 | 88 | THREAD_PROFILER.with(|profiler| { 89 | assert!(profiler.borrow().is_none()); 90 | 91 | let thread_profiler = ThreadProfiler { 92 | id: id, 93 | tx: self.tx.clone(), 94 | }; 95 | 96 | *profiler.borrow_mut() = Some(thread_profiler); 97 | }); 98 | } 99 | 100 | fn write_profile(&self, filename: &str) { 101 | // Stop reading samples that are written after 102 | // write_profile() is called. 103 | let start_time = precise_time_ns(); 104 | let mut data = Vec::new(); 105 | 106 | while let Ok(sample) = self.rx.try_recv() { 107 | if sample.t0 > start_time { 108 | break; 109 | } 110 | 111 | let thread_id = self.threads[sample.tid.0].name.as_str(); 112 | let t0 = sample.t0 / 1000; 113 | let t1 = sample.t1 / 1000; 114 | 115 | data.push(json!({ 116 | "pid": 0, 117 | "tid": thread_id, 118 | "name": sample.name, 119 | "ph": "B", 120 | "ts": t0 121 | })); 122 | 123 | data.push(json!({ 124 | "pid": 0, 125 | "tid": thread_id, 126 | "ph": "E", 127 | "ts": t1 128 | })); 129 | } 130 | 131 | let f = BufWriter::new(File::create(filename).unwrap()); 132 | serde_json::to_writer(f, &data).unwrap(); 133 | } 134 | } 135 | 136 | #[doc(hidden)] 137 | pub struct ProfileScope { 138 | name: String, 139 | t0: u64, 140 | } 141 | 142 | impl ProfileScope { 143 | pub fn new(name: String) -> ProfileScope { 144 | let t0 = precise_time_ns(); 145 | ProfileScope { name: name, t0: t0 } 146 | } 147 | } 148 | 149 | impl Drop for ProfileScope { 150 | /// When the ProfileScope is dropped it records the 151 | /// length of time it was alive for and records it 152 | /// against the Profiler. 153 | fn drop(&mut self) { 154 | let t1 = precise_time_ns(); 155 | 156 | THREAD_PROFILER.with(|profiler| match *profiler.borrow() { 157 | Some(ref profiler) => { 158 | profiler.push_sample(self.name.clone(), self.t0, t1); 159 | } 160 | None => { 161 | println!("ERROR: ProfileScope {} on unregistered thread!", self.name); 162 | } 163 | }); 164 | } 165 | } 166 | 167 | /// Writes the global profile to a specific file. 168 | pub fn write_profile(filename: &str) { 169 | GLOBAL_PROFILER.lock().unwrap().write_profile(filename); 170 | } 171 | 172 | /// Registers the current thread with the global profiler. 173 | pub fn register_thread_with_profiler() { 174 | GLOBAL_PROFILER.lock().unwrap().register_thread(); 175 | } 176 | -------------------------------------------------------------------------------- /tests/profile.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate thread_profiler; 3 | 4 | // Put this in a seperate module to check that the module expansion is working 5 | mod profile_tests { 6 | use std::fs::File; 7 | use std::io::prelude::Read; 8 | use std::string::String; 9 | use thread_profiler; 10 | 11 | #[test] 12 | fn test_profile_macro() { 13 | let output_file = format!( 14 | "{}/{}", 15 | env!("CARGO_MANIFEST_DIR"), 16 | "integration_test_macro_output.json" 17 | ); 18 | thread_profiler::register_thread_with_profiler(); 19 | { 20 | profile_scope!("MyTestProfile"); 21 | } 22 | thread_profiler::write_profile(&output_file); 23 | 24 | // Get the profile that we wrote 25 | let mut f = File::open(output_file).unwrap(); 26 | let mut buffer = String::new(); 27 | f.read_to_string(&mut buffer).unwrap(); 28 | 29 | // Test that the correct name has been used for the profile scope 30 | // but only if we have the feature enabled# 31 | let test = buffer.contains("profile_tests: MyTestProfile"); 32 | if cfg!(feature = "thread_profiler") { 33 | assert!( 34 | test, 35 | "Integration test macro did not contain the correct profile name" 36 | ); 37 | } else { 38 | assert!( 39 | !test, 40 | "Integration test macro incorrectly contained the profile name" 41 | ); 42 | } 43 | } 44 | } 45 | --------------------------------------------------------------------------------