Skip to content

Commit

Permalink
process/predict: Find portage_tmpdir using emerge process's open fds
Browse files Browse the repository at this point in the history
Required permission should be the same as for reading `build.log`.
  • Loading branch information
vincentdephily committed Oct 9, 2023
1 parent 3c42d08 commit 9b4b894
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 23 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

* Support searching by multiple terms
- eg `emlop s -e gcc clang llvm rust`
* Support multiple `--tmpdir`s
* Improve predict's `tmpdir`:
- Autodetect `tmpdir` using currently running emerge processes
- Support multiple `--tmpdir` arguments
* Upgraded clap dependency
- Inline help styling/content changed a bit

Expand Down
14 changes: 9 additions & 5 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use clap::{builder::styling, crate_version, value_parser, Arg, ArgAction::*, Command};
use std::path::PathBuf;

/// Generate cli argument parser without the `complete` subcommand.
pub fn build_cli_nocomplete() -> Command {
Expand Down Expand Up @@ -267,10 +268,12 @@ pub fn build_cli_nocomplete() -> Command {
.num_args(1)
.action(Append)
.default_value("/var/tmp")
.value_parser(value_parser!(PathBuf))
.display_order(2)
.help("Location of portage tmpdir")
.long_help("Location of portage tmpdir\n\
Multiple folders can be provided");
Multiple folders can be provided\n\
Emlop also looks for tmpdir using current emerge processes");
let resume = Arg::new("resume").long("resume")
.value_name("source")
.value_parser(value_parser!(crate::ResumeKind))
Expand Down Expand Up @@ -421,7 +424,7 @@ mod test {
}
macro_rules! many {
($t: ty, $k: expr, $a: expr) => {
matches($a).get_many::<$t>($k).map(|v| v.map(AsRef::as_ref).collect::<Vec<_>>())
matches($a).get_many::<$t>($k).map(|r| r.cloned().collect::<Vec<$t>>())
};
}

Expand All @@ -444,8 +447,9 @@ mod test {
assert_eq!(one!(ColorStyle, "color", "l --color n"), Some(&ColorStyle::Never));
assert_eq!(one!(ColorStyle, "color", "l --color never"), Some(&ColorStyle::Never));

assert_eq!(many!(String, "tmpdir", "p"), Some(vec!["/var/tmp"]));
assert_eq!(many!(String, "tmpdir", "p --tmpdir a"), Some(vec!["a"]));
assert_eq!(many!(String, "tmpdir", "p --tmpdir a --tmpdir b"), Some(vec!["a", "b"]));
let pathvec = |s: &str| Some(s.split_whitespace().map(PathBuf::from).collect());
assert_eq!(many!(PathBuf, "tmpdir", "p"), pathvec("/var/tmp/portage"));
assert_eq!(many!(PathBuf, "tmpdir", "p --tmpdir a"), pathvec("a"));
assert_eq!(many!(PathBuf, "tmpdir", "p --tmpdir a --tmpdir b"), pathvec("a b"));
}
}
7 changes: 4 additions & 3 deletions src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{datetime::*, parse::*, proces::*, table::*, *};
use std::{collections::{BTreeMap, HashMap},
io::stdin};
io::stdin,
path::PathBuf};

/// Straightforward display of merge events
///
Expand Down Expand Up @@ -324,11 +325,11 @@ pub fn cmd_predict(args: &ArgMatches) -> Result<bool, Error> {
let avg = *args.get_one("avg").unwrap();
let resume = *args.get_one("resume").unwrap();
let mut tbl = Table::new(st).align_left(0).align_left(2).margin(2, " ").last(last);
let tmpdirs = args.get_many::<String>("tmpdir").unwrap().cloned().collect();
let mut tmpdirs: Vec<PathBuf> = args.get_many("tmpdir").unwrap().cloned().collect();

// Gather and print info about current merge process.
let mut cms = std::i64::MAX;
for i in get_all_info(Some("emerge")) {
for i in get_all_info(Some("emerge"), &mut tmpdirs) {
cms = std::cmp::min(cms, i.start);
if show.emerge {
tbl.row([&[&i], &[&FmtDur(now - i.start)], &[]]);
Expand Down
9 changes: 5 additions & 4 deletions src/parse/current.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use regex::Regex;
use serde::Deserialize;
use serde_json::from_reader;
use std::{fs::File,
io::{BufRead, BufReader, Read}};
io::{BufRead, BufReader, Read},
path::PathBuf};

/// Package name and version
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
Expand Down Expand Up @@ -91,11 +92,11 @@ fn get_resume_priv(kind: ResumeKind, file: &str) -> Option<Vec<Pkg>> {


/// Retrieve summary info from the build log
pub fn get_buildlog(pkg: &Pkg, portdirs: &Vec<String>) -> Option<String> {
pub fn get_buildlog(pkg: &Pkg, portdirs: &Vec<PathBuf>) -> Option<String> {
for portdir in portdirs {
let name = format!("{}/portage/{}/temp/build.log", portdir, pkg.ebuild_version());
let name = portdir.join("portage").join(pkg.ebuild_version()).join("temp/build.log");
if let Ok(file) = File::open(&name).map_err(|e| warn!("Cannot open {name:?}: {e}")) {
info!("Build log: {name}");
info!("Build log: {}", name.display());
return Some(read_buildlog(file, 50));
}
}
Expand Down
49 changes: 39 additions & 10 deletions src/proces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
use crate::{datetime::*, *};
use anyhow::{ensure, Context};
use std::{fs::{read_dir, DirEntry, File},
io::prelude::*};
io::prelude::*,
path::PathBuf};

#[derive(Debug)]
pub struct Info {
Expand Down Expand Up @@ -37,7 +38,8 @@ impl std::fmt::Display for Info {
fn get_proc_info(filter: Option<&str>,
entry: &DirEntry,
clocktick: i64,
time_ref: i64)
time_ref: i64,
tmpdirs: &mut Vec<PathBuf>)
-> Option<Info> {
// Parse pid.
// At this stage we expect `entry` to not always correspond to a process.
Expand All @@ -57,18 +59,44 @@ fn get_proc_info(filter: Option<&str>,
let mut cmdline = String::new();
File::open(entry.path().join("cmdline")).ok()?.read_to_string(&mut cmdline).ok()?;
cmdline = cmdline.replace('\0', " ").trim().into();
// Find portage tmpdir
extend_tmpdirs(entry.path(), tmpdirs);
// Done
Some(Info { cmdline, start: time_ref + start_time / clocktick, pid })
}

/// Find tmpdir by looking for "build.log" in the process fds, and add it to the provided vector.
fn extend_tmpdirs(proc: PathBuf, tmpdirs: &mut Vec<PathBuf>) {
if let Ok(entries) = read_dir(proc.join("fd")) {
let procstr = proc.to_string_lossy();
for d in entries.filter_map(|e| {
let p = e.ok()?.path().canonicalize().ok()?;
if p.file_name() != Some(std::ffi::OsStr::new("build.log")) {
return None;
}
let d = p.parent()?.parent()?.parent()?.parent()?.parent()?;
debug!("Tmpdir {} found in {}", d.to_string_lossy(), procstr);
Some(d.to_path_buf())
})
{
if !tmpdirs.contains(&d) {
// Insert at the front because it's a better candidate than cli/default tmpdir
tmpdirs.insert(0, d)
}
}
}
}

/// Get command name, arguments, start time, and pid for all processes.
pub fn get_all_info(filter: Option<&str>) -> Vec<Info> {
get_all_info_result(filter).unwrap_or_else(|e| {
log_err(e);
vec![]
})
pub fn get_all_info(filter: Option<&str>, tmpdirs: &mut Vec<PathBuf>) -> Vec<Info> {
get_all_info_result(filter, tmpdirs).unwrap_or_else(|e| {
log_err(e);
vec![]
})
}
fn get_all_info_result(filter: Option<&str>) -> Result<Vec<Info>, Error> {
fn get_all_info_result(filter: Option<&str>,
tmpdirs: &mut Vec<PathBuf>)
-> Result<Vec<Info>, Error> {
// clocktick and time_ref are needed to interpret stat.start_time. time_ref should correspond to
// the system boot time; not sure why it doesn't, but it's still usable as a reference.
// SAFETY: returns a system constant, only failure mode should be a zero/negative value
Expand All @@ -83,7 +111,7 @@ fn get_all_info_result(filter: Option<&str>) -> Result<Vec<Info>, Error> {
// Now iterate through /proc/<pid>
let mut ret: Vec<Info> = Vec::new();
for entry in read_dir("/proc/").context("Listing /proc/")? {
if let Some(i) = get_proc_info(filter, &entry?, clocktick, time_ref) {
if let Some(i) = get_proc_info(filter, &entry?, clocktick, time_ref, tmpdirs) {
ret.push(i)
}
}
Expand All @@ -109,8 +137,9 @@ mod tests {
#[test] #[rustfmt::skip]
fn start_time() {
// First get the system's process start times using our implementation
let mut tmpdirs = vec![];
let mut info: BTreeMap<i32,(String,Option<i64>,Option<i64>)> = //pid => (cmd, rust_time, ps_time)
get_all_info(None)
get_all_info(None, &mut tmpdirs)
.iter()
.fold(BTreeMap::new(), |mut a,i| {a.insert(i.pid, (i.cmdline.clone(),Some(i.start),None)); a});
// Then get them using the ps implementation (merging them into the same data structure)
Expand Down

0 comments on commit 9b4b894

Please sign in to comment.