mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-09-23 17:58:06 +02:00
refactor: optimize username cloning for Unix processes (#1816)
* refactor: rename/update comments for uid_to_username * use arc<str> instead of cloning user repeatedly * clippy * oop * oop2 * hmmm hopefully that works? * ugh
This commit is contained in:
parent
c8614bf2be
commit
e4b814fd0b
@ -32,7 +32,7 @@ cfg_if! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::{borrow::Cow, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use super::{DataCollector, error::CollectionResult};
|
use super::{DataCollector, error::CollectionResult};
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ pub struct ProcessHarvest {
|
|||||||
pub uid: Option<libc::uid_t>,
|
pub uid: Option<libc::uid_t>,
|
||||||
|
|
||||||
/// This is the process' user.
|
/// This is the process' user.
|
||||||
pub user: Cow<'static, str>,
|
pub user: Option<Arc<str>>,
|
||||||
|
|
||||||
/// Gpu memory usage as bytes.
|
/// Gpu memory usage as bytes.
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
|
@ -201,14 +201,7 @@ fn read_proc(
|
|||||||
(0, 0, 0, 0)
|
(0, 0, 0, 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
let user = uid
|
let user = uid.and_then(|uid| user_table.uid_to_username(uid).ok());
|
||||||
.and_then(|uid| {
|
|
||||||
user_table
|
|
||||||
.get_uid_to_username_mapping(uid)
|
|
||||||
.map(Into::into)
|
|
||||||
.ok()
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| "N/A".into());
|
|
||||||
|
|
||||||
let time = if let Ok(ticks_per_sec) = u32::try_from(rustix::param::clock_ticks_per_second()) {
|
let time = if let Ok(ticks_per_sec) = u32::try_from(rustix::param::clock_ticks_per_second()) {
|
||||||
if ticks_per_sec == 0 {
|
if ticks_per_sec == 0 {
|
||||||
|
@ -88,14 +88,7 @@ pub(crate) trait UnixProcessExt {
|
|||||||
total_write: disk_usage.total_written_bytes,
|
total_write: disk_usage.total_written_bytes,
|
||||||
process_state,
|
process_state,
|
||||||
uid,
|
uid,
|
||||||
user: uid
|
user: uid.and_then(|uid| user_table.uid_to_username(uid).ok()),
|
||||||
.and_then(|uid| {
|
|
||||||
user_table
|
|
||||||
.get_uid_to_username_mapping(uid)
|
|
||||||
.map(Into::into)
|
|
||||||
.ok()
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| "N/A".into()),
|
|
||||||
time: if process_val.start_time() == 0 {
|
time: if process_val.start_time() == 0 {
|
||||||
// Workaround for sysinfo occasionally returning a start time equal to UNIX
|
// Workaround for sysinfo occasionally returning a start time equal to UNIX
|
||||||
// epoch, giving a run time in the range of 50+ years. We just
|
// epoch, giving a run time in the range of 50+ years. We just
|
||||||
|
@ -1,28 +1,33 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
|
|
||||||
use crate::collection::error::{CollectionError, CollectionResult};
|
use crate::collection::error::{CollectionError, CollectionResult};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct UserTable {
|
pub struct UserTable {
|
||||||
pub uid_user_mapping: HashMap<libc::uid_t, String>,
|
pub uid_user_mapping: HashMap<libc::uid_t, Arc<str>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserTable {
|
impl UserTable {
|
||||||
pub fn get_uid_to_username_mapping(&mut self, uid: libc::uid_t) -> CollectionResult<String> {
|
/// Get the username associated with a UID. On first access of a name, it will
|
||||||
|
/// be cached for future accesses.
|
||||||
|
pub fn uid_to_username(&mut self, uid: libc::uid_t) -> CollectionResult<Arc<str>> {
|
||||||
if let Some(user) = self.uid_user_mapping.get(&uid) {
|
if let Some(user) = self.uid_user_mapping.get(&uid) {
|
||||||
Ok(user.clone())
|
Ok(user.clone())
|
||||||
} else {
|
} else {
|
||||||
// SAFETY: getpwuid returns a null pointer if no passwd entry is found for the uid.
|
// SAFETY: getpwuid returns a null pointer if no passwd entry is found for the uid which we check.
|
||||||
let passwd = unsafe { libc::getpwuid(uid) };
|
let passwd = unsafe { libc::getpwuid(uid) };
|
||||||
|
|
||||||
if passwd.is_null() {
|
if passwd.is_null() {
|
||||||
Err("passwd is inaccessible".into())
|
Err("passwd is inaccessible".into())
|
||||||
} else {
|
} else {
|
||||||
// SAFETY: We return early if passwd is null.
|
// SAFETY: We return early if passwd is null.
|
||||||
let username = unsafe { std::ffi::CStr::from_ptr((*passwd).pw_name) }
|
let username: Arc<str> = unsafe { std::ffi::CStr::from_ptr((*passwd).pw_name) }
|
||||||
.to_str()
|
.to_str()
|
||||||
.map_err(|err| CollectionError::General(err.into()))?
|
.map_err(|err| CollectionError::General(err.into()))?
|
||||||
.to_string();
|
.into();
|
||||||
|
|
||||||
self.uid_user_mapping.insert(uid, username.clone());
|
self.uid_user_mapping.insert(uid, username.clone());
|
||||||
|
|
||||||
Ok(username)
|
Ok(username)
|
||||||
|
@ -108,8 +108,7 @@ pub fn sysinfo_process_data(
|
|||||||
process_state,
|
process_state,
|
||||||
user: process_val
|
user: process_val
|
||||||
.user_id()
|
.user_id()
|
||||||
.and_then(|uid| users.get_user_by_id(uid))
|
.and_then(|uid| users.get_user_by_id(uid).map(|user| user.name().into())),
|
||||||
.map_or_else(|| "N/A".into(), |user| user.name().to_owned().into()),
|
|
||||||
time: if process_val.start_time() == 0 {
|
time: if process_val.start_time() == 0 {
|
||||||
// Workaround for sysinfo occasionally returning a start time equal to UNIX
|
// Workaround for sysinfo occasionally returning a start time equal to UNIX
|
||||||
// epoch, giving a run time in the range of 50+ years. We just
|
// epoch, giving a run time in the range of 50+ years. We just
|
||||||
|
@ -1188,9 +1188,9 @@ mod test {
|
|||||||
process_state: "N/A",
|
process_state: "N/A",
|
||||||
process_char: '?',
|
process_char: '?',
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
user: "root".to_string(),
|
user: Some("root".into()),
|
||||||
#[cfg(not(target_family = "unix"))]
|
#[cfg(not(target_family = "unix"))]
|
||||||
user: "N/A".to_string(),
|
user: Some("N/A".into()),
|
||||||
num_similar: 0,
|
num_similar: 0,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
time: Duration::from_secs(0),
|
time: Duration::from_secs(0),
|
||||||
|
@ -151,16 +151,17 @@ impl SortsRow for ProcColumn {
|
|||||||
}
|
}
|
||||||
ProcColumn::State => {
|
ProcColumn::State => {
|
||||||
if descending {
|
if descending {
|
||||||
data.sort_by_cached_key(|pd| Reverse(pd.process_state.to_lowercase()));
|
data.sort_by_cached_key(|pd| Reverse(pd.process_state));
|
||||||
} else {
|
} else {
|
||||||
data.sort_by_cached_key(|pd| pd.process_state.to_lowercase());
|
data.sort_by_cached_key(|pd| pd.process_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProcColumn::User => {
|
ProcColumn::User => {
|
||||||
|
// FIXME: Is there a better way here to keep the to_lowercase? Usually it shouldn't matter but...
|
||||||
if descending {
|
if descending {
|
||||||
data.sort_by_cached_key(|pd| Reverse(pd.user.to_lowercase()));
|
data.sort_by_cached_key(|pd| Reverse(pd.user.clone()));
|
||||||
} else {
|
} else {
|
||||||
data.sort_by_cached_key(|pd| pd.user.to_lowercase());
|
data.sort_by_cached_key(|pd| pd.user.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProcColumn::Time => {
|
ProcColumn::Time => {
|
||||||
|
@ -3,6 +3,7 @@ use std::{
|
|||||||
cmp::{Ordering, max},
|
cmp::{Ordering, max},
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
num::NonZeroU16,
|
num::NonZeroU16,
|
||||||
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -206,7 +207,7 @@ pub struct ProcWidgetData {
|
|||||||
pub total_write: u64,
|
pub total_write: u64,
|
||||||
pub process_state: &'static str,
|
pub process_state: &'static str,
|
||||||
pub process_char: char,
|
pub process_char: char,
|
||||||
pub user: String,
|
pub user: Option<Arc<str>>,
|
||||||
pub num_similar: u64,
|
pub num_similar: u64,
|
||||||
pub disabled: bool,
|
pub disabled: bool,
|
||||||
pub time: Duration,
|
pub time: Duration,
|
||||||
@ -249,7 +250,7 @@ impl ProcWidgetData {
|
|||||||
total_write: process.total_write,
|
total_write: process.total_write,
|
||||||
process_state: process.process_state.0,
|
process_state: process.process_state.0,
|
||||||
process_char: process.process_state.1,
|
process_char: process.process_state.1,
|
||||||
user: process.user.to_string(),
|
user: process.user.clone(),
|
||||||
num_similar: 1,
|
num_similar: 1,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
time: process.time,
|
time: process.time,
|
||||||
@ -318,7 +319,11 @@ impl ProcWidgetData {
|
|||||||
ProcColumn::TotalRead => dec_bytes_string(self.total_read),
|
ProcColumn::TotalRead => dec_bytes_string(self.total_read),
|
||||||
ProcColumn::TotalWrite => dec_bytes_string(self.total_write),
|
ProcColumn::TotalWrite => dec_bytes_string(self.total_write),
|
||||||
ProcColumn::State => self.process_char.to_string(),
|
ProcColumn::State => self.process_char.to_string(),
|
||||||
ProcColumn::User => self.user.clone(),
|
ProcColumn::User => self
|
||||||
|
.user
|
||||||
|
.as_ref()
|
||||||
|
.map(|user| user.to_string())
|
||||||
|
.unwrap_or_else(|| "N/A".to_string()),
|
||||||
ProcColumn::Time => format_time(self.time),
|
ProcColumn::Time => format_time(self.time),
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
ProcColumn::GpuMemValue | ProcColumn::GpuMemPercent => self.gpu_mem_usage.to_string(),
|
ProcColumn::GpuMemValue | ProcColumn::GpuMemPercent => self.gpu_mem_usage.to_string(),
|
||||||
@ -355,7 +360,11 @@ impl DataToCell<ProcColumn> for ProcWidgetData {
|
|||||||
self.process_state.into()
|
self.process_state.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProcColumn::User => self.user.clone().into(),
|
ProcColumn::User => self
|
||||||
|
.user
|
||||||
|
.as_ref()
|
||||||
|
.map(|user| user.to_string().into())
|
||||||
|
.unwrap_or_else(|| "N/A".into()),
|
||||||
ProcColumn::Time => format_time(self.time).into(),
|
ProcColumn::Time => format_time(self.time).into(),
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
ProcColumn::GpuMemValue | ProcColumn::GpuMemPercent => {
|
ProcColumn::GpuMemValue | ProcColumn::GpuMemPercent => {
|
||||||
|
@ -815,7 +815,10 @@ impl Prefix {
|
|||||||
}),
|
}),
|
||||||
PrefixType::Pid => r.is_match(process.pid.to_string().as_str()),
|
PrefixType::Pid => r.is_match(process.pid.to_string().as_str()),
|
||||||
PrefixType::State => r.is_match(process.process_state.0),
|
PrefixType::State => r.is_match(process.process_state.0),
|
||||||
PrefixType::User => r.is_match(process.user.as_ref()),
|
PrefixType::User => match process.user.as_ref() {
|
||||||
|
Some(user) => r.is_match(user),
|
||||||
|
None => r.is_match("N/A"),
|
||||||
|
},
|
||||||
_ => true,
|
_ => true,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user