cpumap 0.2.1

GUI/TUI to view and edit CPU affinities of processes and threads on Linux
use std::{
    sync::mpsc,
    thread,
    time::{Duration, Instant},
};
use sysinfo::UpdateKind;
use hwloc2;

use log::*;
use simplelog;
use clap::Parser;
use std::fs::File;

mod util;
mod system;
mod uistate;
mod ordered_core_selections;
mod strings;
mod shortcuts;
mod uifrontend;
#[cfg(feature = "gui")]
mod eguifrontend;
#[cfg(feature = "tui")]
mod ratatuifrontend;
mod cpumap;
mod topologycache;

use cpumap::CPUMap;
use cpumap::Event;
use uifrontend::UIFrontendType;

/* ========== EVENT THREAD ========== */

/// Start a thread that will periodically pass Tick events through returned Receiver.
fn start_tick_thread(tick_period: Duration) -> mpsc::Receiver<Event> {
    let (tx, rx) = mpsc::channel();
    // Tick thread - sends regular ticks to the main thread
    thread::spawn(move || {
        let mut last_tick = Instant::now();
        loop {
            thread::sleep(tick_period / 10);

            if last_tick.elapsed() >= tick_period && tx.send(Event::Tick).is_ok() {
                last_tick = Instant::now();
            }
        }
    });
    rx
}

/// CLI parameters.
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
    /// Use text user interface (if cpumap was built with support for it).
    #[arg(short, long, default_value_t = false)]
    tui: bool,
    /// Log level to use. Must be OFF, ERROR, WARN, INFO, DEBUG or TRACE
    #[arg(short, long, default_value_t = simplelog::LevelFilter::Info)]
    log: simplelog::LevelFilter,
    /// Process data update interval in milliseconds.
    ///
    /// Lower values will see new/removed process sooner, with higher overhead.
    #[clap(short, long, value_parser = |secs: &str| secs.parse().map(Duration::from_millis), default_value = "5000")]
    update: Duration,
}


/// Program entry point.
fn main() -> anyhow::Result<()> {
    let args = Args::parse();
    let log_file = File::create("cpumap.log").expect("Could not create log file `cpumap.log`");

    _ = simplelog::WriteLogger::init(args.log, simplelog::Config::default(), log_file);

    info!("logging initialized");

    let event_rx = start_tick_thread(args.update); 

    let mut sys = sysinfo::System::new();
    // Ensure we have process/CPU info from the start.
    sys.refresh_processes_specifics(
        sysinfo::ProcessRefreshKind::new().with_cmd(UpdateKind::OnlyIfNotSet)
                                          .with_exe(UpdateKind::OnlyIfNotSet));
    sys.refresh_cpu();
    let topo = hwloc2::Topology::new().unwrap(); 

    let frontend_type = if cfg!(feature = "gui") && cfg!(feature = "tui") {
        if args.tui { UIFrontendType::Ratatui } else { UIFrontendType::Egui }
    } else if cfg!(feature = "gui") {
        UIFrontendType::Egui
    } else if cfg!(feature = "tui") {
        UIFrontendType::Ratatui
    } else {
        panic!("cpumap must be built with either `gui` or `tui` feature");
    };

    CPUMap::new(topo, sys, event_rx, frontend_type).run()
}