refactor: Remove ps calls

Removes and refactor ps calls that... should have not been there in the first place.
This commit is contained in:
Clement Tsang 2020-08-20 22:33:12 -07:00 committed by GitHub
parent 4b03b4b0b0
commit 1dc9346d3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 151 additions and 137 deletions

View File

@ -13,6 +13,7 @@
"WASD", "WASD",
"Wojnarowski", "Wojnarowski",
"andys", "andys",
"cmdline",
"crossterm", "crossterm",
"curr", "curr",
"czvf", "czvf",
@ -29,9 +30,12 @@
"paren", "paren",
"processthreadsapi", "processthreadsapi",
"regexes", "regexes",
"rsplitn",
"rustfmt", "rustfmt",
"shilangyu", "shilangyu",
"softirq", "softirq",
"splitn",
"statm",
"stime", "stime",
"subwidget", "subwidget",
"sysconf", "sysconf",
@ -43,6 +47,7 @@
"use curr usage", "use curr usage",
"utime", "utime",
"virt", "virt",
"vsize",
"whitespaces", "whitespaces",
"winapi", "winapi",
"winnt", "winnt",

1
Cargo.lock generated
View File

@ -145,6 +145,7 @@ dependencies = [
"heim", "heim",
"itertools", "itertools",
"lazy_static", "lazy_static",
"libc",
"log", "log",
"predicates", "predicates",
"regex", "regex",

View File

@ -40,7 +40,7 @@ backtrace = "0.3"
serde = {version = "1.0", features = ["derive"] } serde = {version = "1.0", features = ["derive"] }
unicode-segmentation = "1.6.0" unicode-segmentation = "1.6.0"
unicode-width = "0.1.7" unicode-width = "0.1.7"
# libc = "0.2.74" libc = "0.2.74"
# tui = {version = "0.10.0", features = ["crossterm"], default-features = false, git = "https://github.com/fdehau/tui-rs.git"} # tui = {version = "0.10.0", features = ["crossterm"], default-features = false, git = "https://github.com/fdehau/tui-rs.git"}
tui = {version = "0.9.5", features = ["crossterm"], default-features = false } tui = {version = "0.9.5", features = ["crossterm"], default-features = false }

View File

@ -72,7 +72,7 @@ pub struct DataCollector {
pub data: Data, pub data: Data,
sys: System, sys: System,
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
prev_pid_stats: HashMap<u32, processes::PrevProcDetails>, pid_mapping: HashMap<u32, processes::PrevProcDetails>,
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
prev_idle: f64, prev_idle: f64,
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@ -87,7 +87,7 @@ pub struct DataCollector {
widgets_to_harvest: UsedWidgets, widgets_to_harvest: UsedWidgets,
battery_manager: Option<Manager>, battery_manager: Option<Manager>,
battery_list: Option<Vec<Battery>>, battery_list: Option<Vec<Battery>>,
// page_file_size_kb: u64, page_file_size_kb: u64,
} }
impl Default for DataCollector { impl Default for DataCollector {
@ -96,7 +96,7 @@ impl Default for DataCollector {
data: Data::default(), data: Data::default(),
sys: System::new_all(), sys: System::new_all(),
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
prev_pid_stats: HashMap::new(), pid_mapping: HashMap::new(),
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
prev_idle: 0_f64, prev_idle: 0_f64,
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@ -111,11 +111,15 @@ impl Default for DataCollector {
widgets_to_harvest: UsedWidgets::default(), widgets_to_harvest: UsedWidgets::default(),
battery_manager: None, battery_manager: None,
battery_list: None, battery_list: None,
// page_file_size_kb: if cfg!(target_os = "linux") { page_file_size_kb: {
// unsafe { libc::sysconf(libc::_SC_PAGESIZE) as u64 / 1024 } #[cfg(target_os = "linux")]
// } else { unsafe {
// 0 libc::sysconf(libc::_SC_PAGESIZE) as u64 / 1024
// }, }
#[cfg(not(target_os = "linux"))]
0
},
} }
} }
} }
@ -201,12 +205,13 @@ impl DataCollector {
processes::linux_get_processes_list( processes::linux_get_processes_list(
&mut self.prev_idle, &mut self.prev_idle,
&mut self.prev_non_idle, &mut self.prev_non_idle,
&mut self.prev_pid_stats, &mut self.pid_mapping,
self.use_current_cpu_total, self.use_current_cpu_total,
current_instant current_instant
.duration_since(self.last_collection_time) .duration_since(self.last_collection_time)
.as_secs(), .as_secs(),
self.mem_total_kb, self.mem_total_kb,
self.page_file_size_kb,
) )
} }
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]

View File

@ -2,12 +2,10 @@ use std::path::PathBuf;
use sysinfo::ProcessStatus; use sysinfo::ProcessStatus;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use crate::utils::error; use crate::utils::error::{self, BottomError};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use std::{ use std::collections::{hash_map::RandomState, HashMap};
collections::{hash_map::RandomState, HashMap},
process::Command,
};
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
use sysinfo::{ProcessExt, ProcessorExt, System, SystemExt}; use sysinfo::{ProcessExt, ProcessorExt, System, SystemExt};
@ -66,7 +64,7 @@ pub struct ProcessHarvest {
// pub rss_kb: u64, // pub rss_kb: u64,
// pub virt_kb: u64, // pub virt_kb: u64,
pub name: String, pub name: String,
pub path: String, pub command: String,
pub read_bytes_per_sec: u64, pub read_bytes_per_sec: u64,
pub write_bytes_per_sec: u64, pub write_bytes_per_sec: u64,
pub total_read_bytes: u64, pub total_read_bytes: u64,
@ -81,17 +79,21 @@ pub struct PrevProcDetails {
pub total_write_bytes: u64, pub total_write_bytes: u64,
pub cpu_time: f64, pub cpu_time: f64,
pub proc_stat_path: PathBuf, pub proc_stat_path: PathBuf,
// pub proc_statm_path: PathBuf,
pub proc_exe_path: PathBuf, pub proc_exe_path: PathBuf,
pub proc_io_path: PathBuf, pub proc_io_path: PathBuf,
pub proc_cmdline_path: PathBuf,
pub just_read: bool,
} }
impl PrevProcDetails { impl PrevProcDetails {
pub fn new(pid: u32) -> Self { pub fn new(pid: u32) -> Self {
let pid_string = pid.to_string();
PrevProcDetails { PrevProcDetails {
proc_io_path: PathBuf::from(format!("/proc/{}/io", pid_string)), proc_io_path: PathBuf::from(format!("/proc/{}/io", pid)),
proc_exe_path: PathBuf::from(format!("/proc/{}/exe", pid_string)), proc_exe_path: PathBuf::from(format!("/proc/{}/exe", pid)),
proc_stat_path: PathBuf::from(format!("/proc/{}/stat", pid_string)), proc_stat_path: PathBuf::from(format!("/proc/{}/stat", pid)),
// proc_statm_path: PathBuf::from(format!("/proc/{}/statm", pid)),
proc_cmdline_path: PathBuf::from(format!("/proc/{}/cmdline", pid)),
..PrevProcDetails::default() ..PrevProcDetails::default()
} }
} }
@ -172,23 +174,32 @@ fn get_process_io(path: &PathBuf) -> std::io::Result<String> {
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn get_linux_process_io_usage(io_stats: &[&str]) -> (u64, u64) { fn get_linux_process_io_usage(stat: &[&str]) -> (u64, u64) {
// Represents read_bytes and write_bytes // Represents read_bytes and write_bytes
( (
io_stats[9].parse::<u64>().unwrap_or(0), stat[9].parse::<u64>().unwrap_or(0),
io_stats[11].parse::<u64>().unwrap_or(0), stat[11].parse::<u64>().unwrap_or(0),
) )
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn get_process_stats(path: &PathBuf) -> std::io::Result<String> { fn get_linux_process_vsize_rss(stat: &[&str]) -> (u64, u64) {
// Represents vsize and rss (bytes and page numbers respectively)
(
stat[20].parse::<u64>().unwrap_or(0),
stat[21].parse::<u64>().unwrap_or(0),
)
}
#[cfg(target_os = "linux")]
fn read_path_contents(path: &PathBuf) -> std::io::Result<String> {
Ok(std::fs::read_to_string(path)?) Ok(std::fs::read_to_string(path)?)
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn get_linux_process_state(proc_stats: &[&str]) -> (char, String) { fn get_linux_process_state(stat: &[&str]) -> (char, String) {
// The -2 offset is because of us cutting off name + pid // The -2 offset is because of us cutting off name + pid
if let Some(first_char) = proc_stats[0].chars().collect::<Vec<char>>().first() { if let Some(first_char) = stat[0].chars().collect::<Vec<char>>().first() {
( (
*first_char, *first_char,
ProcessStatus::from(*first_char).to_string().to_string(), ProcessStatus::from(*first_char).to_string().to_string(),
@ -201,103 +212,98 @@ fn get_linux_process_state(proc_stats: &[&str]) -> (char, String) {
/// Note that cpu_fraction should be represented WITHOUT the x100 factor! /// Note that cpu_fraction should be represented WITHOUT the x100 factor!
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn get_linux_cpu_usage( fn get_linux_cpu_usage(
proc_stats: &[&str], cpu_usage: f64, cpu_fraction: f64, before_proc_val: f64, proc_stats: &[&str], cpu_usage: f64, cpu_fraction: f64, prev_proc_val: &mut f64,
use_current_cpu_total: bool, use_current_cpu_total: bool,
) -> std::io::Result<(f64, f64)> { ) -> std::io::Result<f64> {
fn get_process_cpu_stats(stats: &[&str]) -> f64 { fn get_process_cpu_stats(stat: &[&str]) -> f64 {
// utime + stime (matches top), the -2 offset is because of us cutting off name + pid // utime + stime (matches top), the -2 offset is because of us cutting off name + pid (normally 13, 14)
stats[11].parse::<f64>().unwrap_or(0_f64) + stats[12].parse::<f64>().unwrap_or(0_f64) stat[11].parse::<f64>().unwrap_or(0_f64) + stat[12].parse::<f64>().unwrap_or(0_f64)
} }
// Based heavily on https://stackoverflow.com/a/23376195 and https://stackoverflow.com/a/1424556 // Based heavily on https://stackoverflow.com/a/23376195 and https://stackoverflow.com/a/1424556
let after_proc_val = get_process_cpu_stats(&proc_stats); let new_proc_val = get_process_cpu_stats(&proc_stats);
if cpu_usage == 0.0 { if cpu_usage == 0.0 {
Ok((0_f64, after_proc_val)) Ok(0_f64)
} else if use_current_cpu_total { } else if use_current_cpu_total {
Ok(( let res = Ok((new_proc_val - *prev_proc_val) / cpu_usage * 100_f64);
(after_proc_val - before_proc_val) / cpu_usage * 100_f64, *prev_proc_val = new_proc_val;
after_proc_val, res
))
} else { } else {
Ok(( let res = Ok((new_proc_val - *prev_proc_val) / cpu_usage * 100_f64 * cpu_fraction);
(after_proc_val - before_proc_val) / cpu_usage * 100_f64 * cpu_fraction, *prev_proc_val = new_proc_val;
after_proc_val, res
))
} }
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn convert_ps<S: core::hash::BuildHasher>( fn read_proc<S: core::hash::BuildHasher>(
process: &str, cpu_usage: f64, cpu_fraction: f64, pid: u32, cpu_usage: f64, cpu_fraction: f64,
prev_pid_stats: &mut HashMap<u32, PrevProcDetails, S>, pid_mapping: &mut HashMap<u32, PrevProcDetails, S>, use_current_cpu_total: bool,
new_pid_stats: &mut HashMap<u32, PrevProcDetails, S>, use_current_cpu_total: bool, time_difference_in_secs: u64, mem_total_kb: u64, page_file_kb: u64,
time_difference_in_secs: u64, mem_total_kb: u64, ) -> error::Result<ProcessHarvest> {
) -> std::io::Result<ProcessHarvest> { let pid_stat = pid_mapping
let pid = (&process[..10]) .entry(pid)
.trim() .or_insert_with(|| PrevProcDetails::new(pid));
.to_string() let stat_results = read_path_contents(&pid_stat.proc_stat_path)?;
.parse::<u32>() let name = stat_results
.unwrap_or(0); .splitn(2, '(')
let name = (&process[11..111]).trim().to_string(); .collect::<Vec<_>>()
let mem_usage_percent = (&process[112..116]) .last()
.trim() .ok_or(BottomError::MinorError())?
.to_string() .rsplitn(2, ')')
.parse::<f64>() .collect::<Vec<_>>()
.unwrap_or(0_f64); .last()
let path = (&process[117..]).trim().to_string(); .ok_or(BottomError::MinorError())?
.to_string();
let mut new_pid_stat = if let Some(prev_proc_stats) = prev_pid_stats.remove(&pid) { let command = {
prev_proc_stats let cmd = read_path_contents(&pid_stat.proc_cmdline_path)?;
if cmd.trim().is_empty() {
format!("[{}]", name)
} else { } else {
PrevProcDetails::new(pid) cmd
}
}; };
let stat = stat_results
let (cpu_usage_percent, process_state_char, process_state) = .split(')')
if let Ok(stat_results) = get_process_stats(&new_pid_stat.proc_stat_path) { .collect::<Vec<_>>()
if let Some(tmp_split) = stat_results.split(')').collect::<Vec<_>>().last() { .last()
let proc_stats = tmp_split.split_whitespace().collect::<Vec<&str>>(); .ok_or(BottomError::MinorError())?
let (process_state_char, process_state) = get_linux_process_state(&proc_stats); .split_whitespace()
.collect::<Vec<&str>>();
let (cpu_usage_percent, after_proc_val) = get_linux_cpu_usage( let (process_state_char, process_state) = get_linux_process_state(&stat);
&proc_stats, let cpu_usage_percent = get_linux_cpu_usage(
&stat,
cpu_usage, cpu_usage,
cpu_fraction, cpu_fraction,
new_pid_stat.cpu_time, &mut pid_stat.cpu_time,
use_current_cpu_total, use_current_cpu_total,
)?; )?;
new_pid_stat.cpu_time = after_proc_val; let (_vsize, rss) = get_linux_process_vsize_rss(&stat);
let mem_usage_kb = rss * page_file_kb;
(cpu_usage_percent, process_state_char, process_state) let mem_usage_percent = mem_usage_kb as f64 * 100.0 / mem_total_kb as f64;
} else {
(0.0, '?', String::new())
}
} else {
(0.0, '?', String::new())
};
// This can fail if permission is denied! // This can fail if permission is denied!
let (total_read_bytes, total_write_bytes, read_bytes_per_sec, write_bytes_per_sec) = let (total_read_bytes, total_write_bytes, read_bytes_per_sec, write_bytes_per_sec) =
if let Ok(io_results) = get_process_io(&new_pid_stat.proc_io_path) { if let Ok(io_results) = get_process_io(&pid_stat.proc_io_path) {
let io_stats = io_results.split_whitespace().collect::<Vec<&str>>(); let io_stats = io_results.split_whitespace().collect::<Vec<&str>>();
let (total_read_bytes, total_write_bytes) = get_linux_process_io_usage(&io_stats); let (total_read_bytes, total_write_bytes) = get_linux_process_io_usage(&io_stats);
let read_bytes_per_sec = if time_difference_in_secs == 0 { let read_bytes_per_sec = if time_difference_in_secs == 0 {
0 0
} else { } else {
total_read_bytes.saturating_sub(new_pid_stat.total_read_bytes) total_read_bytes.saturating_sub(pid_stat.total_read_bytes) / time_difference_in_secs
/ time_difference_in_secs
}; };
let write_bytes_per_sec = if time_difference_in_secs == 0 { let write_bytes_per_sec = if time_difference_in_secs == 0 {
0 0
} else { } else {
total_write_bytes.saturating_sub(new_pid_stat.total_write_bytes) total_write_bytes.saturating_sub(pid_stat.total_write_bytes)
/ time_difference_in_secs / time_difference_in_secs
}; };
new_pid_stat.total_read_bytes = total_read_bytes; pid_stat.total_read_bytes = total_read_bytes;
new_pid_stat.total_write_bytes = total_write_bytes; pid_stat.total_write_bytes = total_write_bytes;
( (
total_read_bytes, total_read_bytes,
@ -309,15 +315,12 @@ fn convert_ps<S: core::hash::BuildHasher>(
(0, 0, 0, 0) (0, 0, 0, 0)
}; };
new_pid_stats.insert(pid, new_pid_stat);
// TODO: Is there a way to re-use these stats so I don't have to do so many syscalls?
Ok(ProcessHarvest { Ok(ProcessHarvest {
pid, pid,
name, name,
path, command,
mem_usage_percent, mem_usage_percent,
mem_usage_kb: (mem_usage_percent * mem_total_kb as f64 / 100.0) as u64, mem_usage_kb,
cpu_usage_percent, cpu_usage_percent,
total_read_bytes, total_read_bytes,
total_write_bytes, total_write_bytes,
@ -331,45 +334,35 @@ fn convert_ps<S: core::hash::BuildHasher>(
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub fn linux_get_processes_list( pub fn linux_get_processes_list(
prev_idle: &mut f64, prev_non_idle: &mut f64, prev_idle: &mut f64, prev_non_idle: &mut f64,
prev_pid_stats: &mut HashMap<u32, PrevProcDetails, RandomState>, use_current_cpu_total: bool, pid_mapping: &mut HashMap<u32, PrevProcDetails, RandomState>, use_current_cpu_total: bool,
time_difference_in_secs: u64, mem_total_kb: u64, time_difference_in_secs: u64, mem_total_kb: u64, page_file_kb: u64,
) -> crate::utils::error::Result<Vec<ProcessHarvest>> { ) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
let ps_result = Command::new("ps")
.args(&["-axo", "pid:10,comm:100,%mem:5,args:100", "--noheader"])
.output()?;
let ps_stdout = String::from_utf8_lossy(&ps_result.stdout);
let split_string = ps_stdout.split('\n');
if let Ok((cpu_usage, cpu_fraction)) = cpu_usage_calculation(prev_idle, prev_non_idle) { if let Ok((cpu_usage, cpu_fraction)) = cpu_usage_calculation(prev_idle, prev_non_idle) {
let process_list = split_string.collect::<Vec<&str>>(); let process_vector: Vec<ProcessHarvest> = std::fs::read_dir("/proc")?
let mut new_pid_stats = HashMap::new(); .filter_map(|dir| {
if let Ok(dir) = dir {
let process_vector: Vec<ProcessHarvest> = process_list let pid = dir.file_name().to_string_lossy().trim().parse::<u32>();
.iter() if let Ok(pid) = pid {
.filter_map(|process| { // I skip checking if the path is also a directory, it's not needed I think?
if process.trim().is_empty() { if let Ok(process_object) = read_proc(
None pid,
} else if let Ok(process_object) = convert_ps(
process,
cpu_usage, cpu_usage,
cpu_fraction, cpu_fraction,
prev_pid_stats, pid_mapping,
&mut new_pid_stats,
use_current_cpu_total, use_current_cpu_total,
time_difference_in_secs, time_difference_in_secs,
mem_total_kb, mem_total_kb,
page_file_kb,
) { ) {
if !process_object.name.is_empty() { return Some(process_object);
Some(process_object)
} else {
None
} }
} else {
None
} }
}
None
}) })
.collect(); .collect();
*prev_pid_stats = new_pid_stats;
Ok(process_vector) Ok(process_vector)
} else { } else {
Ok(Vec::new()) Ok(Vec::new())
@ -405,12 +398,12 @@ pub fn windows_macos_get_processes_list(
} else { } else {
process_val.name().to_string() process_val.name().to_string()
}; };
let path = { let command = {
let path = process_val.cmd().join(" "); let command = process_val.cmd().join(" ");
if path.is_empty() { if command.is_empty() {
name.to_string() name.to_string()
} else { } else {
path command
} }
}; };
@ -430,7 +423,7 @@ pub fn windows_macos_get_processes_list(
process_vector.push(ProcessHarvest { process_vector.push(ProcessHarvest {
pid: process_val.pid() as u32, pid: process_val.pid() as u32,
name, name,
path, command,
mem_usage_percent: if mem_total_kb > 0 { mem_usage_percent: if mem_total_kb > 0 {
process_val.memory() as f64 * 100.0 / mem_total_kb as f64 process_val.memory() as f64 * 100.0 / mem_total_kb as f64
} else { } else {

View File

@ -35,6 +35,7 @@ impl Process {
/// Kills a process, given a PID. /// Kills a process, given a PID.
pub fn kill_process_given_pid(pid: u32) -> crate::utils::error::Result<()> { pub fn kill_process_given_pid(pid: u32) -> crate::utils::error::Result<()> {
if cfg!(target_os = "linux") || cfg!(target_os = "macos") { if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
// FIXME: Use libc instead of a command...
let output = Command::new("kill").arg(pid.to_string()).output()?; let output = Command::new("kill").arg(pid.to_string()).output()?;
if !(output.status).success() { if !(output.status).success() {
return Err(BottomError::GenericError( return Err(BottomError::GenericError(

View File

@ -674,6 +674,7 @@ impl Prefix {
process.cpu_percent_usage, process.cpu_percent_usage,
numerical_query.value, numerical_query.value,
), ),
// FIXME: This doesn't work with mem values!
PrefixType::Mem => matches_condition( PrefixType::Mem => matches_condition(
&numerical_query.condition, &numerical_query.condition,
process.mem_percent_usage, process.mem_percent_usage,

View File

@ -398,7 +398,7 @@ pub fn convert_process_data(
pid: process.pid, pid: process.pid,
name: match name_type { name: match name_type {
ProcessNamingType::Name => process.name.to_string(), ProcessNamingType::Name => process.name.to_string(),
ProcessNamingType::Path => process.path.to_string(), ProcessNamingType::Path => process.command.to_string(),
}, },
cpu_percent_usage: process.cpu_usage_percent, cpu_percent_usage: process.cpu_usage_percent,
mem_percent_usage: process.mem_usage_percent, mem_percent_usage: process.mem_usage_percent,
@ -425,7 +425,7 @@ pub fn convert_process_data(
let entry = grouped_hashmap let entry = grouped_hashmap
.entry(match name_type { .entry(match name_type {
ProcessNamingType::Name => process.name.to_string(), ProcessNamingType::Name => process.name.to_string(),
ProcessNamingType::Path => process.path.to_string(), ProcessNamingType::Path => process.command.to_string(),
}) })
.or_insert(SingleProcessData { .or_insert(SingleProcessData {
pid: process.pid, pid: process.pid,

View File

@ -1,3 +1,8 @@
#![warn(rust_2018_idioms)]
#[allow(unused_imports)]
#[macro_use]
extern crate log;
use std::{ use std::{
boxed::Box, boxed::Box,
io::{stdout, Write}, io::{stdout, Write},

View File

@ -24,6 +24,8 @@ pub enum BottomError {
ConversionError(String), ConversionError(String),
/// An error to represent errors with querying. /// An error to represent errors with querying.
QueryError(Cow<'static, str>), QueryError(Cow<'static, str>),
/// An error that just signifies something minor went wrong; no message.
MinorError(),
} }
impl std::fmt::Display for BottomError { impl std::fmt::Display for BottomError {
@ -50,6 +52,7 @@ impl std::fmt::Display for BottomError {
write!(f, "unable to convert: {}", message) write!(f, "unable to convert: {}", message)
} }
BottomError::QueryError(ref message) => write!(f, "{}", message), BottomError::QueryError(ref message) => write!(f, "{}", message),
BottomError::MinorError() => write!(f, "Minor error."),
} }
} }
} }