use std::cell::{Cell, RefCell};
use std::cmp::Ordering;
use std::collections::HashMap;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use gfx_traits::Epoch;
use ipc_channel::ipc::IpcSender;
use log::warn;
use malloc_size_of_derive::MallocSizeOf;
use msg::constellation_msg::PipelineId;
use profile_traits::time::{send_profile_data, ProfilerCategory, ProfilerChan, TimerMetadata};
use script_traits::{ConstellationControlMsg, LayoutMsg, ProgressiveWebMetricType};
use servo_config::opts;
use servo_url::ServoUrl;
pub trait ProfilerMetadataFactory {
fn new_metadata(&self) -> Option<TimerMetadata>;
}
pub trait ProgressiveWebMetric {
fn get_navigation_start(&self) -> Option<u64>;
fn set_navigation_start(&mut self, time: u64);
fn get_time_profiler_chan(&self) -> &ProfilerChan;
fn send_queued_constellation_msg(&self, name: ProgressiveWebMetricType, time: u64);
fn get_url(&self) -> &ServoUrl;
}
pub const MAX_TASK_NS: u64 = 50000000;
const INTERACTIVE_WINDOW_SECONDS: Duration = Duration::from_secs(10);
pub trait ToMs<T> {
fn to_ms(&self) -> T;
}
impl ToMs<f64> for u64 {
fn to_ms(&self) -> f64 {
*self as f64 / 1000000.
}
}
fn set_metric<U: ProgressiveWebMetric>(
pwm: &U,
metadata: Option<TimerMetadata>,
metric_type: ProgressiveWebMetricType,
category: ProfilerCategory,
attr: &Cell<Option<u64>>,
metric_time: Option<u64>,
url: &ServoUrl,
) {
let navigation_start = match pwm.get_navigation_start() {
Some(time) => time,
None => {
warn!("Trying to set metric before navigation start");
return;
},
};
let now = match metric_time {
Some(time) => time,
None => SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64,
};
let time = now - navigation_start;
attr.set(Some(time));
pwm.send_queued_constellation_msg(metric_type, time);
send_profile_data(category, metadata, pwm.get_time_profiler_chan(), time, time);
if opts::get().print_pwm {
println!(
"Navigation start: {}",
pwm.get_navigation_start().unwrap().to_ms()
);
println!("{:?} {:?} {:?}", url, metric_type, time.to_ms());
}
}
#[derive(MallocSizeOf)]
pub struct InteractiveMetrics {
navigation_start: Option<u64>,
dom_content_loaded: Cell<Option<u64>>,
main_thread_available: Cell<Option<u64>>,
time_to_interactive: Cell<Option<u64>>,
#[ignore_malloc_size_of = "can't measure channels"]
time_profiler_chan: ProfilerChan,
url: ServoUrl,
}
#[derive(Clone, Copy, Debug, MallocSizeOf)]
pub struct InteractiveWindow {
start: SystemTime,
}
impl Default for InteractiveWindow {
fn default() -> Self {
Self {
start: SystemTime::now(),
}
}
}
impl InteractiveWindow {
pub fn start_window(&mut self) {
self.start = SystemTime::now();
}
pub fn needs_check(&self) -> bool {
SystemTime::now()
.duration_since(self.start)
.unwrap_or_default() >=
INTERACTIVE_WINDOW_SECONDS
}
pub fn get_start(&self) -> u64 {
self.start
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64
}
}
#[derive(Debug)]
pub enum InteractiveFlag {
DOMContentLoaded,
TimeToInteractive(u64),
}
impl InteractiveMetrics {
pub fn new(time_profiler_chan: ProfilerChan, url: ServoUrl) -> InteractiveMetrics {
InteractiveMetrics {
navigation_start: None,
dom_content_loaded: Cell::new(None),
main_thread_available: Cell::new(None),
time_to_interactive: Cell::new(None),
time_profiler_chan,
url,
}
}
pub fn set_dom_content_loaded(&self) {
if self.dom_content_loaded.get().is_none() {
self.dom_content_loaded.set(Some(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64,
));
}
}
pub fn set_main_thread_available(&self, time: u64) {
if self.main_thread_available.get().is_none() {
self.main_thread_available.set(Some(time));
}
}
pub fn get_dom_content_loaded(&self) -> Option<u64> {
self.dom_content_loaded.get()
}
pub fn get_main_thread_available(&self) -> Option<u64> {
self.main_thread_available.get()
}
pub fn maybe_set_tti<T>(&self, profiler_metadata_factory: &T, metric: InteractiveFlag)
where
T: ProfilerMetadataFactory,
{
if self.get_tti().is_some() {
return;
}
match metric {
InteractiveFlag::DOMContentLoaded => self.set_dom_content_loaded(),
InteractiveFlag::TimeToInteractive(time) => self.set_main_thread_available(time),
}
let dcl = self.dom_content_loaded.get();
let mta = self.main_thread_available.get();
let (dcl, mta) = match (dcl, mta) {
(Some(dcl), Some(mta)) => (dcl, mta),
_ => return,
};
let metric_time = match dcl.partial_cmp(&mta) {
Some(Ordering::Less) => mta,
Some(_) => dcl,
None => panic!("no ordering possible. something bad happened"),
};
set_metric(
self,
profiler_metadata_factory.new_metadata(),
ProgressiveWebMetricType::TimeToInteractive,
ProfilerCategory::TimeToInteractive,
&self.time_to_interactive,
Some(metric_time),
&self.url,
);
}
pub fn get_tti(&self) -> Option<u64> {
self.time_to_interactive.get()
}
pub fn needs_tti(&self) -> bool {
self.get_tti().is_none()
}
}
impl ProgressiveWebMetric for InteractiveMetrics {
fn get_navigation_start(&self) -> Option<u64> {
self.navigation_start
}
fn set_navigation_start(&mut self, time: u64) {
self.navigation_start = Some(time);
}
fn send_queued_constellation_msg(&self, _name: ProgressiveWebMetricType, _time: u64) {}
fn get_time_profiler_chan(&self) -> &ProfilerChan {
&self.time_profiler_chan
}
fn get_url(&self) -> &ServoUrl {
&self.url
}
}
pub struct PaintTimeMetrics {
pending_metrics: RefCell<HashMap<Epoch, (Option<TimerMetadata>, bool)>>,
navigation_start: u64,
first_paint: Cell<Option<u64>>,
first_contentful_paint: Cell<Option<u64>>,
pipeline_id: PipelineId,
time_profiler_chan: ProfilerChan,
constellation_chan: IpcSender<LayoutMsg>,
script_chan: IpcSender<ConstellationControlMsg>,
url: ServoUrl,
}
impl PaintTimeMetrics {
pub fn new(
pipeline_id: PipelineId,
time_profiler_chan: ProfilerChan,
constellation_chan: IpcSender<LayoutMsg>,
script_chan: IpcSender<ConstellationControlMsg>,
url: ServoUrl,
navigation_start: u64,
) -> PaintTimeMetrics {
PaintTimeMetrics {
pending_metrics: RefCell::new(HashMap::new()),
navigation_start,
first_paint: Cell::new(None),
first_contentful_paint: Cell::new(None),
pipeline_id,
time_profiler_chan,
constellation_chan,
script_chan,
url,
}
}
pub fn maybe_set_first_paint<T>(&self, profiler_metadata_factory: &T)
where
T: ProfilerMetadataFactory,
{
if self.first_paint.get().is_some() {
return;
}
set_metric(
self,
profiler_metadata_factory.new_metadata(),
ProgressiveWebMetricType::FirstPaint,
ProfilerCategory::TimeToFirstPaint,
&self.first_paint,
None,
&self.url,
);
}
pub fn maybe_observe_paint_time<T>(
&self,
profiler_metadata_factory: &T,
epoch: Epoch,
display_list_is_contentful: bool,
) where
T: ProfilerMetadataFactory,
{
if self.first_paint.get().is_some() && self.first_contentful_paint.get().is_some() {
return;
}
self.pending_metrics.borrow_mut().insert(
epoch,
(
profiler_metadata_factory.new_metadata(),
display_list_is_contentful,
),
);
let msg = LayoutMsg::PendingPaintMetric(self.pipeline_id, epoch);
if let Err(e) = self.constellation_chan.send(msg) {
warn!("Failed to send PendingPaintMetric {:?}", e);
}
}
pub fn maybe_set_metric(&self, epoch: Epoch, paint_time: u64) {
if self.first_paint.get().is_some() && self.first_contentful_paint.get().is_some() {
return;
}
if let Some(pending_metric) = self.pending_metrics.borrow_mut().remove(&epoch) {
let profiler_metadata = pending_metric.0;
set_metric(
self,
profiler_metadata.clone(),
ProgressiveWebMetricType::FirstPaint,
ProfilerCategory::TimeToFirstPaint,
&self.first_paint,
Some(paint_time),
&self.url,
);
if pending_metric.1 {
set_metric(
self,
profiler_metadata,
ProgressiveWebMetricType::FirstContentfulPaint,
ProfilerCategory::TimeToFirstContentfulPaint,
&self.first_contentful_paint,
Some(paint_time),
&self.url,
);
}
}
}
pub fn get_first_paint(&self) -> Option<u64> {
self.first_paint.get()
}
pub fn get_first_contentful_paint(&self) -> Option<u64> {
self.first_contentful_paint.get()
}
}
impl ProgressiveWebMetric for PaintTimeMetrics {
fn get_navigation_start(&self) -> Option<u64> {
Some(self.navigation_start)
}
fn set_navigation_start(&mut self, time: u64) {
self.navigation_start = time;
}
fn send_queued_constellation_msg(&self, name: ProgressiveWebMetricType, time: u64) {
let msg = ConstellationControlMsg::PaintMetric(self.pipeline_id, name, time);
if let Err(e) = self.script_chan.send(msg) {
warn!("Sending metric to script thread failed ({}).", e);
}
}
fn get_time_profiler_chan(&self) -> &ProfilerChan {
&self.time_profiler_chan
}
fn get_url(&self) -> &ServoUrl {
&self.url
}
}