self_meter/
scan.rs

1use std::io::{Read, Seek, SeekFrom};
2use std::fs::File;
3use std::fmt::Write;
4use std::num::ParseIntError;
5use std::time::{Instant, SystemTime};
6use std::collections::HashMap;
7
8use {Meter, Snapshot, ThreadInfo, Pid, Error};
9use error::{UptimeError, StatError, StatusError, IoStatError};
10
11
12impl Meter {
13    /// Scan system for metrics
14    ///
15    /// This method must be called regularly at intervals specified
16    /// in constructor.
17    pub fn scan(&mut self) -> Result<(), Error> {
18        // We reuse Snapshot structure (mostly becasuse of threads hash map)
19        // to have smaller allocations on the fast path
20        let mut snap = if self.snapshots.len() >= self.num_snapshots {
21            self.snapshots.pop_front().unwrap()
22        } else {
23            Snapshot::new(&self.thread_names)
24        };
25        snap.timestamp = SystemTime::now();
26        snap.instant = Instant::now();
27
28        // First scan everything that relates to cpu_time to have as accurate
29        // CPU usage measurements as possible
30        try!(self.read_cpu_times(&mut snap.process,
31            &mut snap.threads,
32            &mut snap.uptime, &mut snap.idle_time));
33
34        try!(self.read_memory(&mut snap));
35        try!(self.read_io(&mut snap));
36
37        if snap.memory_rss > self.memory_rss_peak {
38            self.memory_rss_peak = snap.memory_rss;
39        }
40        if snap.memory_swap > self.memory_swap_peak {
41            self.memory_swap_peak = snap.memory_swap;
42        }
43
44        self.snapshots.push_back(snap);
45        Ok(())
46    }
47
48    #[cfg(target_os="linux")]
49    fn read_cpu_times(&mut self, process: &mut ThreadInfo,
50                      threads: &mut HashMap<Pid, ThreadInfo>,
51                      uptime: &mut u64, idle_time: &mut u64)
52        -> Result<(), Error>
53    {
54        self.text_buf.truncate(0);
55        try!(File::open("/proc/uptime")
56             .and_then(|mut f| f.read_to_string(&mut self.text_buf))
57             .map_err(|e| Error::Uptime(e.into())));
58        {
59            let mut iter = self.text_buf.split_whitespace();
60            let seconds = try!(iter.next()
61                .ok_or(Error::Uptime(UptimeError::BadFormat)));
62            let idle_sec = try!(iter.next()
63                .ok_or(Error::Uptime(UptimeError::BadFormat)));
64            *uptime = try!(parse_uptime(seconds));
65            *idle_time = try!(parse_uptime(idle_sec));
66        }
67        try!(read_stat(&mut self.text_buf, "/proc/self/stat", process)
68            .map_err(Error::Stat));
69        for (&tid, _) in &self.thread_names {
70            self.path_buf.truncate(0);
71            write!(&mut self.path_buf,
72                "/proc/self/task/{}/stat", tid).unwrap();
73            try!(read_stat(&mut self.text_buf, &self.path_buf[..],
74                    threads.entry(tid).or_insert_with(ThreadInfo::new))
75                .map_err(|e| Error::ThreadStat(tid, e)));
76        }
77        Ok(())
78    }
79
80    #[cfg(not(target_os="linux"))]
81    fn read_cpu_times(&mut self, process: &mut ThreadInfo,
82                      threads: &mut HashMap<Pid, ThreadInfo>,
83                      uptime: &mut u64, idle_time: &mut u64)
84        -> Result<(), Error>
85    {
86        Ok(())
87    }
88
89    #[cfg(target_os="linux")]
90    fn read_memory(&mut self, snap: &mut Snapshot)
91        -> Result<(), StatusError>
92    {
93        self.text_buf.truncate(0);
94        try!(File::open("/proc/self/status")
95             .and_then(|mut f| f.read_to_string(&mut self.text_buf)));
96        for line in self.text_buf.lines() {
97            let mut pairs = line.split(':');
98            match (pairs.next(), pairs.next()) {
99                (Some("VmPeak"), Some(text))
100                => snap.memory_virtual_peak = try!(parse_memory(text)),
101                (Some("VmSize"), Some(text))
102                => snap.memory_virtual = try!(parse_memory(text)),
103                (Some("VmRSS"), Some(text))
104                => snap.memory_rss = try!(parse_memory(text)),
105                (Some("VmSwap"), Some(text))
106                => snap.memory_swap = try!(parse_memory(text)),
107                _ => {}
108            }
109        }
110        Ok(())
111    }
112
113    #[cfg(not(target_os="linux"))]
114    fn read_memory(&mut self, snap: &mut Snapshot)
115        -> Result<(), StatusError>
116    {
117        Ok(())
118    }
119
120    #[cfg(target_os="linux")]
121    fn read_io(&mut self, snap: &mut Snapshot)
122        -> Result<(), Error>
123    {
124        let err = &|e: ParseIntError| Error::IoStat(e.into());
125        self.text_buf.truncate(0);
126        self.io_file.seek(SeekFrom::Start(0))
127            .map_err(IoStatError::Io)?;
128        self.io_file.read_to_string(&mut self.text_buf)
129            .map_err(IoStatError::Io)?;
130        for line in self.text_buf.lines() {
131            let mut pairs = line.split(':');
132            match (pairs.next(), pairs.next().map(|x| x.trim())) {
133                (Some("rchar"), Some(text))
134                => snap.read_bytes = text.parse().map_err(err)?,
135                (Some("wchar"), Some(text))
136                => snap.write_bytes = text.parse().map_err(err)?,
137                (Some("syscr"), Some(text))
138                => snap.read_ops = text.parse().map_err(err)?,
139                (Some("syscw"), Some(text))
140                => snap.write_ops = text.parse().map_err(err)?,
141                (Some("read_bytes"), Some(text))
142                => snap.read_disk_bytes = text.parse().map_err(err)?,
143                (Some("write_bytes"), Some(text))
144                => snap.write_disk_bytes = text.parse().map_err(err)?,
145                (Some("cancelled_write_bytes"), Some(text)) => {
146                    snap.write_cancelled_bytes = text.parse().map_err(err)?;
147                }
148                _ => {}
149            }
150        }
151        Ok(())
152    }
153    #[cfg(not(target_os="linux"))]
154    fn read_io(&mut self, snap: &mut Snapshot)
155        -> Result<(), Error>
156    {
157        // No IO tracking yet
158        Ok(())
159    }
160
161}
162
163fn parse_memory(value: &str) -> Result<u64, StatusError> {
164    let mut pair = value.split_whitespace();
165    let value = try!(try!(pair.next().ok_or(StatusError::BadFormat))
166        .parse::<u64>());
167    match pair.next() {
168        Some("kB") => Ok(value * 1024),
169        _ => Err(StatusError::BadUnit),
170    }
171}
172
173pub fn parse_uptime(value: &str) -> Result<u64, UptimeError> {
174    if value.len() <= 3 {
175        return Err(UptimeError::BadFormat);
176    }
177    let dot = value.find('.').ok_or(UptimeError::BadFormat)?;
178    let (integer, decimals) = value.split_at(dot);
179    if decimals.len() == 1+1 {
180        Ok(try!(integer.parse::<u64>()) * 100 +
181           try!(decimals[1..].parse::<u64>())*10)
182    } else if decimals.len() == 1+2 {
183        Ok(try!(integer.parse::<u64>()) * 100 +
184           try!(decimals[1..].parse::<u64>()))
185    } else {
186        Err(UptimeError::BadFormat)
187    }
188}
189
190fn read_stat(text_buf: &mut String, path: &str, thread_info: &mut ThreadInfo)
191    -> Result<(), StatError>
192{
193    text_buf.truncate(0);
194    try!(File::open(path)
195         .and_then(|mut f| f.read_to_string(text_buf)));
196    let right_paren = try!(text_buf.rfind(')')
197        .ok_or(StatError::BadFormat));
198    let mut iter = text_buf[right_paren+1..].split_whitespace();
199    thread_info.user_time = try!(
200        try!(iter.nth(11).ok_or(StatError::BadFormat)).parse());
201    thread_info.system_time = try!(
202        try!(iter.next().ok_or(StatError::BadFormat)).parse());
203    thread_info.child_user_time = try!(
204        try!(iter.next().ok_or(StatError::BadFormat)).parse());
205    thread_info.child_system_time = try!(
206        try!(iter.next().ok_or(StatError::BadFormat)).parse());
207    Ok(())
208}
209
210impl ThreadInfo {
211    fn new() -> ThreadInfo {
212        ThreadInfo {
213            user_time: 0,
214            system_time: 0,
215            child_user_time: 0,
216            child_system_time: 0,
217        }
218    }
219}
220
221impl Snapshot {
222    fn new(threads: &HashMap<Pid, String>) -> Snapshot {
223        Snapshot {
224            timestamp: SystemTime::now(),
225            instant: Instant::now(),
226            uptime: 0,
227            idle_time: 0,
228            process: ThreadInfo::new(),
229            memory_rss: 0,
230            memory_virtual: 0,
231            memory_virtual_peak: 0,
232            memory_swap: 0,
233            read_bytes: 0,
234            write_bytes: 0,
235            read_ops: 0,
236            write_ops: 0,
237            read_disk_bytes: 0,
238            write_disk_bytes: 0,
239            write_cancelled_bytes: 0,
240            threads: threads.iter()
241                .map(|(&pid, _)| (pid, ThreadInfo::new()))
242                .collect(),
243        }
244    }
245}
246
247#[cfg(test)]
248mod test {
249    use super::parse_uptime;
250
251    #[test]
252    fn normal_uptime() {
253        assert_eq!(parse_uptime("1927830.69").unwrap(), 192783069);
254    }
255    #[test]
256    fn one_zero_uptime() {
257        assert_eq!(parse_uptime("4780.0").unwrap(), 478000);
258    }
259}