···11+# Heater
22+33+A Rust command-line application that churns your CPU cores at maximum capacity so your laptop isn't cold
44+55+## Build
66+77+```bash
88+cargo build --release
99+```
1010+1111+The compiled binary will be at `target/release/heater`.
1212+1313+## Usage
1414+1515+### Run indefinitely (until Ctrl+C)
1616+```bash
1717+cargo run --release
1818+# or
1919+./target/release/heater
2020+```
2121+2222+### Run for a specific duration (in seconds)
2323+```bash
2424+cargo run --release -- --duration 30
2525+# or
2626+./target/release/heater --duration 30
2727+```
2828+2929+### Customize the number of free cores
3030+```bash
3131+# Leave 2 cores free
3232+cargo run --release -- --free-cores 2
3333+3434+# Use ALL cores (leave 0 free)
3535+cargo run --release -- --free-cores 0
3636+3737+# Run for 60 seconds, leaving 2 cores free
3838+cargo run --release -- --duration 60 --free-cores 2
3939+```
4040+4141+## Options
4242+4343+```
4444+-d, --duration <DURATION> Duration to run in seconds (omit for indefinite run)
4545+-f, --free-cores <FREE_CORES> Number of cores to leave free (default: 1)
4646+-h, --help Print help
4747+-V, --version Print version
4848+```
+90
src/main.rs
···11+use clap::Parser;
22+use std::sync::atomic::{AtomicBool, Ordering};
33+use std::sync::Arc;
44+use std::thread;
55+use std::time::{Duration, Instant};
66+77+#[derive(Parser, Debug)]
88+#[command(author, version, about = "A CPU heater that churns CPU cores", long_about = None)]
99+struct Args {
1010+ /// Duration to run in seconds (omit for indefinite run)
1111+ #[arg(short, long)]
1212+ duration: Option<u64>,
1313+1414+ /// Number of cores to leave free (default: 1)
1515+ #[arg(short = 'f', long, default_value_t = 1)]
1616+ free_cores: usize,
1717+}
1818+1919+fn main() {
2020+ let args = Args::parse();
2121+2222+ // Get the number of logical CPU cores
2323+ let total_cores = num_cpus::get();
2424+ let cores_to_use = total_cores.saturating_sub(args.free_cores).max(1);
2525+2626+ println!("Total CPU cores: {}", total_cores);
2727+ println!("Cores to heat: {}", cores_to_use);
2828+ println!("Cores to leave free: {}", args.free_cores);
2929+3030+ match args.duration {
3131+ Some(seconds) => println!("Running for {} seconds", seconds),
3232+ None => println!("Running indefinitely (press Ctrl+C to stop)"),
3333+ }
3434+3535+ // Set up signal handler for graceful shutdown
3636+ let running = Arc::new(AtomicBool::new(true));
3737+ let r = running.clone();
3838+3939+ ctrlc::set_handler(move || {
4040+ println!("\nReceived Ctrl+C, shutting down...");
4141+ r.store(false, Ordering::SeqCst);
4242+ })
4343+ .expect("Error setting Ctrl-C handler");
4444+4545+ let start_time = Instant::now();
4646+ let duration = args.duration.map(Duration::from_secs);
4747+4848+ // Spawn threads to churn CPU
4949+ let mut handles = vec![];
5050+ for i in 0..cores_to_use {
5151+ let running = running.clone();
5252+ let handle = thread::spawn(move || {
5353+ println!("Thread {} started", i);
5454+ let mut counter: u64 = 0;
5555+ while running.load(Ordering::SeqCst) {
5656+ //compute something meaningless but CPU-intensive
5757+ counter = counter.wrapping_add(1);
5858+ for j in 0..1000 {
5959+ counter = counter
6060+ .wrapping_mul(1103515245)
6161+ .wrapping_add(j)
6262+ .wrapping_rem(2147483648);
6363+ }
6464+ }
6565+ println!("Thread {} stopped after {} iterations", i, counter);
6666+ });
6767+ handles.push(handle);
6868+ }
6969+7070+ // Monitor duration if specified
7171+ if let Some(dur) = duration {
7272+ while start_time.elapsed() < dur && running.load(Ordering::SeqCst) {
7373+ thread::sleep(Duration::from_millis(100));
7474+ }
7575+ running.store(false, Ordering::SeqCst);
7676+ } else {
7777+ // Wait indefinitely until Ctrl+C
7878+ for handle in handles {
7979+ handle.join().unwrap();
8080+ }
8181+ return;
8282+ }
8383+8484+ // Wait for all threads to finish
8585+ for handle in handles {
8686+ handle.join().unwrap();
8787+ }
8888+8989+ println!("Finished heating for {:?}", start_time.elapsed());
9090+}