Reimplemented profiler to use tracing.
This commit is contained in:
parent
5101fd74e6
commit
3026c13e44
|
@ -1,7 +1,4 @@
|
||||||
{
|
{
|
||||||
"rust.features": [
|
|
||||||
"app_profiler"
|
|
||||||
],
|
|
||||||
"spellright.language": [
|
"spellright.language": [
|
||||||
"en"
|
"en"
|
||||||
],
|
],
|
||||||
|
|
|
@ -10,13 +10,6 @@ readme = "README.md"
|
||||||
path = "zero-ui/lib.rs"
|
path = "zero-ui/lib.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# Enable the zero_ui::core::profiler module.
|
|
||||||
#
|
|
||||||
# USAGE:
|
|
||||||
#
|
|
||||||
# `#[cfg(feature = "app_profiler")` or just use the `profile_scope!` macro.
|
|
||||||
app_profiler = ["zero-ui-core/app_profiler"]
|
|
||||||
|
|
||||||
# # Other Features
|
# # Other Features
|
||||||
#
|
#
|
||||||
# ## `doc_nightly`
|
# ## `doc_nightly`
|
||||||
|
|
|
@ -4,11 +4,14 @@ use zero_ui::prelude::*;
|
||||||
use zero_ui_view_prebuilt as zero_ui_view;
|
use zero_ui_view_prebuilt as zero_ui_view;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
examples_util::print_info();
|
//examples_util::print_info();
|
||||||
|
let rec = examples_util::record_profile("profile-gradient.json");
|
||||||
// zero_ui_view::run_same_process(app_main);
|
// zero_ui_view::run_same_process(app_main);
|
||||||
|
|
||||||
zero_ui_view::init();
|
zero_ui_view::init();
|
||||||
app_main();
|
app_main();
|
||||||
|
|
||||||
|
rec.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app_main() {
|
fn app_main() {
|
||||||
|
@ -32,9 +35,6 @@ fn app_main() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(feature = "app_profiler")]
|
|
||||||
zero_ui::core::profiler::write_profile("profile-gradient.json", false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(title: &'static str) -> impl Widget {
|
fn title(title: &'static str) -> impl Widget {
|
||||||
|
|
|
@ -22,9 +22,6 @@ fn app_main() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(feature = "app_profiler")]
|
|
||||||
zero_ui::core::profiler::write_profile("same_process-profile.json", false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn click_counter() -> impl Widget {
|
fn click_counter() -> impl Widget {
|
||||||
|
|
|
@ -5,4 +5,7 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
flume = "0.10"
|
||||||
|
rustc-hash = "1"
|
||||||
|
v_jsonescape = "0.6"
|
|
@ -1,31 +1,46 @@
|
||||||
use tracing::{Level, Subscriber};
|
use tracing::{Level, Subscriber};
|
||||||
use tracing_subscriber::{layer::Layer, prelude::*};
|
use tracing_subscriber::{layer::Layer, prelude::*};
|
||||||
|
|
||||||
/// Prints `tracing` and `log` events of level INFO and above.
|
mod profiler;
|
||||||
|
pub use profiler::*;
|
||||||
|
|
||||||
|
/// Prints `tracing` and `log` events of levels INFO, WARN and ERROR.
|
||||||
pub fn print_info() {
|
pub fn print_info() {
|
||||||
|
tracing_print(Level::INFO)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints `tracing` and `log` events of all levels.
|
||||||
|
pub fn print_trace() {
|
||||||
|
tracing_print(Level::TRACE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tracing_print(max: Level) {
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
.with(CustomFilter)
|
.with(FilterLayer(max))
|
||||||
.with(tracing_subscriber::fmt::layer().without_time().pretty())
|
.with(tracing_subscriber::fmt::layer().without_time().pretty())
|
||||||
.init();
|
.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CustomFilter;
|
struct FilterLayer(Level);
|
||||||
impl<S: Subscriber> Layer<S> for CustomFilter {
|
impl<S: Subscriber> Layer<S> for FilterLayer {
|
||||||
fn enabled(&self, metadata: &tracing::Metadata<'_>, _: tracing_subscriber::layer::Context<'_, S>) -> bool {
|
fn enabled(&self, metadata: &tracing::Metadata<'_>, _: tracing_subscriber::layer::Context<'_, S>) -> bool {
|
||||||
if metadata.level() > &Level::INFO {
|
filter(&self.0, metadata)
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// suppress webrender vertex debug-only warnings.
|
|
||||||
// see: https://bugzilla.mozilla.org/show_bug.cgi?id=1615342
|
|
||||||
if metadata.target() == "webrender::device::gl" && metadata.line() == Some(2331) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn max_level_hint(&self) -> Option<tracing::metadata::LevelFilter> {
|
fn max_level_hint(&self) -> Option<tracing::metadata::LevelFilter> {
|
||||||
Some(tracing::metadata::LevelFilter::INFO)
|
Some(self.0.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn filter(level: &Level, metadata: &tracing::Metadata) -> bool {
|
||||||
|
if metadata.level() > level {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// suppress webrender vertex debug-only warnings.
|
||||||
|
// see: https://bugzilla.mozilla.org/show_bug.cgi?id=1615342
|
||||||
|
if metadata.target() == "webrender::device::gl" && metadata.line() == Some(2331) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,293 @@
|
||||||
|
use std::{
|
||||||
|
cell::Cell,
|
||||||
|
fs::File,
|
||||||
|
io::{BufWriter, Write},
|
||||||
|
path::Path,
|
||||||
|
sync::atomic::{AtomicU64, Ordering},
|
||||||
|
thread::{self, ThreadId},
|
||||||
|
};
|
||||||
|
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
use tracing::{span, Level, Subscriber};
|
||||||
|
use v_jsonescape::escape;
|
||||||
|
|
||||||
|
/// Start recording trace level spans and events.
|
||||||
|
///
|
||||||
|
/// Call [`Recording::finish`] to stop recording and wait flush.
|
||||||
|
///
|
||||||
|
/// Profiles can be viewed using the `chrome://tracing` app.
|
||||||
|
pub fn record_profile(path: impl AsRef<Path>) -> Recording {
|
||||||
|
let mut file = BufWriter::new(File::create(path).unwrap());
|
||||||
|
let (sender, recv) = flume::unbounded();
|
||||||
|
|
||||||
|
let worker = thread::spawn(move || {
|
||||||
|
let mut threads = FxHashMap::<ThreadId, String>::default();
|
||||||
|
let mut spans = FxHashMap::<span::Id, Span>::default();
|
||||||
|
|
||||||
|
struct Span {
|
||||||
|
name: &'static str,
|
||||||
|
target: &'static str,
|
||||||
|
file: Option<&'static str>,
|
||||||
|
line: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let pid = std::process::id();
|
||||||
|
|
||||||
|
// specs: https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.lpfof2aylapb
|
||||||
|
|
||||||
|
write!(&mut file, "[").unwrap();
|
||||||
|
let mut comma = "";
|
||||||
|
loop {
|
||||||
|
match recv.recv().unwrap() {
|
||||||
|
Msg::Event {
|
||||||
|
tid,
|
||||||
|
name,
|
||||||
|
target,
|
||||||
|
file: c_file,
|
||||||
|
line,
|
||||||
|
ts,
|
||||||
|
} => {
|
||||||
|
let tid = threads.get(&tid).unwrap();
|
||||||
|
write!(
|
||||||
|
&mut file,
|
||||||
|
r#"{}{{"pid":"{}","tid":"{}","ts":{},"ph":"i","name":"{}","args":{{"target":"{}""#,
|
||||||
|
comma,
|
||||||
|
pid,
|
||||||
|
tid,
|
||||||
|
ts,
|
||||||
|
escape(name),
|
||||||
|
escape(target)
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
if let Some(f) = c_file {
|
||||||
|
write!(&mut file, r#","file":"{}""#, escape(f)).unwrap();
|
||||||
|
}
|
||||||
|
if let Some(l) = line {
|
||||||
|
write!(&mut file, r#","line":{}"#, l).unwrap();
|
||||||
|
}
|
||||||
|
write!(&mut file, "}}}}").unwrap();
|
||||||
|
comma = ",";
|
||||||
|
}
|
||||||
|
Msg::Enter { id, tid, ts } => {
|
||||||
|
let span = spans.get(&id).unwrap();
|
||||||
|
let tid = threads.get(&tid).unwrap();
|
||||||
|
write!(
|
||||||
|
&mut file,
|
||||||
|
r#"{}{{"pid":"{}","tid":"{}","name":"{}","ph":"B","ts":{},"args":{{"target":"{}""#,
|
||||||
|
comma,
|
||||||
|
pid,
|
||||||
|
tid,
|
||||||
|
escape(span.name),
|
||||||
|
ts,
|
||||||
|
escape(span.target)
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
if let Some(f) = span.file {
|
||||||
|
write!(&mut file, r#","file":"{}""#, escape(f)).unwrap();
|
||||||
|
}
|
||||||
|
if let Some(l) = span.line {
|
||||||
|
write!(&mut file, r#","line":{}"#, l).unwrap();
|
||||||
|
}
|
||||||
|
write!(&mut file, "}}}}").unwrap();
|
||||||
|
comma = ",";
|
||||||
|
}
|
||||||
|
Msg::Exit { id, tid, ts } => {
|
||||||
|
let _ = id;
|
||||||
|
let tid = threads.get(&tid).unwrap();
|
||||||
|
write!(
|
||||||
|
&mut file,
|
||||||
|
r#"{}{{"pid":"{}","tid":"{}","ph":"E","ts":{}}}"#,
|
||||||
|
comma,
|
||||||
|
pid,
|
||||||
|
escape(tid),
|
||||||
|
ts
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
comma = ",";
|
||||||
|
}
|
||||||
|
Msg::NewSpan {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
target,
|
||||||
|
file,
|
||||||
|
line,
|
||||||
|
} => {
|
||||||
|
spans.insert(id, Span { name, target, file, line });
|
||||||
|
}
|
||||||
|
Msg::ThreadInfo { id, name } => {
|
||||||
|
threads.insert(id, name);
|
||||||
|
}
|
||||||
|
Msg::Finish => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(&mut file, "]").unwrap();
|
||||||
|
|
||||||
|
file.flush().unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
tracing::dispatcher::set_global_default(tracing::Dispatch::new(Profiler::new(sender.clone()))).unwrap();
|
||||||
|
|
||||||
|
Recording { sender, worker }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A running recording operation.
|
||||||
|
pub struct Recording {
|
||||||
|
sender: flume::Sender<Msg>,
|
||||||
|
worker: thread::JoinHandle<()>,
|
||||||
|
}
|
||||||
|
impl Recording {
|
||||||
|
/// Stop recording and wait flush.
|
||||||
|
pub fn finish(self) {
|
||||||
|
self.sender.send(Msg::Finish).unwrap();
|
||||||
|
self.worker.join().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Msg {
|
||||||
|
ThreadInfo {
|
||||||
|
id: ThreadId,
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
NewSpan {
|
||||||
|
id: span::Id,
|
||||||
|
name: &'static str,
|
||||||
|
target: &'static str,
|
||||||
|
file: Option<&'static str>,
|
||||||
|
line: Option<u32>,
|
||||||
|
},
|
||||||
|
|
||||||
|
Event {
|
||||||
|
tid: ThreadId,
|
||||||
|
name: &'static str,
|
||||||
|
target: &'static str,
|
||||||
|
file: Option<&'static str>,
|
||||||
|
line: Option<u32>,
|
||||||
|
ts: u64,
|
||||||
|
},
|
||||||
|
|
||||||
|
Enter {
|
||||||
|
id: span::Id,
|
||||||
|
tid: ThreadId,
|
||||||
|
ts: u64,
|
||||||
|
},
|
||||||
|
Exit {
|
||||||
|
id: span::Id,
|
||||||
|
tid: ThreadId,
|
||||||
|
ts: u64,
|
||||||
|
},
|
||||||
|
|
||||||
|
Finish,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Profiler {
|
||||||
|
id: AtomicU64,
|
||||||
|
sender: flume::Sender<Msg>,
|
||||||
|
}
|
||||||
|
impl Profiler {
|
||||||
|
fn new(sender: flume::Sender<Msg>) -> Self {
|
||||||
|
Profiler {
|
||||||
|
id: AtomicU64::new(1),
|
||||||
|
sender,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thread_id(&self) -> ThreadId {
|
||||||
|
let thread = thread::current();
|
||||||
|
let tid = thread.id();
|
||||||
|
|
||||||
|
THREAD_INFO.with(|sent| {
|
||||||
|
if !sent.get() {
|
||||||
|
sent.set(true);
|
||||||
|
|
||||||
|
self.sender
|
||||||
|
.send(Msg::ThreadInfo {
|
||||||
|
id: tid,
|
||||||
|
name: thread
|
||||||
|
.name()
|
||||||
|
.map(|n| escape(n).to_string())
|
||||||
|
.unwrap_or_else(|| format!("<{:?}>", tid)),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Subscriber for Profiler {
|
||||||
|
fn enabled(&self, metadata: &tracing::Metadata<'_>) -> bool {
|
||||||
|
crate::filter(&Level::TRACE, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_span(&self, span: &span::Attributes<'_>) -> span::Id {
|
||||||
|
let id = span::Id::from_u64(self.id.fetch_add(1, Ordering::Relaxed));
|
||||||
|
|
||||||
|
let meta = span.metadata();
|
||||||
|
|
||||||
|
self.sender
|
||||||
|
.send(Msg::NewSpan {
|
||||||
|
id: id.clone(),
|
||||||
|
name: meta.name(),
|
||||||
|
target: meta.target(),
|
||||||
|
file: meta.file(),
|
||||||
|
line: meta.line(),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record(&self, span: &span::Id, values: &span::Record<'_>) {
|
||||||
|
let _ = (span, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record_follows_from(&self, span: &span::Id, follows: &span::Id) {
|
||||||
|
let _ = (span, follows);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&self, event: &tracing::Event<'_>) {
|
||||||
|
let ts = time_ns();
|
||||||
|
|
||||||
|
let tid = self.thread_id();
|
||||||
|
let meta = event.metadata();
|
||||||
|
|
||||||
|
self.sender
|
||||||
|
.send(Msg::Event {
|
||||||
|
tid,
|
||||||
|
name: meta.name(),
|
||||||
|
target: meta.target(),
|
||||||
|
file: meta.file(),
|
||||||
|
line: meta.line(),
|
||||||
|
ts,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter(&self, span: &span::Id) {
|
||||||
|
let ts = time_ns();
|
||||||
|
|
||||||
|
let tid = self.thread_id();
|
||||||
|
|
||||||
|
self.sender.send(Msg::Enter { id: span.clone(), tid, ts }).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit(&self, span: &span::Id) {
|
||||||
|
let ts = time_ns();
|
||||||
|
|
||||||
|
let tid = self.thread_id();
|
||||||
|
|
||||||
|
self.sender.send(Msg::Exit { id: span.clone(), tid, ts }).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn time_ns() -> u64 {
|
||||||
|
std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_micros() as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static THREAD_INFO: Cell<bool> = Cell::new(false);
|
||||||
|
}
|
|
@ -116,9 +116,6 @@ fn app_main() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(feature = "app_profiler")]
|
|
||||||
zero_ui::core::profiler::write_profile("window-profile.json", false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn property_stack(header: &'static str, items: impl WidgetList) -> impl Widget {
|
fn property_stack(header: &'static str, items: impl WidgetList) -> impl Widget {
|
||||||
|
|
|
@ -195,15 +195,13 @@ fn test(mut args: Vec<&str>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// do run, r EXAMPLE [-p, --profile] [-b, --backtrace] [<cargo-run-args>]
|
// do run, r EXAMPLE [-b, --backtrace] [<cargo-run-args>]
|
||||||
// Run an example in ./examples. If profiling builds in release with app_profiler.
|
// Run an example in ./examples.
|
||||||
// USAGE:
|
// USAGE:
|
||||||
// run some_example
|
// run some_example
|
||||||
// Runs the example in debug mode.
|
// Runs the example in debug mode.
|
||||||
// run some_example --release
|
// run some_example --release
|
||||||
// Runs the example in release mode.
|
// Runs the example in release mode.
|
||||||
// run some_example --profile
|
|
||||||
// Runs the example "app_profiler" feature.
|
|
||||||
// run some_example --backtrace
|
// run some_example --backtrace
|
||||||
// Runs the "some_example" with `RUST_BACKTRACE=1`.
|
// Runs the "some_example" with `RUST_BACKTRACE=1`.
|
||||||
// run *
|
// run *
|
||||||
|
@ -215,18 +213,7 @@ fn run(mut args: Vec<&str>) {
|
||||||
("", "")
|
("", "")
|
||||||
};
|
};
|
||||||
|
|
||||||
if take_flag(&mut args, &["-p", "--profile"]) {
|
if let Some(&"*") = args.first() {
|
||||||
let release = take_flag(&mut args, &["--release"]);
|
|
||||||
let rust_flags = release_rust_flags(release);
|
|
||||||
let rust_flags = &[(rust_flags.0, rust_flags.1.as_str()), trace];
|
|
||||||
let release = if release { "--release" } else { "" };
|
|
||||||
cmd_env(
|
|
||||||
"cargo",
|
|
||||||
&["run", "--features", "app_profiler", release, "--example"],
|
|
||||||
&args,
|
|
||||||
rust_flags,
|
|
||||||
);
|
|
||||||
} else if let Some(&"*") = args.first() {
|
|
||||||
args.remove(0);
|
args.remove(0);
|
||||||
let release = args.contains(&"--release");
|
let release = args.contains(&"--release");
|
||||||
let rust_flags = release_rust_flags(release);
|
let rust_flags = release_rust_flags(release);
|
||||||
|
|
|
@ -6,13 +6,6 @@ edition = "2021"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# Enable the zero_ui_core::profiler module.
|
|
||||||
#
|
|
||||||
# USAGE:
|
|
||||||
#
|
|
||||||
# `#[cfg(feature = "app_profiler")` or just use the `profile_scope!` macro.
|
|
||||||
app_profiler = []
|
|
||||||
|
|
||||||
# like cfg(test) but also visible in docs and integration tests.
|
# like cfg(test) but also visible in docs and integration tests.
|
||||||
#
|
#
|
||||||
# USAGE:
|
# USAGE:
|
||||||
|
|
|
@ -4,7 +4,6 @@ use crate::context::*;
|
||||||
use crate::crate_util::PanicPayload;
|
use crate::crate_util::PanicPayload;
|
||||||
use crate::event::{cancelable_event_args, event, AnyEventUpdate, EventUpdate, EventUpdateArgs, Events};
|
use crate::event::{cancelable_event_args, event, AnyEventUpdate, EventUpdate, EventUpdateArgs, Events};
|
||||||
use crate::image::ImageManager;
|
use crate::image::ImageManager;
|
||||||
use crate::profiler::*;
|
|
||||||
use crate::timer::Timers;
|
use crate::timer::Timers;
|
||||||
use crate::var::{response_var, ResponderVar, ResponseVar, Vars};
|
use crate::var::{response_var, ResponderVar, ResponseVar, Vars};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -713,9 +712,6 @@ impl<E: AppExtension> AppExtended<E> {
|
||||||
/// Panics if not called by the main thread. This means you cannot run an app in unit tests, use a headless
|
/// Panics if not called by the main thread. This means you cannot run an app in unit tests, use a headless
|
||||||
/// app without renderer for that. The main thread is required by some operating systems and OpenGL.
|
/// app without renderer for that. The main thread is required by some operating systems and OpenGL.
|
||||||
pub fn run(self, start: impl FnOnce(&mut AppContext)) {
|
pub fn run(self, start: impl FnOnce(&mut AppContext)) {
|
||||||
#[cfg(feature = "app_profiler")]
|
|
||||||
register_thread_with_profiler();
|
|
||||||
|
|
||||||
let mut app = RunningApp::start(self.extensions, true, true, self.view_process_exe);
|
let mut app = RunningApp::start(self.extensions, true, true, self.view_process_exe);
|
||||||
|
|
||||||
start(&mut app.ctx());
|
start(&mut app.ctx());
|
||||||
|
@ -733,9 +729,6 @@ impl<E: AppExtension> AppExtended<E> {
|
||||||
/// If called in a test (`cfg(test)`) this blocks until no other instance of [`HeadlessApp`] and
|
/// If called in a test (`cfg(test)`) this blocks until no other instance of [`HeadlessApp`] and
|
||||||
/// [`TestWidgetContext`] are running in the current thread.
|
/// [`TestWidgetContext`] are running in the current thread.
|
||||||
pub fn run_headless(self, with_renderer: bool) -> HeadlessApp {
|
pub fn run_headless(self, with_renderer: bool) -> HeadlessApp {
|
||||||
#[cfg(feature = "app_profiler")]
|
|
||||||
register_thread_with_profiler();
|
|
||||||
|
|
||||||
let app = RunningApp::start(self.extensions.boxed(), false, with_renderer, self.view_process_exe);
|
let app = RunningApp::start(self.extensions.boxed(), false, with_renderer, self.view_process_exe);
|
||||||
|
|
||||||
HeadlessApp { app }
|
HeadlessApp { app }
|
||||||
|
@ -767,7 +760,7 @@ impl<E: AppExtension> RunningApp<E> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
profile_scope!("App::start");
|
let _s = tracing::debug_span!("App::start").entered();
|
||||||
|
|
||||||
let (sender, receiver) = AppEventSender::new();
|
let (sender, receiver) = AppEventSender::new();
|
||||||
|
|
||||||
|
@ -795,7 +788,7 @@ impl<E: AppExtension> RunningApp<E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
profile_scope!("extensions.init");
|
let _s = tracing::debug_span!("extensions.init").entered();
|
||||||
extensions.init(&mut ctx);
|
extensions.init(&mut ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -852,7 +845,7 @@ impl<E: AppExtension> RunningApp<E> {
|
||||||
args: Ev::Args,
|
args: Ev::Args,
|
||||||
observer: &mut O,
|
observer: &mut O,
|
||||||
) {
|
) {
|
||||||
profile_scope!("notify_event<{}>", type_name::<Ev>());
|
let _scope = tracing::trace_span!("notify_event", event = type_name::<Ev>()).entered();
|
||||||
|
|
||||||
let update = EventUpdate::<Ev>(args);
|
let update = EventUpdate::<Ev>(args);
|
||||||
extensions.event_preview(ctx, &update);
|
extensions.event_preview(ctx, &update);
|
||||||
|
@ -877,8 +870,7 @@ impl<E: AppExtension> RunningApp<E> {
|
||||||
let mut flow = ControlFlow::Poll;
|
let mut flow = ControlFlow::Poll;
|
||||||
|
|
||||||
while let ControlFlow::Poll = flow {
|
while let ControlFlow::Poll = flow {
|
||||||
#[cfg(feature = "app_profiler")]
|
let idle = tracing::trace_span!("<poll-idle>").entered();
|
||||||
let idle = crate::profiler::ProfileScope::new("<poll-idle>");
|
|
||||||
|
|
||||||
let ev = if let Some(timer) = self.wake_time {
|
let ev = if let Some(timer) = self.wake_time {
|
||||||
match self.receiver.recv_deadline(timer) {
|
match self.receiver.recv_deadline(timer) {
|
||||||
|
@ -897,7 +889,6 @@ impl<E: AppExtension> RunningApp<E> {
|
||||||
self.receiver.recv().expect("app events channel disconnected")
|
self.receiver.recv().expect("app events channel disconnected")
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "app_profiler")]
|
|
||||||
drop(idle);
|
drop(idle);
|
||||||
|
|
||||||
flow = if !matches!(ev, AppEvent::ViewEvent(view_process::Event::EventsCleared)) || self.receiver.is_empty() {
|
flow = if !matches!(ev, AppEvent::ViewEvent(view_process::Event::EventsCleared)) || self.receiver.is_empty() {
|
||||||
|
@ -937,7 +928,7 @@ impl<E: AppExtension> RunningApp<E> {
|
||||||
|
|
||||||
/// Process an [`AppEvent`].
|
/// Process an [`AppEvent`].
|
||||||
fn app_event<O: AppEventObserver>(&mut self, app_event: AppEvent, observer: &mut O) -> ControlFlow {
|
fn app_event<O: AppEventObserver>(&mut self, app_event: AppEvent, observer: &mut O) -> ControlFlow {
|
||||||
profile_scope!("app_event");
|
let _s = tracing::trace_span!("app_event").entered();
|
||||||
|
|
||||||
self.maybe_has_updates = true;
|
self.maybe_has_updates = true;
|
||||||
|
|
||||||
|
@ -962,15 +953,7 @@ impl<E: AppExtension> RunningApp<E> {
|
||||||
///
|
///
|
||||||
/// Does `update` on `EventsCleared`.
|
/// Does `update` on `EventsCleared`.
|
||||||
fn view_event<O: AppEventObserver>(&mut self, ev: zero_ui_view_api::Event, observer: &mut O) -> ControlFlow {
|
fn view_event<O: AppEventObserver>(&mut self, ev: zero_ui_view_api::Event, observer: &mut O) -> ControlFlow {
|
||||||
profile_scope!("view_event");
|
let _s = tracing::debug_span!("view_event", ?ev).entered();
|
||||||
profile_scope!("{}", {
|
|
||||||
let ev = format!("Event::{:?}", ev);
|
|
||||||
let l = ev.len();
|
|
||||||
let p = ev.find('(').unwrap_or(l);
|
|
||||||
let c = ev.find('{').unwrap_or(l);
|
|
||||||
let i = p.min(c);
|
|
||||||
ev[..i].to_owned()
|
|
||||||
});
|
|
||||||
|
|
||||||
use raw_device_events::*;
|
use raw_device_events::*;
|
||||||
use raw_events::*;
|
use raw_events::*;
|
||||||
|
@ -1294,7 +1277,7 @@ impl<E: AppExtension> RunningApp<E> {
|
||||||
/// [`wake_time`]: RunningApp::wake_time
|
/// [`wake_time`]: RunningApp::wake_time
|
||||||
pub fn update<O: AppEventObserver>(&mut self, observer: &mut O) -> ControlFlow {
|
pub fn update<O: AppEventObserver>(&mut self, observer: &mut O) -> ControlFlow {
|
||||||
if self.maybe_has_updates {
|
if self.maybe_has_updates {
|
||||||
profile_scope!("update-cycle");
|
let _s = tracing::debug_span!("update-cycle").entered();
|
||||||
|
|
||||||
self.maybe_has_updates = false;
|
self.maybe_has_updates = false;
|
||||||
|
|
||||||
|
@ -1318,7 +1301,7 @@ impl<E: AppExtension> RunningApp<E> {
|
||||||
if u.update {
|
if u.update {
|
||||||
// check shutdown.
|
// check shutdown.
|
||||||
if let Some(r) = ctx.services.app_process().take_requests() {
|
if let Some(r) = ctx.services.app_process().take_requests() {
|
||||||
profile_scope!("shutdown_requested");
|
let _s = tracing::debug_span!("shutdown_requested").entered();
|
||||||
|
|
||||||
let args = ShutdownRequestedArgs::now();
|
let args = ShutdownRequestedArgs::now();
|
||||||
|
|
||||||
|
@ -1338,10 +1321,10 @@ impl<E: AppExtension> RunningApp<E> {
|
||||||
|
|
||||||
// does `Event` notifications.
|
// does `Event` notifications.
|
||||||
{
|
{
|
||||||
profile_scope!("events");
|
let _s = tracing::trace_span!("events").entered();
|
||||||
|
|
||||||
for event in u.events {
|
for event in u.events {
|
||||||
profile_scope!("{:?}", event);
|
let _s = tracing::debug_span!("event", ?event).entered();
|
||||||
|
|
||||||
self.extensions.event_preview(&mut ctx, &event);
|
self.extensions.event_preview(&mut ctx, &event);
|
||||||
observer.event_preview(&mut ctx, &event);
|
observer.event_preview(&mut ctx, &event);
|
||||||
|
@ -1358,7 +1341,7 @@ impl<E: AppExtension> RunningApp<E> {
|
||||||
|
|
||||||
// does general updates.
|
// does general updates.
|
||||||
{
|
{
|
||||||
profile_scope!("update");
|
let _s = tracing::trace_span!("update").entered();
|
||||||
|
|
||||||
self.extensions.update_preview(&mut ctx);
|
self.extensions.update_preview(&mut ctx);
|
||||||
observer.update_preview(&mut ctx);
|
observer.update_preview(&mut ctx);
|
||||||
|
@ -1372,14 +1355,14 @@ impl<E: AppExtension> RunningApp<E> {
|
||||||
Updates::on_updates(&mut ctx);
|
Updates::on_updates(&mut ctx);
|
||||||
}
|
}
|
||||||
} else if layout {
|
} else if layout {
|
||||||
profile_scope!("layout");
|
let _s = tracing::trace_span!("layout").entered();
|
||||||
|
|
||||||
self.extensions.layout(&mut ctx);
|
self.extensions.layout(&mut ctx);
|
||||||
observer.layout(&mut ctx);
|
observer.layout(&mut ctx);
|
||||||
|
|
||||||
layout = false;
|
layout = false;
|
||||||
} else if render {
|
} else if render {
|
||||||
profile_scope!("render");
|
let _s = tracing::trace_span!("render").entered();
|
||||||
|
|
||||||
self.extensions.render(&mut ctx);
|
self.extensions.render(&mut ctx);
|
||||||
observer.render(&mut ctx);
|
observer.render(&mut ctx);
|
||||||
|
@ -1400,7 +1383,7 @@ impl<E: AppExtension> RunningApp<E> {
|
||||||
}
|
}
|
||||||
impl<E: AppExtension> Drop for RunningApp<E> {
|
impl<E: AppExtension> Drop for RunningApp<E> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
profile_scope!("extensions.deinit");
|
let _s = tracing::debug_span!("extensions.deinit").entered();
|
||||||
let mut ctx = self.owned_ctx.borrow();
|
let mut ctx = self.owned_ctx.borrow();
|
||||||
self.extensions.deinit(&mut ctx);
|
self.extensions.deinit(&mut ctx);
|
||||||
}
|
}
|
||||||
|
@ -1932,7 +1915,7 @@ pub mod view_process {
|
||||||
use crate::task::SignalOnce;
|
use crate::task::SignalOnce;
|
||||||
use crate::units::{DipPoint, DipSize, Px, PxPoint, PxRect, PxSize};
|
use crate::units::{DipPoint, DipSize, Px, PxPoint, PxRect, PxSize};
|
||||||
use crate::window::{MonitorId, WindowId};
|
use crate::window::{MonitorId, WindowId};
|
||||||
use crate::{event, event_args, profile_scope};
|
use crate::{event, event_args};
|
||||||
use zero_ui_view_api::webrender_api::{
|
use zero_ui_view_api::webrender_api::{
|
||||||
DocumentId, FontInstanceKey, FontInstanceOptions, FontInstancePlatformOptions, FontKey, FontVariation, HitTestResult, IdNamespace,
|
DocumentId, FontInstanceKey, FontInstanceOptions, FontInstancePlatformOptions, FontKey, FontVariation, HitTestResult, IdNamespace,
|
||||||
ImageKey, PipelineId,
|
ImageKey, PipelineId,
|
||||||
|
@ -1992,7 +1975,7 @@ pub mod view_process {
|
||||||
where
|
where
|
||||||
F: FnMut(Event) + Send + 'static,
|
F: FnMut(Event) + Send + 'static,
|
||||||
{
|
{
|
||||||
profile_scope!("ViewProcess::start");
|
let _s = tracing::debug_span!("ViewProcess::start").entered();
|
||||||
|
|
||||||
let process = zero_ui_view_api::Controller::start(view_process_exe, device_events, headless, on_event);
|
let process = zero_ui_view_api::Controller::start(view_process_exe, device_events, headless, on_event);
|
||||||
Self(Rc::new(RefCell::new(ViewApp {
|
Self(Rc::new(RefCell::new(ViewApp {
|
||||||
|
@ -2020,7 +2003,7 @@ pub mod view_process {
|
||||||
|
|
||||||
/// Open a window and associate it with the `window_id`.
|
/// Open a window and associate it with the `window_id`.
|
||||||
pub fn open_window(&self, config: WindowRequest) -> Result<(ViewWindow, WindowOpenData)> {
|
pub fn open_window(&self, config: WindowRequest) -> Result<(ViewWindow, WindowOpenData)> {
|
||||||
profile_scope!("ViewProcess.open_window");
|
let _s = tracing::debug_span!("ViewProcess.open_window").entered();
|
||||||
|
|
||||||
let mut app = self.0.borrow_mut();
|
let mut app = self.0.borrow_mut();
|
||||||
let _ = app.check_generation();
|
let _ = app.check_generation();
|
||||||
|
@ -2044,7 +2027,7 @@ pub mod view_process {
|
||||||
/// Note that no actual window is created, only the renderer, the use of window-ids to identify
|
/// Note that no actual window is created, only the renderer, the use of window-ids to identify
|
||||||
/// this renderer is only for convenience.
|
/// this renderer is only for convenience.
|
||||||
pub fn open_headless(&self, config: HeadlessRequest) -> Result<ViewHeadless> {
|
pub fn open_headless(&self, config: HeadlessRequest) -> Result<ViewHeadless> {
|
||||||
profile_scope!("ViewProcess.open_headless");
|
let _s = tracing::debug_span!("ViewProcess.open_headless").entered();
|
||||||
|
|
||||||
let mut app = self.0.borrow_mut();
|
let mut app = self.0.borrow_mut();
|
||||||
|
|
||||||
|
@ -3063,13 +3046,13 @@ pub mod view_process {
|
||||||
|
|
||||||
/// Render a new frame.
|
/// Render a new frame.
|
||||||
pub fn render(&self, frame: FrameRequest) -> Result<()> {
|
pub fn render(&self, frame: FrameRequest) -> Result<()> {
|
||||||
profile_scope!("ViewRenderer.render");
|
let _s = tracing::debug_span!("ViewRenderer.render").entered();
|
||||||
self.call(|id, p| p.render(id, frame))
|
self.call(|id, p| p.render(id, frame))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the current frame and re-render it.
|
/// Update the current frame and re-render it.
|
||||||
pub fn render_update(&self, frame: FrameUpdateRequest) -> Result<()> {
|
pub fn render_update(&self, frame: FrameUpdateRequest) -> Result<()> {
|
||||||
profile_scope!("ViewRenderer.render_update");
|
let _s = tracing::debug_span!("ViewRenderer.render_update").entered();
|
||||||
self.call(|id, p| p.render_update(id, frame))
|
self.call(|id, p| p.render_update(id, frame))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ use crate::{
|
||||||
context::RenderContext,
|
context::RenderContext,
|
||||||
crate_util::IdMap,
|
crate_util::IdMap,
|
||||||
event::EventUpdateArgs,
|
event::EventUpdateArgs,
|
||||||
formatx, profile_scope,
|
formatx,
|
||||||
text::{Text, ToText},
|
text::{Text, ToText},
|
||||||
BoxedUiNode,
|
BoxedUiNode,
|
||||||
};
|
};
|
||||||
|
@ -673,7 +673,7 @@ impl UiNode for PropertyInfoNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, ctx: &mut RenderContext, frame: &mut FrameBuilder) {
|
fn render(&self, ctx: &mut RenderContext, frame: &mut FrameBuilder) {
|
||||||
profile_scope!("{}.render", self.info.borrow().property_name);
|
let _scope = tracing::trace_span!("property.render", property = self.info.borrow().property_name).entered();
|
||||||
|
|
||||||
let t = Instant::now();
|
let t = Instant::now();
|
||||||
self.child.render(ctx, frame);
|
self.child.render(ctx, frame);
|
||||||
|
|
|
@ -54,7 +54,6 @@ pub mod gradient;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
pub mod mouse;
|
pub mod mouse;
|
||||||
pub mod profiler;
|
|
||||||
pub mod render;
|
pub mod render;
|
||||||
pub mod service;
|
pub mod service;
|
||||||
pub mod task;
|
pub mod task;
|
||||||
|
|
|
@ -1,261 +0,0 @@
|
||||||
//! Performance profiling.
|
|
||||||
//!
|
|
||||||
//! Crate must be compiled with the `app_profiler`. See [`profile_scope!`] and [`write_profile`] for more details.
|
|
||||||
//!
|
|
||||||
//! Profiler can be viewed using the `chrome://tracing` app.
|
|
||||||
//!
|
|
||||||
//! [`profile_scope!`]: crate::profiler::profile_scope
|
|
||||||
//! [`write_profile`]: crate::profiler::write_profile
|
|
||||||
|
|
||||||
#[cfg(feature = "app_profiler")]
|
|
||||||
#[cfg_attr(doc_nightly, doc(cfg(feature = "app_profiler")))]
|
|
||||||
mod profiler_impl {
|
|
||||||
use serde_json::*;
|
|
||||||
|
|
||||||
use crate::text::Text;
|
|
||||||
use flume::{unbounded, Receiver, Sender};
|
|
||||||
use parking_lot::{const_mutex, Mutex};
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::BufWriter;
|
|
||||||
use std::string::String;
|
|
||||||
use std::thread;
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
|
||||||
|
|
||||||
static GLOBAL_PROFILER: Mutex<Option<Profiler>> = const_mutex(None);
|
|
||||||
|
|
||||||
thread_local!(static THREAD_PROFILER: RefCell<Option<ThreadProfiler>> = RefCell::new(None));
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
struct ThreadId(usize);
|
|
||||||
|
|
||||||
struct ThreadInfo {
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Sample {
|
|
||||||
tid: ThreadId,
|
|
||||||
name: Text,
|
|
||||||
t0: u64,
|
|
||||||
t1: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ThreadProfiler {
|
|
||||||
id: ThreadId,
|
|
||||||
tx: Sender<Sample>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThreadProfiler {
|
|
||||||
fn push_sample(&self, name: Text, t0: u64, t1: u64) {
|
|
||||||
let sample = Sample {
|
|
||||||
tid: self.id,
|
|
||||||
name,
|
|
||||||
t0,
|
|
||||||
t1,
|
|
||||||
};
|
|
||||||
self.tx.send(sample).ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Profiler {
|
|
||||||
rx: Receiver<Sample>,
|
|
||||||
tx: Sender<Sample>,
|
|
||||||
threads: Vec<ThreadInfo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Profiler {
|
|
||||||
fn new() -> Profiler {
|
|
||||||
let (tx, rx) = unbounded();
|
|
||||||
|
|
||||||
Profiler {
|
|
||||||
rx,
|
|
||||||
tx,
|
|
||||||
threads: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_thread(&mut self) {
|
|
||||||
let registered_name = THREAD_PROFILER.with(|profiler| {
|
|
||||||
if profiler.borrow().is_none() {
|
|
||||||
let id = ThreadId(self.threads.len());
|
|
||||||
|
|
||||||
let thread_profiler = ThreadProfiler { id, tx: self.tx.clone() };
|
|
||||||
*profiler.borrow_mut() = Some(thread_profiler);
|
|
||||||
|
|
||||||
Some(match thread::current().name() {
|
|
||||||
Some(s) => s.to_string(),
|
|
||||||
None => format!("<unnamed-{}>", id.0),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let Some(mut name) = registered_name {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
name.push_str("-DEBUG");
|
|
||||||
}
|
|
||||||
self.threads.push(ThreadInfo { name });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_profile(&self, filename: &str, ignore_0ms: bool) {
|
|
||||||
// Stop reading samples that are written after
|
|
||||||
// write_profile() is called.
|
|
||||||
let start_time = precise_time_ns();
|
|
||||||
let mut data = Vec::new();
|
|
||||||
|
|
||||||
let p_id = std::process::id();
|
|
||||||
|
|
||||||
while let Ok(sample) = self.rx.try_recv() {
|
|
||||||
if sample.t0 > start_time {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let thread_id = self.threads[sample.tid.0].name.as_str();
|
|
||||||
let t0 = sample.t0 / 1000;
|
|
||||||
let t1 = sample.t1 / 1000;
|
|
||||||
|
|
||||||
if ignore_0ms && t0 == t1 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.push(json!({
|
|
||||||
"pid": p_id,
|
|
||||||
"tid": thread_id,
|
|
||||||
"name": sample.name.as_ref(),
|
|
||||||
"ph": "B",
|
|
||||||
"ts": t0
|
|
||||||
}));
|
|
||||||
|
|
||||||
data.push(json!({
|
|
||||||
"pid": p_id,
|
|
||||||
"tid": thread_id,
|
|
||||||
"ph": "E",
|
|
||||||
"ts": t1
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
let f = BufWriter::new(File::create(filename).unwrap());
|
|
||||||
serde_json::to_writer(f, &data).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Named profile scope. The scope start time is when [`new`](ProfileScope::new) is called,
|
|
||||||
/// the scope duration is the time it was alive.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # use zero_ui_core::profiler::ProfileScope;
|
|
||||||
/// # fn do_thing() { }
|
|
||||||
/// # fn do_another_thing() { }
|
|
||||||
/// {
|
|
||||||
/// #[cfg(feature = "app_profiler")]
|
|
||||||
/// let _scope = ProfileScope::new("do-things");
|
|
||||||
///
|
|
||||||
/// do_thing();
|
|
||||||
/// do_another_thing();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// # Macro
|
|
||||||
///
|
|
||||||
/// For basic usage like in the example there is also the [`profile_scope!`](macro.profile_scope.html) macro.
|
|
||||||
#[cfg_attr(doc_nightly, doc(cfg(feature = "app_profiler")))]
|
|
||||||
pub struct ProfileScope {
|
|
||||||
name: Text,
|
|
||||||
t0: u64,
|
|
||||||
}
|
|
||||||
impl ProfileScope {
|
|
||||||
/// Starts a new profile scope, the start time is when this method is called.
|
|
||||||
pub fn new(name: impl Into<Text>) -> ProfileScope {
|
|
||||||
let t0 = precise_time_ns();
|
|
||||||
ProfileScope { name: name.into(), t0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Drop for ProfileScope {
|
|
||||||
/// When the `ProfileScope` is dropped it records the
|
|
||||||
/// length of time it was alive for and records it
|
|
||||||
/// against the Profiler.
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let t1 = precise_time_ns();
|
|
||||||
|
|
||||||
THREAD_PROFILER.with(|profiler| match *profiler.borrow() {
|
|
||||||
Some(ref profiler) => {
|
|
||||||
profiler.push_sample(std::mem::take(&mut self.name), self.t0, t1);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
println!("ERROR: ProfileScope {} on unregistered thread!", self.name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes the global profile to a specific file.
|
|
||||||
#[inline]
|
|
||||||
#[cfg_attr(doc_nightly, doc(cfg(feature = "app_profiler")))]
|
|
||||||
pub fn write_profile(filename: &str, ignore_0ms: bool) {
|
|
||||||
GLOBAL_PROFILER
|
|
||||||
.lock()
|
|
||||||
.get_or_insert_with(Profiler::new)
|
|
||||||
.write_profile(filename, ignore_0ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Registers the current thread with the global profiler.
|
|
||||||
///
|
|
||||||
/// Does nothing if the thread is already registered.
|
|
||||||
#[inline]
|
|
||||||
#[cfg_attr(doc_nightly, doc(cfg(feature = "app_profiler")))]
|
|
||||||
pub fn register_thread_with_profiler() {
|
|
||||||
GLOBAL_PROFILER.lock().get_or_insert_with(Profiler::new).register_thread();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn precise_time_ns() -> u64 {
|
|
||||||
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "app_profiler")]
|
|
||||||
pub use profiler_impl::*;
|
|
||||||
|
|
||||||
///<span data-inline></span> Declares a [`ProfileScope`](crate::profiler::ProfileScope) variable if
|
|
||||||
/// the `app_profiler` feature is active.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// If compiled with the `app_profiler` feature, this will register a "do-things" scope
|
|
||||||
/// that starts when the macro was called and has the duration of the block.
|
|
||||||
/// ```
|
|
||||||
/// # use zero_ui_core::profiler::profile_scope;
|
|
||||||
/// # fn main()
|
|
||||||
/// {
|
|
||||||
/// # fn do_thing() { }
|
|
||||||
/// # fn do_another_thing() { }
|
|
||||||
/// profile_scope!("do-things");
|
|
||||||
///
|
|
||||||
/// do_thing();
|
|
||||||
/// do_another_thing();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// You can also format strings:
|
|
||||||
/// ```
|
|
||||||
/// # use zero_ui_core::profiler::profile_scope;
|
|
||||||
/// # let thing = "";
|
|
||||||
/// profile_scope!("do-{}", thing);
|
|
||||||
/// ```
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! profile_scope {
|
|
||||||
($name:expr) => {
|
|
||||||
#[cfg(feature = "app_profiler")]
|
|
||||||
let _profile_scope =
|
|
||||||
$crate::profiler::ProfileScope::new($name);
|
|
||||||
};
|
|
||||||
($($args:tt)+) => {
|
|
||||||
#[cfg(feature = "app_profiler")]
|
|
||||||
let _profile_scope =
|
|
||||||
$crate::profiler::ProfileScope::new(format!($($args)+));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use crate::profile_scope;
|
|
|
@ -27,7 +27,7 @@ use crate::{
|
||||||
event::{event, EventUpdateArgs},
|
event::{event, EventUpdateArgs},
|
||||||
event_args,
|
event_args,
|
||||||
image::{Image, ImageDataFormat, ImageSource, ImageVar, ImagesExt},
|
image::{Image, ImageDataFormat, ImageSource, ImageVar, ImagesExt},
|
||||||
impl_from_and_into_var, profile_scope,
|
impl_from_and_into_var,
|
||||||
render::{
|
render::{
|
||||||
webrender_api::{BuiltDisplayList, DynamicProperties, ExternalScrollId, PipelineId},
|
webrender_api::{BuiltDisplayList, DynamicProperties, ExternalScrollId, PipelineId},
|
||||||
FrameBuilder, FrameHitInfo, FrameId, FrameInfo, FrameUpdate, WidgetTransformKey,
|
FrameBuilder, FrameHitInfo, FrameId, FrameInfo, FrameUpdate, WidgetTransformKey,
|
||||||
|
@ -1901,12 +1901,12 @@ impl AppWindow {
|
||||||
|
|
||||||
fn on_update(&mut self, ctx: &mut AppContext) {
|
fn on_update(&mut self, ctx: &mut AppContext) {
|
||||||
if self.first_update {
|
if self.first_update {
|
||||||
profile_scope!("window({}).on_update-init", self.id.sequential());
|
let _s = tracing::trace_span!("window.on_update#first", window = %self.id.sequential());
|
||||||
|
|
||||||
self.context.init(ctx);
|
self.context.init(ctx);
|
||||||
self.first_update = false;
|
self.first_update = false;
|
||||||
} else {
|
} else {
|
||||||
profile_scope!("window({}).on_update", self.id.sequential());
|
let _s = tracing::trace_span!("window.on_update", window = %self.id.sequential());
|
||||||
|
|
||||||
self.context.update(ctx);
|
self.context.update(ctx);
|
||||||
|
|
||||||
|
@ -2141,7 +2141,7 @@ impl AppWindow {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
profile_scope!("window({}).on_layout", self.id.sequential());
|
let _s = tracing::trace_span!("window.on_layout", window = %self.id.sequential());
|
||||||
|
|
||||||
// layout using the "system" size, it can still be overwritten by auto_size.
|
// layout using the "system" size, it can still be overwritten by auto_size.
|
||||||
let (size, _, _) = self.layout_size(ctx, true);
|
let (size, _, _) = self.layout_size(ctx, true);
|
||||||
|
@ -2167,7 +2167,7 @@ impl AppWindow {
|
||||||
fn on_init_layout(&mut self, ctx: &mut AppContext) {
|
fn on_init_layout(&mut self, ctx: &mut AppContext) {
|
||||||
debug_assert!(self.first_layout);
|
debug_assert!(self.first_layout);
|
||||||
|
|
||||||
profile_scope!("window({}).on_init_layout", self.id.sequential());
|
let _s = tracing::trace_span!("window.on_init_layout", window = %self.id.sequential());
|
||||||
|
|
||||||
self.first_layout = false;
|
self.first_layout = false;
|
||||||
|
|
||||||
|
@ -2501,7 +2501,7 @@ impl AppWindow {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
profile_scope!("window({}).on_render", self.id.sequential());
|
let _s = tracing::trace_span!("window.on_render", window = %self.id.sequential());
|
||||||
|
|
||||||
let frame = self.render_frame(ctx);
|
let frame = self.render_frame(ctx);
|
||||||
|
|
||||||
|
@ -2519,7 +2519,7 @@ impl AppWindow {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
profile_scope!("window({}).on_render_update", self.id.sequential());
|
let _s = tracing::trace_span!("window.on_render_update", window = %self.id.sequential());
|
||||||
|
|
||||||
let capture_image = self.take_capture_image(ctx.vars);
|
let capture_image = self.take_capture_image(ctx.vars);
|
||||||
|
|
||||||
|
@ -2669,7 +2669,7 @@ impl OwnedWindowContext {
|
||||||
scale_factor: f32,
|
scale_factor: f32,
|
||||||
renderer: &Option<ViewRenderer>,
|
renderer: &Option<ViewRenderer>,
|
||||||
) -> ((PipelineId, BuiltDisplayList), RenderColor, FrameInfo) {
|
) -> ((PipelineId, BuiltDisplayList), RenderColor, FrameInfo) {
|
||||||
profile_scope!("WindowContext.render");
|
let _s = tracing::trace_span!("WindowContext.render");
|
||||||
|
|
||||||
self.update = WindowUpdates::none();
|
self.update = WindowUpdates::none();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue