efficiency_benchmark/
lib.rs

1use std::env::set_current_dir;
2use std::fs::{copy, create_dir_all, metadata, read_dir, remove_dir_all, remove_file, File};
3use std::io::{BufRead, BufReader, stdout, Write, stdin};
4use std::process::{Command, exit, Stdio};
5use std::sync::mpsc::{channel, Receiver, Sender};
6use std::thread::{self, sleep};
7use std::time::Duration;
8use battery::{Manager, State};
9use colored::Colorize;
10use chrono::Local;
11
12/// Runs the benchmark.
13///
14/// This method takes in the repository URL, build command, source directory, build directory, and a flag indicating whether the repository already exists.
15/// It creates a channel for communication between threads and spawns a new thread to perform the benchmarking.
16/// If the repository does not exist, it clones the repository using `git clone` command.
17/// It then checks if the build directory exists and removes it if it does.
18/// If the system is plugged in, it waits for the system to be unplugged before starting the benchmarking.
19/// It creates a log file with a timestamp and starts an infinite loop.
20/// In each iteration of the loop, it copies the source directory to the build directory, changes the current directory to the build directory, executes the build command, changes the current directory back, removes the build directory, and increments the score in the log file.
21/// The output of each step is sent through the channel to be consumed by the main thread.
22/// The method returns an iterator over the output lines received from the channel.
23pub fn bench(repo_url: &str, build_command: &str, source_dir: &str, build_dir: &str, repo_exists: bool) -> impl Iterator<Item = String> {
24    let (sender, receiver): (Sender<String>, Receiver<String>) = channel();
25    let repo_url = repo_url.to_owned();
26    let build_command = build_command.to_owned();
27    let source_dir = source_dir.to_owned();
28    let build_dir = build_dir.to_owned();
29    thread::spawn(move || {
30        if !repo_exists {
31            let mut command = Command::new("git")
32                .arg("clone")
33                .arg("--progress")
34                .arg(&repo_url)
35                .arg("--recursive")
36                .arg(&source_dir)
37                .stdout(Stdio::piped())
38                .spawn()
39                .expect("failed to clone repository");
40    
41            let reader = BufReader::new(command.stdout.take().expect("failed to get stderr"));
42            for line in reader.lines() {
43                match line {
44                    Ok(line) => {
45                        sender.send(line.clone()).unwrap(); // Add the output line to the vector
46                    },
47                    Err(_) => {},
48                }
49            }
50        
51            command.wait().expect("failed to wait for command");
52        }
53    
54        if metadata(&build_dir).is_ok() {
55            remove_dir_all(&build_dir).unwrap();
56        }
57    
58        
59        if is_plugged(false) {
60            sender.send("Please unplug the system to start the benchmarking".to_string()).unwrap();
61            loop {
62                if !is_plugged(true){
63                    break;
64                }
65                sleep(Duration::from_secs(1));
66            }
67        }
68        
69        let current_time = Local::now().format("%d-%m-%Y_%H:%M").to_string();
70        let logfile = &format!("benchmark-{}.log", current_time);
71        if metadata(logfile).is_ok() {
72            remove_file(logfile).unwrap();
73        }
74        
75        loop {
76            // Copy build dir
77            sender.send("Copying repo".to_string()).unwrap();
78            copy_directory(&source_dir, &build_dir).expect("failed to copy src directory");
79    
80            set_current_dir(&build_dir).unwrap();
81            
82            // Build
83            sender.send("Building".to_string()).unwrap();
84            execute_build_command(&build_command);
85    
86            // Delete build dir
87            set_current_dir("../").unwrap();
88            remove_dir_all(&build_dir).unwrap();
89            
90            // Add score
91            sender.send("Build successful!".to_string()).unwrap();
92            
93            add_one(logfile);
94        }
95    });
96
97    receiver.into_iter()
98}
99
100
101fn add_one(logfile: &str) {
102    
103    if !metadata(logfile).is_ok() {
104        let mut file = File::create(logfile).unwrap();
105        file.write_all("0".as_bytes()).unwrap();
106    }
107
108    let mut reader = BufReader::new(File::open(logfile).unwrap());
109    let mut score = Vec::new(); // Change the type of score to Vec<u8>
110    reader.read_until(b'\n', &mut score).unwrap();
111    let score = String::from_utf8_lossy(&score).parse::<u32>().unwrap(); // Parse the score as u32
112    let score = score + 1; // Increment the score
113    let mut file = File::create(logfile).unwrap();
114    file.write_all(score.to_string().as_bytes()).unwrap();
115    println!("Current Score: {}", score);
116    sleep(Duration::from_secs(1));
117}
118
119/// Returns the battery percentage
120/// If on a device without battery, it will return 100
121pub fn get_battery_percentage() -> u8 {
122    let manager = Manager::new().unwrap().batteries().unwrap();
123    let battery = match manager.into_iter().next(){
124        Some(battery) => battery.unwrap(),
125        None => {
126            return 100 as u8;
127        },
128    
129    };
130    let percentage = battery.state_of_charge().value * 100.0;
131    return percentage as u8;
132}
133
134/// Returns true if the laptop is plugged in
135/// Returns false if the laptop is not plugged in
136/// If on a device without battery, it will ask the user if they want to continue
137/// and return false if the user confirms
138pub fn is_plugged(has_asked: bool) -> bool {
139    let manager = Manager::new().unwrap().batteries().unwrap();
140    let battery = match manager.into_iter().next() {
141        Some(battery) => battery,
142        None => {
143            if !has_asked {
144                println!("[{}] This benchmark is meant for laptops", "WARNING".red());
145                println!("[{}] This benchmark will infinitely loop compiling something until it runs out of battery", "WARNING".red());
146                print!("Would you like to continue anyway? [Y/N] ");
147                stdout().flush().unwrap();
148                let mut input = String::new();
149                stdin().read_line(&mut input).unwrap();
150                if input.trim().to_lowercase() != "y" {
151                    exit(1);
152                }
153            }
154            return false;
155        },
156    };
157    let state = battery.unwrap().state();
158    match state {
159        State::Charging => { return true; },
160        State::Full => { return true; },
161        _ => { return false; },
162    }
163}
164
165pub fn execute_build_command(command: &str) -> Receiver<String> {
166    let (sender, receiver) = channel();
167
168    let iterator = command.split_whitespace();
169    let mut command = Command::new(iterator.clone().next().unwrap());
170    for arg in iterator.skip(1) {
171        command.arg(arg);
172    }
173    let mut process = command
174        .stdout(Stdio::piped())
175        .spawn()
176        .expect("failed to build repository");
177    thread::spawn(move || {
178        let reader = BufReader::new(process.stdout.take().expect("failed to get stdout"));
179        for line in reader.lines() {
180            match line {
181                Ok(line) => {
182                    sender.send(line.clone()).unwrap();
183                },
184                Err(_) => {},
185            }
186        }
187    });
188
189    receiver
190}
191
192fn copy_directory(source: &str, destination: &str) -> std::io::Result<()> {
193    create_dir_all(destination)?;
194
195    for entry in read_dir(source)? {
196        let entry = entry?;
197        let entry_type = entry.file_type()?;
198        let entry_path = entry.path();
199        let destination_path = format!("{}/{}", destination, entry_path.file_name().unwrap().to_string_lossy());
200
201        if entry_type.is_dir() {
202            copy_directory(&entry_path.to_string_lossy(), &destination_path)?;
203        } else {
204            copy(&entry_path, &destination_path)?;
205        }
206    }
207
208    Ok(())
209}
210
211pub fn get_highest_score(app_dir: &str) -> u32 {
212    if metadata(app_dir).is_err() {
213        create_dir_all(app_dir).unwrap();
214    }
215    let mut highest_score = 0;
216    for entry in read_dir(app_dir).unwrap() {
217        let entry = entry.unwrap();
218        if entry.file_type().unwrap().is_file() && entry.file_name().to_string_lossy().starts_with("benchmark"){
219            let file = File::open(entry.path()).unwrap();
220            let mut reader = BufReader::new(file);
221            let mut score = Vec::new();
222            reader.read_until(b'\n', &mut score).unwrap();
223            let score = match String::from_utf8_lossy(&score).parse::<u32>(){
224                Ok(score) => score,
225                Err(_) => 0,
226            
227            };
228            if score > highest_score {
229                highest_score = score;
230            }
231        }
232        
233    }
234    highest_score
235}
236
237pub fn get_latest_score(app_dir: &str) -> u32 {
238    if metadata(app_dir).is_err() {
239        create_dir_all(app_dir).unwrap();
240    }
241    let mut latest_date = String::new();
242    let mut latest_time = String::new();
243    for entry in read_dir(app_dir).unwrap() {
244        let entry = entry.unwrap();
245        if entry.file_type().unwrap().is_file() {
246            if entry.file_name().to_string_lossy().starts_with("benchmark") {
247                let date = entry.file_name().to_str().unwrap().to_owned();
248                let date = date.replace("benchmark-", "");
249                let date = date.replace(".log", "");
250                let parts = date.split("_").collect::<Vec<&str>>();
251                let time = parts[1].to_owned();
252                let date = parts[0].to_owned();
253                if date > latest_date {
254                    latest_date = date;
255                    latest_time = time;
256                } else if date == latest_date {
257                    if time > latest_time {
258                        latest_time = time;
259                    }
260                }
261
262                
263
264            }
265        }
266    }
267    #[cfg(unix)]
268    let logfile = &format!("{}/benchmark-{}_{}.log",app_dir, latest_date, latest_time);
269    #[cfg(windows)]
270    let logfile = &format!("{}\\benchmark-{}_{}.log",app_dir, latest_date, latest_time);
271    if metadata(logfile).is_ok() {
272        let file = File::open(logfile).unwrap();
273        let mut reader = BufReader::new(file);
274        let mut score = Vec::new();
275        reader.read_until(b'\n', &mut score).unwrap();
276        let score = match String::from_utf8_lossy(&score).parse::<u32>(){
277            Ok(score) => score,
278            Err(_) => 0,
279        };
280        return score;
281    }
282    0
283}