Skip to content

Commit 6e32299

Browse files
Actually implement showing all tasks (threads)
1 parent 800cb3d commit 6e32299

File tree

5 files changed

+177
-38
lines changed

5 files changed

+177
-38
lines changed

src/procrs/error.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ pub enum ProcFile {
2929
/// /proc/[pid]/stat file, contains various stats about the process.
3030
PidStat,
3131
/// /proc/[pid]/cmdline file, contains the cmdline given when starting the process.
32-
PidCmdline
32+
PidCmdline,
33+
34+
// TODO: Attach a pid to this directory
35+
/// /proc/[pid]/task directory, contains threads of a process.
36+
PidTaskDir,
3337
}
3438

3539
impl Error for ProcFile {
@@ -45,7 +49,8 @@ impl Error for ProcFile {
4549
ProcFile::PidDir => "/proc/[pid] directory",
4650
ProcFile::PidStatus => "/proc/[pid]/status file",
4751
ProcFile::PidStat => "/proc/[pid]/stat file",
48-
ProcFile::PidCmdline => "/proc/[pid]/cmdline file"
52+
ProcFile::PidCmdline => "/proc/[pid]/cmdline file",
53+
ProcFile::PidTaskDir => "/proc/[pid]/task",
4954
}
5055
}
5156

src/procrs/pid/mod.rs

Lines changed: 138 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::io;
22
use std::io::prelude::*;
33
use std::fs::{self, File, ReadDir, DirEntry};
44
use std::path::Path;
5+
use std::vec;
56
use std::io::BufReader;
67
use std::cmp::Ordering;
78
use std::str::FromStr;
@@ -16,42 +17,54 @@ use self::status::PidStatus;
1617
use error::{ProcError, ProcFile, ProcOper};
1718
use TaskId;
1819

19-
fn err_str<T: ToString>(err: T) -> String {
20-
err.to_string()
21-
}
22-
2320
/// A struct containing information about a process.
2421
///
2522
/// This struct contains information from various files inside the
2623
/// /proc/[pid] directory (for the respective pid).
2724
#[derive(Debug)]
2825
pub struct Pid {
26+
/// The tid of this process
27+
pub pid: TaskId,
2928
/// The /proc/[pid]/stat file
3029
pub stat: Box<PidStat>,
3130
/// The /proc/[pid]/status file
3231
pub status: Box<PidStatus>,
3332
/// The /proc/[pid]/cmdline file
34-
pub cmdline: Vec<String>
33+
pub cmdline: Vec<String>,
34+
/// If this is a thread, this is set to true.
35+
/// Threads will never have tasks attached.
36+
is_thread: bool,
37+
/// Vec of threads under /proc/[pid]/tasks/[tid]
38+
threads: Option<Vec<Pid>>,
3539
}
3640

3741
impl Pid {
42+
/// Create a new Pid struct for a process, given a pid.
3843
pub fn new(pid: TaskId) -> Result<Self, ProcError> {
39-
let proc_dir = format!("/proc/{}", pid);
44+
let pid_dir = Path::new("/proc");
45+
Self::new_dir(pid_dir, pid)
46+
}
47+
48+
fn new_dir(proc_dir: &Path, pid: TaskId) -> Result<Self, ProcError> {
49+
let proc_dir = proc_dir.join(pid.to_string());
4050
let pid_stat = try!(PidStat::new(&proc_dir));
4151
let pid_status = try!(PidStatus::new(&proc_dir));
4252
let cmdline = try!(Self::read_cmdline(&proc_dir));
4353

44-
Ok(Pid{
54+
Ok(Pid {
55+
pid: pid,
4556
stat: Box::new(pid_stat),
4657
status: Box::new(pid_status),
47-
cmdline: cmdline
58+
cmdline: cmdline,
59+
is_thread: false,
60+
threads: None,
4861
})
4962
}
5063

5164
/// Given a /proc/[pid] directory, read the respective /proc/[pid]/cmdline
5265
/// file and return them in a Vec.
53-
fn read_cmdline(proc_dir: &str) -> Result<Vec<String>, ProcError> {
54-
File::open(Path::new(proc_dir).join("cmdline"))
66+
fn read_cmdline(proc_dir: &Path) -> Result<Vec<String>, ProcError> {
67+
File::open(proc_dir.join("cmdline"))
5568
.map_err(|e| ProcError::new_err(ProcOper::Opening, ProcFile::PidCmdline, e))
5669
.and_then(|file| {
5770
let mut contents = Vec::new();
@@ -86,6 +99,27 @@ impl Pid {
8699
PidQuery::NoneQuery => true
87100
}
88101
}
102+
103+
pub fn tasks(&mut self) -> Option<Vec<Pid>> {
104+
self.tasks_query(PidQuery::NoneQuery)
105+
}
106+
107+
// TODO: Work out if this really should return Option<_>
108+
// or Option<Result<Vec<Pid>>>. Otherwise the error is uncaught.
109+
pub fn tasks_query(&self, query: PidQuery) -> Option<Vec<Pid>> {
110+
if self.is_thread {
111+
return None;
112+
}
113+
114+
PidIter::new_tid_query(self.pid, query.clone()).unwrap()
115+
.filter(|p| {
116+
let query = query.clone();
117+
match *p {
118+
Ok(ref pid) => pid.query(&query),
119+
Err(_) => true
120+
}
121+
}).collect::<Result<Vec<_>, _>>().ok()
122+
}
89123
}
90124

91125
impl PartialEq for Pid {
@@ -113,21 +147,42 @@ impl Ord for Pid {
113147
/// non-trivial.
114148
pub struct PidIter {
115149
dir_iter: ReadDir,
116-
query: PidQuery
150+
query: PidQuery,
117151
}
118152

119153
impl PidIter {
120154
/// Create a new iterator over all processes in /proc.
121-
pub fn new() -> Result<Self, String> {
155+
pub fn new() -> Result<Self, ProcError> {
122156
Self::new_query(PidQuery::NoneQuery)
123157
}
124158

125159
/// Create a new iterator over all processes in /proc, but only yield
126160
/// processes that match the given query.
127-
pub fn new_query(query: PidQuery) -> Result<Self, String> {
161+
pub fn new_query(query: PidQuery) -> Result<Self, ProcError> {
128162
let proc_dir = Path::new("/proc");
129-
let dir_iter = try!(fs::read_dir(proc_dir).map_err(err_str));
130-
Ok(PidIter{
163+
let dir_iter = try!(
164+
fs::read_dir(proc_dir)
165+
.map_err(|e|
166+
ProcError::new(ProcOper::Opening, ProcFile::ProcDir, Some(e), Some("PidIter"))
167+
)
168+
);
169+
Ok(PidIter {
170+
dir_iter: dir_iter,
171+
query: query,
172+
})
173+
}
174+
175+
fn new_tid_query(pid: TaskId, query: PidQuery) -> Result<Self, ProcError> {
176+
let dir_name = format!("/proc/{}/task", pid);
177+
let task_dir = Path::new(&dir_name);
178+
let dir_iter = try!(
179+
fs::read_dir(task_dir)
180+
.map_err(|e|
181+
ProcError::new(ProcOper::Opening, ProcFile::PidTaskDir,
182+
Some(e), Some("PidIter"))
183+
)
184+
);
185+
Ok(PidIter {
131186
dir_iter: dir_iter,
132187
query: query
133188
})
@@ -136,19 +191,21 @@ impl PidIter {
136191
/// Given a DirEntry, try to create a Pid struct, and only return if
137192
/// it matches the query, and is complete.
138193
fn proc_dir_filter(entry_opt: Result<DirEntry, io::Error>, query: &PidQuery)
139-
-> Option<Result<Pid, String>> {
140-
// TODO: This sucks, find a better way
194+
-> Option<Result<Pid, ProcError>> {
141195
let file = entry_opt
142-
.map_err(err_str)
196+
.map_err(|e|
197+
ProcError::new(ProcOper::Reading, ProcFile::ProcDir, Some(e), Some("PidIter"))
198+
)
143199
.and_then(|entry|
144200
entry.file_name().into_string()
145-
.or(Err("Error parsing filename".to_owned()))
201+
.or(Err(ProcError::new_more(ProcOper::Parsing, ProcFile::ProcDir, Some("PidIter"))))
146202
);
147203

148-
if file.is_err() {
149-
return None;
204+
if let Err(e) = file{
205+
return Some(Err(e));
150206
}
151207

208+
// Ensure filename is an integer (skip if not)
152209
match file.unwrap().parse() {
153210
Ok(pid) => {
154211
// If an error is not hard (error opening or reading file),
@@ -158,7 +215,7 @@ impl PidIter {
158215
Ok(prc) => prc,
159216
Err(e) => {
160217
if e.is_hard() {
161-
return Some(Err(e).map_err(err_str));
218+
return Some(Err(e));
162219
} else {
163220
return None;
164221
}
@@ -175,7 +232,7 @@ impl PidIter {
175232
}
176233

177234
impl Iterator for PidIter {
178-
type Item = Result<Pid, String>;
235+
type Item = Result<Pid, ProcError>;
179236

180237
fn next(&mut self) -> Option<Self::Item> {
181238
for entry in self.dir_iter.by_ref() {
@@ -187,12 +244,69 @@ impl Iterator for PidIter {
187244
None
188245
}
189246

190-
// Size may be anywhere from 0 to number of dirs
247+
/// Size may be anywhere from 0 to number of dirs.
191248
fn size_hint(&self) -> (usize, Option<usize>) {
192249
(0, self.dir_iter.size_hint().1)
193250
}
194251
}
195252

253+
/// An Iterator over threads of processes in the system.
254+
///
255+
/// If a task disappears while scanning it, the partial Pid struct
256+
/// will not be yielded. An atomic view of processes on the system seems
257+
/// non-trivial.
258+
pub struct TidIter {
259+
pid_iter: PidIter,
260+
task_iter: Option<vec::IntoIter<Pid>>,
261+
query: PidQuery,
262+
}
263+
264+
impl TidIter {
265+
/// Create a new iterator over all tasks in /proc.
266+
pub fn new() -> Result<Self, ProcError> {
267+
println!("{:?}", 3);
268+
Self::new_query(PidQuery::NoneQuery)
269+
}
270+
271+
/// Create a new iterator over all tasks in /proc, but only yield
272+
/// those that match the given query.
273+
pub fn new_query(query: PidQuery) -> Result<Self, ProcError> {
274+
Ok(TidIter{
275+
pid_iter: try!(PidIter::new_query(query.clone())),
276+
task_iter: None,
277+
query: query,
278+
})
279+
}
280+
}
281+
282+
impl Iterator for TidIter {
283+
type Item = Result<Pid, ProcError>;
284+
285+
fn next(&mut self) -> Option<Self::Item> {
286+
loop {
287+
if self.task_iter.is_none() {
288+
let pid = match self.pid_iter.next() {
289+
Some(Ok(pid)) => pid,
290+
Some(Err(e)) => { return Some(Err(e)) },
291+
None => { return None; }
292+
};
293+
let tasks_vec = pid.tasks_query(self.query.clone());
294+
if let Some(vec) = tasks_vec {
295+
self.task_iter = Some(vec.into_iter());
296+
}
297+
continue;
298+
} else {
299+
let next = self.task_iter.as_mut().unwrap().next();
300+
match next {
301+
Some(pid) => { return Some(Ok(pid)); },
302+
None => { self.task_iter = None; },
303+
};
304+
}
305+
}
306+
}
307+
}
308+
309+
#[derive(Clone, Debug)]
196310
/// A list of query types for process querying.
197311
pub enum PidQuery {
198312
/// Query by pid

src/procrs/pid/stat.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,9 @@ macro_rules! stat_parse_opt_num {
142142

143143
impl PidStat {
144144
/// Generate PidStat struct given a process directory.
145-
pub fn new(pid_dir: &str) -> Result<Self, ProcError> {
145+
pub fn new(pid_dir: &Path) -> Result<Self, ProcError> {
146146
let file = try!(
147-
File::open(Path::new(pid_dir).join("stat"))
147+
File::open(pid_dir.join("stat"))
148148
.map_err(|e|
149149
ProcError::new_err(ProcOper::Opening, ProcFile::PidStat, e)
150150
)

src/procrs/pid/status.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@ macro_rules! extract_line_opt {
6868

6969
impl PidStatus {
7070
// Generate PidStatus struct given a process directory
71-
pub fn new(pid_dir: &str) -> Result<Self, ProcError> {
71+
pub fn new(pid_dir: &Path) -> Result<Self, ProcError> {
7272
// Try opening file
7373
let status_file = try!(
74-
File::open(Path::new(pid_dir).join("status"))
74+
File::open(pid_dir.join("status"))
7575
.map_err(|e| ProcError::new_err(ProcOper::Opening, ProcFile::PidStatus, e))
7676
);
7777

src/psq/main.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use prettytable::row::Row;
77
use prettytable::format::FormatBuilder;
88
use std::collections::HashMap;
99
use std::iter::repeat;
10+
use std::cmp::Ordering;
1011
use procrs::pid::*;
1112
use procrs::TaskId;
1213
use argparse::{ArgumentParser, StoreTrue, Store};
@@ -16,9 +17,18 @@ fn main() {
1617
let (query, long, perf, verbose, tree, threads) =
1718
(opts.query, opts.long, opts.perf, opts.verbose, opts.tree, opts.threads);
1819

19-
let pid_iter = PidIter::new_query(query);
20-
let mut pids = pid_iter.unwrap()
21-
.collect::<Result<Vec<_>, _>>().unwrap();
20+
let mut pids: Vec<_> = match threads {
21+
false => {
22+
PidIter::new_query(query)
23+
.unwrap()
24+
.collect::<Result<_, _>>().unwrap()
25+
},
26+
true => {
27+
TidIter::new_query(query)
28+
.unwrap()
29+
.collect::<Result<_, _>>().unwrap()
30+
}
31+
};
2232

2333
let mut name_indent = HashMap::new();
2434

@@ -32,7 +42,16 @@ fn main() {
3242
if opts.tree {
3343
pids = treeify_names(pids, &mut name_indent);
3444
} else {
35-
pids.sort_by(|p1, p2| p1.stat.pid.cmp(&p2.stat.pid));
45+
pids.sort_by(|p1, p2|
46+
match threads {
47+
false => p1.stat.pid.cmp(&p2.stat.pid),
48+
true => {
49+
let cmp = p1.status.tgid.cmp(&p2.status.tgid);
50+
if let Ordering::Equal = cmp { return Ordering::Equal; }
51+
p1.stat.pid.cmp(&p2.stat.pid)
52+
}
53+
}
54+
);
3655
};
3756
// Assume hertz is 100.
3857
// TODO: Look this up via syscall (no /proc value for it)
@@ -71,10 +90,10 @@ fn main() {
7190
}
7291
};
7392
row.push(cell!(p.stat.ppid));
93+
if long {
94+
}
7495
match (long, perf) {
75-
(_, false) => {
76-
row.push(cell!(name));
77-
},
96+
(_, false) => {},
7897
(_, true) => {
7998
let rss = p.status.vmrss.map(|m| (m / 1024).to_string()).unwrap_or("".to_owned());
8099
let raw_time = p.stat.utime + p.stat.stime;
@@ -101,11 +120,12 @@ fn main() {
101120
if threads {
102121
titles.push(cell!("Tid"));
103122
}
123+
titles.push(cell!("Ppid"));
104124
// TODO: Possible remove Ppid from when long is false,
105125
// and have Cmd/Args as separate columns for long.
106126
match (long, perf) {
107127
(_, false) =>
108-
titles.extend_from_slice(&[cell!("Ppid"), cell!("Cmd")]),
128+
titles.extend_from_slice(&[cell!("Cmd")]),
109129
(_, true) =>
110130
titles.extend_from_slice(&[cell!("RSS"), cell!("Time"), cell!("Cmd")])
111131
};

0 commit comments

Comments
 (0)