Measure cache memory usage (#19251):

Made the memory cache data structure derive MallocSizeOf, along with
manual size_of() implementations in malloc_size_of.

Added a Measurable struct that acts as a container for fields size_of() can be called for.

Added a new IpcReceiver used for listening to messages from the memory profiler,
and used run_with_memory reporting to register a memory reporter in the thread.
Now when a message from the memory profiler arrives, report includes sizes of public and private http caches.

Updated test file.
This commit is contained in:
modal-d17 2018-02-20 16:00:16 -05:00 committed by modal17
parent d232705106
commit af445a357d
13 changed files with 312 additions and 76 deletions

View file

@ -14,14 +14,17 @@ use hyper::header::Headers;
use hyper::method::Method;
use hyper::status::StatusCode;
use hyper_serde::Serde;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalSizeOf, MallocUnconditionalShallowSizeOf};
use malloc_size_of::Measurable;
use net_traits::{Metadata, FetchMetadata};
use net_traits::request::Request;
use net_traits::response::{HttpsState, Response, ResponseBody};
use servo_arc::Arc;
use servo_config::prefs::PREFS;
use servo_url::ServoUrl;
use std::collections::HashMap;
use std::str;
use std::sync::{Arc, Mutex};
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, Sender};
use time;
@ -29,7 +32,7 @@ use time::{Duration, Tm};
/// The key used to differentiate requests in the cache.
#[derive(Clone, Eq, Hash, PartialEq)]
#[derive(Clone, Eq, Hash, MallocSizeOf, PartialEq )]
pub struct CacheKey {
url: ServoUrl
}
@ -56,9 +59,16 @@ impl CacheKey {
/// A complete cached resource.
#[derive(Clone)]
struct CachedResource {
metadata: CachedMetadata,
request_headers: Arc<Mutex<Headers>>,
body: Arc<Mutex<ResponseBody>>,
aborted: Arc<AtomicBool>,
awaiting_body: Arc<Mutex<Vec<Sender<Data>>>>,
data: Measurable<MeasurableCachedResource>
}
#[derive(Clone, MallocSizeOf)]
struct MeasurableCachedResource {
metadata: CachedMetadata,
location_url: Option<Result<ServoUrl, String>>,
https_state: HttpsState,
status: Option<StatusCode>,
@ -66,25 +76,47 @@ struct CachedResource {
url_list: Vec<ServoUrl>,
expires: Duration,
last_validated: Tm,
aborted: Arc<AtomicBool>,
awaiting_body: Arc<Mutex<Vec<Sender<Data>>>>
}
impl MallocSizeOf for CachedResource {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
self.request_headers.unconditional_size_of(ops) +
self.body.unconditional_size_of(ops) +
self.aborted.unconditional_size_of(ops) +
self.awaiting_body.unconditional_size_of(ops) +
self.data.size_of(ops)
}
}
/// Metadata about a loaded resource, such as is obtained from HTTP headers.
#[derive(Clone)]
struct CachedMetadata {
/// Headers
pub headers: Arc<Mutex<Headers>>,
/// Fields that implement MallocSizeOf
pub data: Measurable<MeasurableCachedMetadata>
}
#[derive(Clone, MallocSizeOf)]
struct MeasurableCachedMetadata {
/// Final URL after redirects.
pub final_url: ServoUrl,
/// MIME type / subtype.
pub content_type: Option<Serde<ContentType>>,
/// Character set.
pub charset: Option<String>,
/// Headers
pub headers: Arc<Mutex<Headers>>,
/// HTTP Status
pub status: Option<(u16, Vec<u8>)>
}
impl MallocSizeOf for CachedMetadata {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
self.headers.unconditional_shallow_size_of(ops) +
self.headers.size_of(ops) +
self.data.size_of(ops)
}
}
/// Wrapper around a cached response, including information on re-validation needs
pub struct CachedResponse {
/// The response constructed from the cached resource
@ -94,6 +126,7 @@ pub struct CachedResponse {
}
/// A memory cache.
#[derive(MallocSizeOf)]
pub struct HttpCache {
/// cached responses.
entries: HashMap<CacheKey, Vec<CachedResource>>,
@ -278,7 +311,7 @@ fn create_cached_response(request: &Request,
cached_headers: &Headers,
done_chan: &mut DoneChannel)
-> CachedResponse {
let mut response = Response::new(cached_resource.metadata.final_url.clone());
let mut response = Response::new(cached_resource.data.metadata.data.final_url.clone());
response.headers = cached_headers.clone();
response.body = cached_resource.body.clone();
if let ResponseBody::Receiving(_) = *cached_resource.body.lock().unwrap() {
@ -286,18 +319,18 @@ fn create_cached_response(request: &Request,
*done_chan = Some((done_sender.clone(), done_receiver));
cached_resource.awaiting_body.lock().unwrap().push(done_sender);
}
response.location_url = cached_resource.location_url.clone();
response.status = cached_resource.status.clone();
response.raw_status = cached_resource.raw_status.clone();
response.url_list = cached_resource.url_list.clone();
response.https_state = cached_resource.https_state.clone();
response.location_url = cached_resource.data.location_url.clone();
response.status = cached_resource.data.status.clone();
response.raw_status = cached_resource.data.raw_status.clone();
response.url_list = cached_resource.data.url_list.clone();
response.https_state = cached_resource.data.https_state.clone();
response.referrer = request.referrer.to_url().cloned();
response.referrer_policy = request.referrer_policy.clone();
response.aborted = cached_resource.aborted.clone();
let expires = cached_resource.expires;
let expires = cached_resource.data.expires;
let adjusted_expires = get_expiry_adjustment_from_request_headers(request, expires);
let now = Duration::seconds(time::now().to_timespec().sec);
let last_validated = Duration::seconds(cached_resource.last_validated.to_timespec().sec);
let last_validated = Duration::seconds(cached_resource.data.last_validated.to_timespec().sec);
let time_since_validated = now - last_validated;
// TODO: take must-revalidate into account <https://tools.ietf.org/html/rfc7234#section-5.2.2.1>
// TODO: if this cache is to be considered shared, take proxy-revalidate into account
@ -312,18 +345,20 @@ fn create_cached_response(request: &Request,
fn create_resource_with_bytes_from_resource(bytes: &[u8], resource: &CachedResource)
-> CachedResource {
CachedResource {
metadata: resource.metadata.clone(),
request_headers: resource.request_headers.clone(),
body: Arc::new(Mutex::new(ResponseBody::Done(bytes.to_owned()))),
location_url: resource.location_url.clone(),
https_state: resource.https_state.clone(),
status: Some(StatusCode::PartialContent),
raw_status: Some((206, b"Partial Content".to_vec())),
url_list: resource.url_list.clone(),
expires: resource.expires.clone(),
last_validated: resource.last_validated.clone(),
aborted: Arc::new(AtomicBool::new(false)),
awaiting_body: Arc::new(Mutex::new(vec![]))
awaiting_body: Arc::new(Mutex::new(vec![])),
data: Measurable(MeasurableCachedResource {
metadata: resource.data.metadata.clone(),
location_url: resource.data.location_url.clone(),
https_state: resource.data.https_state.clone(),
status: Some(StatusCode::PartialContent),
raw_status: Some((206, b"Partial Content".to_vec())),
url_list: resource.data.url_list.clone(),
expires: resource.data.expires.clone(),
last_validated: resource.data.last_validated.clone(),
})
}
}
@ -334,13 +369,13 @@ fn handle_range_request(request: &Request,
done_chan: &mut DoneChannel)
-> Option<CachedResponse> {
let mut complete_cached_resources = candidates.iter().filter(|resource| {
match resource.raw_status {
match resource.data.raw_status {
Some((ref code, _)) => *code == 200,
None => false
}
});
let partial_cached_resources = candidates.iter().filter(|resource| {
match resource.raw_status {
match resource.data.raw_status {
Some((ref code, _)) => *code == 206,
None => false
}
@ -361,7 +396,7 @@ fn handle_range_request(request: &Request,
let requested = body.get(b..e);
if let Some(bytes) = requested {
let new_resource = create_resource_with_bytes_from_resource(bytes, complete_resource);
let cached_headers = new_resource.metadata.headers.lock().unwrap();
let cached_headers = new_resource.data.metadata.headers.lock().unwrap();
let cached_response = create_cached_response(request, &new_resource, &*cached_headers, done_chan);
return Some(cached_response);
}
@ -369,7 +404,7 @@ fn handle_range_request(request: &Request,
},
(&header::ByteRangeSpec::FromTo(beginning, end), None) => {
for partial_resource in partial_cached_resources {
let headers = partial_resource.metadata.headers.lock().unwrap();
let headers = partial_resource.data.metadata.headers.lock().unwrap();
let content_range = headers.get::<header::ContentRange>();
let (res_beginning, res_end) = match content_range {
Some(&header::ContentRange(
@ -401,7 +436,7 @@ fn handle_range_request(request: &Request,
let requested = body.get(b..);
if let Some(bytes) = requested {
let new_resource = create_resource_with_bytes_from_resource(bytes, complete_resource);
let cached_headers = new_resource.metadata.headers.lock().unwrap();
let cached_headers = new_resource.data.metadata.headers.lock().unwrap();
let cached_response = create_cached_response(request, &new_resource, &*cached_headers, done_chan);
return Some(cached_response);
}
@ -409,7 +444,7 @@ fn handle_range_request(request: &Request,
},
(&header::ByteRangeSpec::AllFrom(beginning), None) => {
for partial_resource in partial_cached_resources {
let headers = partial_resource.metadata.headers.lock().unwrap();
let headers = partial_resource.data.metadata.headers.lock().unwrap();
let content_range = headers.get::<header::ContentRange>();
let (res_beginning, res_end, total) = match content_range {
Some(&header::ContentRange(
@ -441,7 +476,7 @@ fn handle_range_request(request: &Request,
let requested = body.get(from_byte..);
if let Some(bytes) = requested {
let new_resource = create_resource_with_bytes_from_resource(bytes, complete_resource);
let cached_headers = new_resource.metadata.headers.lock().unwrap();
let cached_headers = new_resource.data.metadata.headers.lock().unwrap();
let cached_response = create_cached_response(request, &new_resource, &*cached_headers, done_chan);
return Some(cached_response);
}
@ -449,7 +484,7 @@ fn handle_range_request(request: &Request,
},
(&header::ByteRangeSpec::Last(offset), None) => {
for partial_resource in partial_cached_resources {
let headers = partial_resource.metadata.headers.lock().unwrap();
let headers = partial_resource.data.metadata.headers.lock().unwrap();
let content_range = headers.get::<header::ContentRange>();
let (res_beginning, res_end, total) = match content_range {
Some(&header::ContentRange(
@ -501,7 +536,7 @@ impl HttpCache {
let mut candidates = vec![];
for cached_resource in resources {
let mut can_be_constructed = true;
let cached_headers = cached_resource.metadata.headers.lock().unwrap();
let cached_headers = cached_resource.data.metadata.headers.lock().unwrap();
let original_request_headers = cached_resource.request_headers.lock().unwrap();
if let Some(vary_data) = cached_headers.get_raw("Vary") {
// Calculating Secondary Keys with Vary <https://tools.ietf.org/html/rfc7234#section-4.1>
@ -554,7 +589,7 @@ impl HttpCache {
// Returning the first response that can be constructed
// TODO: select the most appropriate one, using a known mechanism from a selecting header field,
// or using the Date header to return the most recent one.
let cached_headers = cached_resource.metadata.headers.lock().unwrap();
let cached_headers = cached_resource.data.metadata.headers.lock().unwrap();
let cached_response = create_cached_response(request, cached_resource, &*cached_headers, done_chan);
return Some(cached_response);
}
@ -589,24 +624,24 @@ impl HttpCache {
let entry_key = CacheKey::new(request.clone());
if let Some(cached_resources) = self.entries.get_mut(&entry_key) {
for cached_resource in cached_resources.iter_mut() {
let mut stored_headers = cached_resource.metadata.headers.lock().unwrap();
// Received a response with 304 status code, in response to a request that matches a cached resource.
// 1. update the headers of the cached resource.
// 2. return a response, constructed from the cached resource.
stored_headers.extend(response.headers.iter());
let mut constructed_response = Response::new(cached_resource.metadata.final_url.clone());
constructed_response.headers = stored_headers.clone();
let mut constructed_response = Response::new(cached_resource.data.metadata.data.final_url.clone());
constructed_response.body = cached_resource.body.clone();
constructed_response.status = cached_resource.status.clone();
constructed_response.https_state = cached_resource.https_state.clone();
constructed_response.status = cached_resource.data.status.clone();
constructed_response.https_state = cached_resource.data.https_state.clone();
constructed_response.referrer = request.referrer.to_url().cloned();
constructed_response.referrer_policy = request.referrer_policy.clone();
constructed_response.raw_status = cached_resource.raw_status.clone();
constructed_response.url_list = cached_resource.url_list.clone();
constructed_response.raw_status = cached_resource.data.raw_status.clone();
constructed_response.url_list = cached_resource.data.url_list.clone();
// done_chan will have been set to Some by http_network_fetch,
// set it back to None since the response returned here replaces the 304 one from the network.
*done_chan = None;
cached_resource.expires = get_response_expiry(&constructed_response);
cached_resource.data.expires = get_response_expiry(&constructed_response);
let mut stored_headers = cached_resource.data.metadata.headers.lock().unwrap();
stored_headers.extend(response.headers.iter());
constructed_response.headers = stored_headers.clone();
return Some(constructed_response);
}
}
@ -617,7 +652,7 @@ impl HttpCache {
let entry_key = CacheKey::from_servo_url(url);
if let Some(cached_resources) = self.entries.get_mut(&entry_key) {
for cached_resource in cached_resources.iter_mut() {
cached_resource.expires = Duration::seconds(0i64);
cached_resource.data.expires = Duration::seconds(0i64);
}
}
}
@ -664,25 +699,29 @@ impl HttpCache {
}
let expiry = get_response_expiry(&response);
let cacheable_metadata = CachedMetadata {
final_url: metadata.final_url,
content_type: metadata.content_type,
charset: metadata.charset,
status: metadata.status,
headers: Arc::new(Mutex::new(response.headers.clone()))
headers: Arc::new(Mutex::new(response.headers.clone())),
data: Measurable(MeasurableCachedMetadata {
final_url: metadata.final_url,
content_type: metadata.content_type,
charset: metadata.charset,
status: metadata.status
})
};
let entry_resource = CachedResource {
metadata: cacheable_metadata,
request_headers: Arc::new(Mutex::new(request.headers.clone())),
body: response.body.clone(),
location_url: response.location_url.clone(),
https_state: response.https_state.clone(),
status: response.status.clone(),
raw_status: response.raw_status.clone(),
url_list: response.url_list.clone(),
expires: expiry,
last_validated: time::now(),
aborted: response.aborted.clone(),
awaiting_body: Arc::new(Mutex::new(vec![]))
awaiting_body: Arc::new(Mutex::new(vec![])),
data: Measurable(MeasurableCachedResource {
metadata: cacheable_metadata,
location_url: response.location_url.clone(),
https_state: response.https_state.clone(),
status: response.status.clone(),
raw_status: response.raw_status.clone(),
url_list: response.url_list.clone(),
expires: expiry,
last_validated: time::now()
})
};
let entry = self.entries.entry(entry_key).or_insert(vec![]);
entry.push(entry_resource);