compositing: Add memory reporter for WebRender. (#36557)

This adds a memory reporter for WebRender's memory usage. I seeded it
with a couple entries that looked reasonable based on
https://searchfox.org/mozilla-central/rev/2c71f1e9b5947612abdc16b64008162c58c1b9d3/gfx/thebes/gfxPlatform.cpp#722-738.

Testing: Verified that new numbers appear in about:memory for servo.org.
The new images category is surprisingly large (40mb).

Signed-off-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
Josh Matthews 2025-04-16 09:14:04 -04:00 committed by GitHub
parent afe98e9e1e
commit af000d6c91
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 98 additions and 8 deletions

4
Cargo.lock generated
View file

@ -1106,6 +1106,7 @@ dependencies = [
"pixels", "pixels",
"profile_traits", "profile_traits",
"script_traits", "script_traits",
"servo_allocator",
"servo_config", "servo_config",
"servo_geometry", "servo_geometry",
"stylo_traits", "stylo_traits",
@ -1114,6 +1115,7 @@ dependencies = [
"webrender", "webrender",
"webrender_api", "webrender_api",
"webxr", "webxr",
"wr_malloc_size_of",
] ]
[[package]] [[package]]
@ -1132,6 +1134,7 @@ dependencies = [
"ipc-channel", "ipc-channel",
"log", "log",
"pixels", "pixels",
"profile_traits",
"raw-window-handle", "raw-window-handle",
"serde", "serde",
"servo_geometry", "servo_geometry",
@ -4353,6 +4356,7 @@ dependencies = [
"servo-media", "servo-media",
"servo-media-dummy", "servo-media-dummy",
"servo-media-gstreamer", "servo-media-gstreamer",
"servo_allocator",
"servo_config", "servo_config",
"servo_geometry", "servo_geometry",
"servo_url", "servo_url",

View file

@ -35,6 +35,7 @@ net = { path = "../net" }
pixels = { path = "../pixels" } pixels = { path = "../pixels" }
profile_traits = { workspace = true } profile_traits = { workspace = true }
script_traits = { workspace = true } script_traits = { workspace = true }
servo_allocator = { path = "../allocator" }
servo_config = { path = "../config" } servo_config = { path = "../config" }
servo_geometry = { path = "../geometry" } servo_geometry = { path = "../geometry" }
stylo_traits = { workspace = true } stylo_traits = { workspace = true }
@ -42,6 +43,7 @@ tracing = { workspace = true, optional = true }
webrender = { workspace = true } webrender = { workspace = true }
webrender_api = { workspace = true } webrender_api = { workspace = true }
webxr = { path = "../webxr", optional = true } webxr = { path = "../webxr", optional = true }
wr_malloc_size_of = { workspace = true }
[dev-dependencies] [dev-dependencies]
surfman = { workspace = true } surfman = { workspace = true }

View file

@ -34,8 +34,9 @@ use ipc_channel::ipc::{self, IpcSharedMemory};
use libc::c_void; use libc::c_void;
use log::{debug, info, trace, warn}; use log::{debug, info, trace, warn};
use pixels::{CorsStatus, Image, ImageFrame, PixelFormat}; use pixels::{CorsStatus, Image, ImageFrame, PixelFormat};
use profile_traits::mem::{ProcessReports, ProfilerRegistration, Report, ReportKind};
use profile_traits::time::{self as profile_time, ProfilerCategory}; use profile_traits::time::{self as profile_time, ProfilerCategory};
use profile_traits::time_profile; use profile_traits::{path, time_profile};
use servo_config::opts; use servo_config::opts;
use servo_geometry::DeviceIndependentPixel; use servo_geometry::DeviceIndependentPixel;
use style_traits::CSSPixel; use style_traits::CSSPixel;
@ -147,6 +148,10 @@ pub struct IOCompositor {
/// The [`Instant`] of the last animation tick, used to avoid flooding the Constellation and /// The [`Instant`] of the last animation tick, used to avoid flooding the Constellation and
/// ScriptThread with a deluge of animation ticks. /// ScriptThread with a deluge of animation ticks.
last_animation_tick: Instant, last_animation_tick: Instant,
/// A handle to the memory profiler which will automatically unregister
/// when it's dropped.
_mem_profiler_registration: ProfilerRegistration,
} }
/// Why we need to be repainted. This is used for debugging. /// Why we need to be repainted. This is used for debugging.
@ -391,6 +396,11 @@ impl ServoRenderer {
impl IOCompositor { impl IOCompositor {
pub fn new(state: InitialCompositorState, convert_mouse_to_touch: bool) -> Self { pub fn new(state: InitialCompositorState, convert_mouse_to_touch: bool) -> Self {
let registration = state.mem_profiler_chan.prepare_memory_reporting(
"compositor".into(),
state.sender.clone(),
CompositorMsg::CollectMemoryReport,
);
let compositor = IOCompositor { let compositor = IOCompositor {
global: Rc::new(RefCell::new(ServoRenderer { global: Rc::new(RefCell::new(ServoRenderer {
shutdown_state: state.shutdown_state, shutdown_state: state.shutdown_state,
@ -414,6 +424,7 @@ impl IOCompositor {
rendering_context: state.rendering_context, rendering_context: state.rendering_context,
pending_frames: 0, pending_frames: 0,
last_animation_tick: Instant::now(), last_animation_tick: Instant::now(),
_mem_profiler_registration: registration,
}; };
{ {
@ -496,6 +507,30 @@ impl IOCompositor {
} }
match msg { match msg {
CompositorMsg::CollectMemoryReport(sender) => {
let ops =
wr_malloc_size_of::MallocSizeOfOps::new(servo_allocator::usable_size, None);
let report = self.global.borrow().webrender_api.report_memory(ops);
let reports = vec![
Report {
path: path!["webrender", "fonts"],
kind: ReportKind::ExplicitJemallocHeapSize,
size: report.fonts,
},
Report {
path: path!["webrender", "images"],
kind: ReportKind::ExplicitJemallocHeapSize,
size: report.images,
},
Report {
path: path!["webrender", "display-list"],
kind: ReportKind::ExplicitJemallocHeapSize,
size: report.display_list,
},
];
sender.send(ProcessReports::new(reports));
},
CompositorMsg::ChangeRunningAnimationsState( CompositorMsg::ChangeRunningAnimationsState(
webview_id, webview_id,
pipeline_id, pipeline_id,

View file

@ -57,6 +57,7 @@ mod from_constellation {
Self::GetClientWindowRect(..) => target!("GetClientWindowRect"), Self::GetClientWindowRect(..) => target!("GetClientWindowRect"),
Self::GetScreenSize(..) => target!("GetScreenSize"), Self::GetScreenSize(..) => target!("GetScreenSize"),
Self::GetAvailableScreenSize(..) => target!("GetAvailableScreenSize"), Self::GetAvailableScreenSize(..) => target!("GetAvailableScreenSize"),
Self::CollectMemoryReport(..) => target!("CollectMemoryReport"),
} }
} }
} }

View file

@ -96,6 +96,7 @@ serde = { workspace = true }
servo-media = { workspace = true } servo-media = { workspace = true }
servo-media-dummy = { workspace = true } servo-media-dummy = { workspace = true }
servo-media-gstreamer = { workspace = true, optional = true } servo-media-gstreamer = { workspace = true, optional = true }
servo_allocator = { path = "../allocator" }
servo_config = { path = "../config" } servo_config = { path = "../config" }
servo_geometry = { path = "../geometry" } servo_geometry = { path = "../geometry" }
servo_url = { path = "../url" } servo_url = { path = "../url" }

View file

@ -373,6 +373,7 @@ impl Servo {
clear_color, clear_color,
upload_method, upload_method,
workers, workers,
size_of_op: Some(servo_allocator::usable_size),
..Default::default() ..Default::default()
}, },
None, None,

View file

@ -27,6 +27,7 @@ image = { workspace = true }
ipc-channel = { workspace = true } ipc-channel = { workspace = true }
log = { workspace = true } log = { workspace = true }
pixels = { path = '../../pixels' } pixels = { path = '../../pixels' }
profile_traits = { path = '../profile' }
raw-window-handle = { version = "0.6" } raw-window-handle = { version = "0.6" }
serde = { workspace = true } serde = { workspace = true }
servo_geometry = { path = "../../geometry" } servo_geometry = { path = "../../geometry" }

View file

@ -29,6 +29,7 @@ use display_list::CompositorDisplayListInfo;
use embedder_traits::{CompositorHitTestResult, ScreenGeometry}; use embedder_traits::{CompositorHitTestResult, ScreenGeometry};
use euclid::default::Size2D as UntypedSize2D; use euclid::default::Size2D as UntypedSize2D;
use ipc_channel::ipc::{self, IpcSharedMemory}; use ipc_channel::ipc::{self, IpcSharedMemory};
use profile_traits::mem::{OpaqueSender, ReportsChan};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use servo_geometry::{DeviceIndependentIntRect, DeviceIndependentIntSize}; use servo_geometry::{DeviceIndependentIntRect, DeviceIndependentIntSize};
use webrender_api::units::{DevicePoint, LayoutPoint, TexelRect}; use webrender_api::units::{DevicePoint, LayoutPoint, TexelRect};
@ -50,6 +51,12 @@ pub struct CompositorProxy {
pub event_loop_waker: Box<dyn EventLoopWaker>, pub event_loop_waker: Box<dyn EventLoopWaker>,
} }
impl OpaqueSender<CompositorMsg> for CompositorProxy {
fn send(&self, message: CompositorMsg) {
CompositorProxy::send(self, message)
}
}
impl CompositorProxy { impl CompositorProxy {
pub fn send(&self, msg: CompositorMsg) { pub fn send(&self, msg: CompositorMsg) {
if let Err(err) = self.sender.send(msg) { if let Err(err) = self.sender.send(msg) {
@ -154,6 +161,10 @@ pub enum CompositorMsg {
/// Get the available screen size (without toolbars and docks) for the screen /// Get the available screen size (without toolbars and docks) for the screen
/// the client window inhabits. /// the client window inhabits.
GetAvailableScreenSize(WebViewId, IpcSender<DeviceIndependentIntSize>), GetAvailableScreenSize(WebViewId, IpcSender<DeviceIndependentIntSize>),
/// Measure the current memory usage associated with the compositor.
/// The report must be sent on the provided channel once it's complete.
CollectMemoryReport(ReportsChan),
} }
impl Debug for CompositorMsg { impl Debug for CompositorMsg {

View file

@ -50,6 +50,21 @@ where
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ProfilerChan(pub IpcSender<ProfilerMsg>); pub struct ProfilerChan(pub IpcSender<ProfilerMsg>);
/// A handle that encompasses a registration with the memory profiler.
/// The registration is tied to the lifetime of this type; the memory
/// profiler unregister the reporter when this object is dropped.
pub struct ProfilerRegistration {
sender: ProfilerChan,
reporter_name: String,
}
impl Drop for ProfilerRegistration {
fn drop(&mut self) {
self.sender
.send(ProfilerMsg::UnregisterReporter(self.reporter_name.clone()));
}
}
impl ProfilerChan { impl ProfilerChan {
/// Send `msg` on this `IpcSender`. /// Send `msg` on this `IpcSender`.
/// ///
@ -60,15 +75,15 @@ impl ProfilerChan {
} }
} }
/// Runs `f()` with memory profiling. /// Register a new reporter and return a handle to automatically
pub fn run_with_memory_reporting<F, M, T, C>( /// unregister it in the future.
pub fn prepare_memory_reporting<M, T, C>(
&self, &self,
f: F,
reporter_name: String, reporter_name: String,
channel_for_reporter: C, channel_for_reporter: C,
msg: M, msg: M,
) where ) -> ProfilerRegistration
F: FnOnce(), where
M: Fn(ReportsChan) -> T + Send + 'static, M: Fn(ReportsChan) -> T + Send + 'static,
T: Send + 'static, T: Send + 'static,
C: OpaqueSender<T> + Send + 'static, C: OpaqueSender<T> + Send + 'static,
@ -88,9 +103,28 @@ impl ProfilerChan {
Reporter(reporter_sender), Reporter(reporter_sender),
)); ));
f(); ProfilerRegistration {
sender: self.clone(),
reporter_name,
}
}
self.send(ProfilerMsg::UnregisterReporter(reporter_name)); /// Runs `f()` with memory profiling.
pub fn run_with_memory_reporting<F, M, T, C>(
&self,
f: F,
reporter_name: String,
channel_for_reporter: C,
msg: M,
) where
F: FnOnce(),
M: Fn(ReportsChan) -> T + Send + 'static,
T: Send + 'static,
C: OpaqueSender<T> + Send + 'static,
{
let _registration = self.prepare_memory_reporting(reporter_name, channel_for_reporter, msg);
f();
} }
} }