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

View file

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

View file

@ -34,8 +34,9 @@ use ipc_channel::ipc::{self, IpcSharedMemory};
use libc::c_void;
use log::{debug, info, trace, warn};
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_profile;
use profile_traits::{path, time_profile};
use servo_config::opts;
use servo_geometry::DeviceIndependentPixel;
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
/// ScriptThread with a deluge of animation ticks.
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.
@ -391,6 +396,11 @@ impl ServoRenderer {
impl IOCompositor {
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 {
global: Rc::new(RefCell::new(ServoRenderer {
shutdown_state: state.shutdown_state,
@ -414,6 +424,7 @@ impl IOCompositor {
rendering_context: state.rendering_context,
pending_frames: 0,
last_animation_tick: Instant::now(),
_mem_profiler_registration: registration,
};
{
@ -496,6 +507,30 @@ impl IOCompositor {
}
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(
webview_id,
pipeline_id,

View file

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

View file

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

View file

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

View file

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

View file

@ -29,6 +29,7 @@ use display_list::CompositorDisplayListInfo;
use embedder_traits::{CompositorHitTestResult, ScreenGeometry};
use euclid::default::Size2D as UntypedSize2D;
use ipc_channel::ipc::{self, IpcSharedMemory};
use profile_traits::mem::{OpaqueSender, ReportsChan};
use serde::{Deserialize, Serialize};
use servo_geometry::{DeviceIndependentIntRect, DeviceIndependentIntSize};
use webrender_api::units::{DevicePoint, LayoutPoint, TexelRect};
@ -50,6 +51,12 @@ pub struct CompositorProxy {
pub event_loop_waker: Box<dyn EventLoopWaker>,
}
impl OpaqueSender<CompositorMsg> for CompositorProxy {
fn send(&self, message: CompositorMsg) {
CompositorProxy::send(self, message)
}
}
impl CompositorProxy {
pub fn send(&self, msg: CompositorMsg) {
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
/// the client window inhabits.
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 {

View file

@ -50,6 +50,21 @@ where
#[derive(Clone, Debug, Deserialize, Serialize)]
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 {
/// Send `msg` on this `IpcSender`.
///
@ -60,15 +75,15 @@ impl ProfilerChan {
}
}
/// Runs `f()` with memory profiling.
pub fn run_with_memory_reporting<F, M, T, C>(
/// Register a new reporter and return a handle to automatically
/// unregister it in the future.
pub fn prepare_memory_reporting<M, T, C>(
&self,
f: F,
reporter_name: String,
channel_for_reporter: C,
msg: M,
) where
F: FnOnce(),
) -> ProfilerRegistration
where
M: Fn(ReportsChan) -> T + Send + 'static,
T: Send + 'static,
C: OpaqueSender<T> + Send + 'static,
@ -88,9 +103,28 @@ impl ProfilerChan {
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();
}
}