Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read smaps in the memory profiler on Linux #4894

Merged
merged 2 commits into from Feb 24, 2015
Merged
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Prev

Report detailed RSS measurements from /proc/<pid>/smaps on Linux.

All anonymous segments are aggregated into a single measurement, as are
all segments smaller than 512 KiB.

Example output:

      142.89: resident-according-to-smaps
       97.84: - anonymous (rw-p)
       23.98: - /home/njn/moz/servo/components/servo/target/servo (r-xp)
        6.58: - [heap] (rw-p)
        5.36: - other
        3.51: - /usr/lib/x86_64-linux-gnu/dri/i965_dri.so (r-xp)
        1.33: - /lib/x86_64-linux-gnu/libc-2.19.so (r-xp)
        0.93: - /home/njn/moz/servo/components/servo/target/servo (r--p)
        0.76: - /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.20 (r-xp)
        0.74: - /usr/lib/x86_64-linux-gnu/libX11.so.6.3.0 (r-xp)
        0.50: - /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 (r-xp)
        0.50: - /lib/x86_64-linux-gnu/libglib-2.0.so.0.4200.1 (r-xp)
        0.45: - /usr/lib/x86_64-linux-gnu/mesa/libGL.so.1.2.0 (r-xp)
        0.43: - /lib/x86_64-linux-gnu/libm-2.19.so (r-xp)
  • Loading branch information
nnethercote committed Feb 23, 2015
commit 34a384241aae7dc79a500cdd6738a11b560d8352

Some generated files are not rendered by default. Learn more.

@@ -43,10 +43,11 @@ git = "https://github.com/servo/string-cache"
git = "https://github.com/Kimundi/lazy-static.rs"

[dependencies]
text_writer = "0.1.1"
url = "0.2.16"
time = "0.1.12"
bitflags = "*"
libc = "*"
rand = "*"
regex = "0.1.14"
rustc-serialize = "0.2"
libc = "*"
text_writer = "0.1.1"
time = "0.1.12"
url = "0.2.16"
@@ -32,6 +32,8 @@ extern crate layers;
extern crate libc;
#[no_link] #[macro_use] extern crate cssparser;
extern crate rand;
#[cfg(target_os="linux")]
extern crate regex;
extern crate "rustc-serialize" as rustc_serialize;
#[cfg(target_os="macos")]
extern crate task_info;
@@ -11,8 +11,6 @@ use std::old_io::timer::sleep;
#[cfg(target_os="linux")]
use std::old_io::File;
use std::mem::size_of;
#[cfg(target_os="linux")]
use std::env::page_size;
use std::ptr::null_mut;
use std::sync::mpsc::{Sender, channel, Receiver};
use std::time::duration::Duration;
@@ -126,6 +124,10 @@ impl MemoryProfiler {
MemoryProfiler::print_measurement("vsize", get_vsize());
MemoryProfiler::print_measurement("resident", get_resident());

for seg in get_resident_segments().iter() {
MemoryProfiler::print_measurement(seg.0.as_slice(), Some(seg.1));
}

// Total number of bytes allocated by the application on the system
// heap.
MemoryProfiler::print_measurement("system-heap-allocated",
@@ -244,8 +246,8 @@ fn get_proc_self_statm_field(field: uint) -> Option<u64> {
match f.read_to_string() {
Ok(contents) => {
let s = option_try!(contents.as_slice().words().nth(field));
let npages: u64 = option_try!(s.parse().ok());
Some(npages * (page_size() as u64))
let npages = option_try!(s.parse::<u64>().ok());
Some(npages * (::std::env::page_size() as u64))
}
Err(_) => None
}
@@ -280,3 +282,115 @@ fn get_vsize() -> Option<u64> {
fn get_resident() -> Option<u64> {
None
}

#[cfg(target_os="linux")]
fn get_resident_segments() -> Vec<(String, u64)> {
use regex::Regex;
use std::collections::HashMap;
use std::collections::hash_map::Entry;

// The first line of an entry in /proc/<pid>/smaps looks just like an entry
// in /proc/<pid>/maps:
//
// address perms offset dev inode pathname
// 02366000-025d8000 rw-p 00000000 00:00 0 [heap]
//
// Each of the following lines contains a key and a value, separated
// by ": ", where the key does not contain either of those characters.
// For example:
//
// Rss: 132 kB

let path = Path::new("/proc/self/smaps");
let mut f = ::std::old_io::BufferedReader::new(File::open(&path));

let seg_re = Regex::new(
r"^[:xdigit:]+-[:xdigit:]+ (....) [:xdigit:]+ [:xdigit:]+:[:xdigit:]+ \d+ +(.*)").unwrap();
let rss_re = Regex::new(r"^Rss: +(\d+) kB").unwrap();

// We record each segment's resident size.
let mut seg_map: HashMap<String, u64> = HashMap::new();

#[derive(PartialEq)]
enum LookingFor { Segment, Rss }
let mut looking_for = LookingFor::Segment;

let mut curr_seg_name = String::new();

// Parse the file.
for line in f.lines() {
let line = match line {
Ok(line) => line,
Err(_) => continue,
};
if looking_for == LookingFor::Segment {
// Look for a segment info line.
let cap = match seg_re.captures(line.as_slice()) {
Some(cap) => cap,
None => continue,
};
let perms = cap.at(1).unwrap();
let pathname = cap.at(2).unwrap();

// Construct the segment name from its pathname and permissions.
curr_seg_name.clear();
curr_seg_name.push_str("- ");
if pathname == "" || pathname.starts_with("[stack:") {
// Anonymous memory. Entries marked with "[stack:nnn]"
// look like thread stacks but they may include other
// anonymous mappings, so we can't trust them and just
// treat them as entirely anonymous.
curr_seg_name.push_str("anonymous");
} else {
curr_seg_name.push_str(pathname);
}
curr_seg_name.push_str(" (");
curr_seg_name.push_str(perms);
curr_seg_name.push_str(")");

looking_for = LookingFor::Rss;
} else {
// Look for an "Rss:" line.
let cap = match rss_re.captures(line.as_slice()) {
Some(cap) => cap,
None => continue,
};
let rss = cap.at(1).unwrap().parse::<u64>().unwrap() * 1024;

if rss > 0 {
// Aggregate small segments into "- other".
let seg_name = if rss < 512 * 1024 {
"- other".to_owned()
} else {
curr_seg_name.clone()
};
match seg_map.entry(seg_name) {
Entry::Vacant(entry) => { entry.insert(rss); },
Entry::Occupied(mut entry) => *entry.get_mut() += rss,
}
}

looking_for = LookingFor::Segment;
}
}

let mut segs: Vec<(String, u64)> = seg_map.into_iter().collect();

// Get the total and add it to the vector. Note that this total differs
// from the "resident" measurement obtained via /proc/<pid>/statm in
// get_resident(). It's unclear why this difference occurs; for some
// processes the measurements match, but for Servo they do not.
let total = segs.iter().fold(0u64, |total, &(_, size)| total + size);
segs.push(("resident-according-to-smaps".to_owned(), total));

// Sort by size; the total will be first.
segs.sort_by(|&(_, rss1), &(_, rss2)| rss2.cmp(&rss1));

segs
}

#[cfg(not(target_os="linux"))]
fn get_resident_segments() -> Vec<(String, u64)> {
vec![]
}

Some generated files are not rendered by default. Learn more.

Some generated files are not rendered by default. Learn more.

ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.