diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 5bf40510f786023b345cd2b7d3a6fbc15c351105..03d64f6b1c06c192abf672b165833ee06b0b183f 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -34,7 +34,7 @@ use std::{ Arc, }, }; -use util::ResultExt; +use util::{measure, ResultExt}; mod element_cx; pub use element_cx::*; @@ -310,7 +310,9 @@ impl Window { platform_window.on_request_frame(Box::new({ let mut cx = cx.to_async(); move || { - handle.update(&mut cx, |_, cx| cx.draw()).log_err(); + measure("frame duration", || { + handle.update(&mut cx, |_, cx| cx.draw()).log_err(); + }) } })); platform_window.on_resize(Box::new({ diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index ccf612e16a2441a00fe95b34b793f613c6462354..ed03eb25ba534d6a123810b89667d0e577396d38 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -7,19 +7,21 @@ pub mod paths; #[cfg(any(test, feature = "test-support"))] pub mod test; +pub use backtrace::Backtrace; +use futures::Future; +use lazy_static::lazy_static; +use rand::{seq::SliceRandom, Rng}; use std::{ borrow::Cow, cmp::{self, Ordering}, + env, ops::{AddAssign, Range, RangeInclusive}, panic::Location, pin::Pin, task::{Context, Poll}, + time::Instant, }; -pub use backtrace::Backtrace; -use futures::Future; -use rand::{seq::SliceRandom, Rng}; - pub use take_until::*; #[macro_export] @@ -133,6 +135,24 @@ pub fn merge_non_null_json_value_into(source: serde_json::Value, target: &mut se } } +pub fn measure(label: &str, f: impl FnOnce() -> R) -> R { + lazy_static! { + pub static ref ZED_MEASUREMENTS: bool = env::var("ZED_MEASUREMENTS") + .map(|measurements| measurements == "1" || measurements == "true") + .unwrap_or(false); + } + + if *ZED_MEASUREMENTS { + let start = Instant::now(); + let result = f(); + let elapsed = start.elapsed(); + eprintln!("{}: {:?}", label, elapsed); + result + } else { + f() + } +} + pub trait ResultExt { type Ok; diff --git a/script/histogram b/script/histogram new file mode 100755 index 0000000000000000000000000000000000000000..b9885fbc006d9cbd284211a705d261041dae1630 --- /dev/null +++ b/script/histogram @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 + +# Required dependencies for this script: +# +# pandas: For data manipulation and analysis. +# matplotlib: For creating static, interactive, and animated visualizations in Python. +# seaborn: For making statistical graphics in Python, based on matplotlib. + +# To install these dependencies, use the following pip command: +# pip install pandas matplotlib seaborn + +# This script is designed to parse log files for performance measurements and create histograms of these measurements. +# It expects log files to contain lines with measurements in the format "measurement: timeunit" where timeunit can be in milliseconds (ms) or microseconds (µs). +# Lines that do not contain a colon ':' are skipped. +# The script takes one or more file paths as command-line arguments, parses each log file, and then combines the data into a single DataFrame. +# It then converts all time measurements into milliseconds, discards the original time and unit columns, and creates histograms for each unique measurement type. +# The histograms display the distribution of times for each measurement, separated by log file, and normalized to show density rather than count. +# To use this script, run it from the command line with the log file paths as arguments, like so: +# python this_script.py log1.txt log2.txt ... +# The script will then parse the provided log files and display the histograms for each type of measurement found. + +import pandas as pd +import matplotlib.pyplot as plt +import seaborn as sns +import sys + +def parse_log_file(file_path): + data = {'measurement': [], 'time': [], 'unit': [], 'log_file': []} + with open(file_path, 'r') as file: + for line in file: + if ':' not in line: + continue + + parts = line.strip().split(': ') + if len(parts) != 2: + continue + + measurement, time_with_unit = parts[0], parts[1] + if 'ms' in time_with_unit: + time, unit = time_with_unit[:-2], 'ms' + elif 'µs' in time_with_unit: + time, unit = time_with_unit[:-2], 'µs' + else: + raise ValueError(f"Invalid time unit in line: {line.strip()}") + continue + + data['measurement'].append(measurement) + data['time'].append(float(time)) + data['unit'].append(unit) + data['log_file'].append(file_path.split('/')[-1]) + return pd.DataFrame(data) + +def create_histograms(df, measurement): + filtered_df = df[df['measurement'] == measurement] + plt.figure(figsize=(12, 6)) + sns.histplot(data=filtered_df, x='time_ms', hue='log_file', element='step', stat='density', common_norm=False, palette='bright') + plt.title(f'Histogram of {measurement}') + plt.xlabel('Time (ms)') + plt.ylabel('Density') + plt.grid(True) + plt.xlim(filtered_df['time_ms'].quantile(0.01), filtered_df['time_ms'].quantile(0.99)) + plt.show() + + +file_paths = sys.argv[1:] +dfs = [parse_log_file(path) for path in file_paths] +combined_df = pd.concat(dfs, ignore_index=True) +combined_df['time_ms'] = combined_df.apply(lambda row: row['time'] if row['unit'] == 'ms' else row['time'] / 1000, axis=1) +combined_df.drop(['time', 'unit'], axis=1, inplace=True) + +measurement_types = combined_df['measurement'].unique() +for measurement in measurement_types: + create_histograms(combined_df, measurement)