use chardetng::EncodingDetector;
use charset_normalizer_rs::consts::CHARDET_CORRESPONDENCE;
use charset_normalizer_rs::entity::{PerformanceArgs, PerformanceResult};
use charset_normalizer_rs::from_bytes;
use charset_normalizer_rs::utils::{get_large_test_datasets, round_float};
use clap::Parser;
use encoding::label::encoding_from_whatwg_label;
use log::trace;
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::process;
use std::time::{Duration, Instant};
fn calc_percentile(results: &Vec<PerformanceResult>, percentile: f64) -> Duration {
let mut sorted_data: Vec<Duration> = results.iter().map(|r| r.duration).collect();
sorted_data.sort();
let index = ((percentile / 100.0) * sorted_data.len() as f64) as usize;
sorted_data[index]
}
fn calc_stat(results: &Vec<PerformanceResult>) -> (Duration, Duration, f32) {
let durations: Vec<Duration> = results.iter().map(|r| r.duration).collect();
if durations.is_empty() {
(Duration::new(0, 0), Duration::new(0, 0), 0.0)
} else {
let total_duration: Duration = durations.iter().sum();
let num_durations = durations.len() as u32;
let accuracy = round_float(
100.0 * results.iter().filter(|r| r.correct).count() as f32 / num_durations as f32,
1,
);
(total_duration, total_duration / num_durations, accuracy)
}
}
fn performance_compare(args: &PerformanceArgs) -> i32 {
let datasets = get_large_test_datasets();
if datasets.is_err() {
println!("{}", datasets.unwrap_err());
process::exit(1);
}
let datasets = datasets.unwrap();
let nof_files = datasets.len();
println!("Found {} datasets for performance tests", nof_files);
let mut performance_results: HashMap<&str, Vec<PerformanceResult>> = HashMap::new();
let mut tested_functions: HashMap<&str, Box<dyn Fn(&Vec<u8>) -> String>> = HashMap::new();
tested_functions.insert(
"charset-normalizer-rs",
Box::new(|bytes: &Vec<u8>| {
if let Some(gb) = from_bytes(bytes, None).get_best() {
gb.encoding().to_string()
} else {
String::from("None")
}
}),
);
tested_functions.insert(
"chardet",
Box::new(|bytes: &Vec<u8>| {
let detected = &chardet::detect(bytes).0.to_ascii_lowercase();
let alternative = CHARDET_CORRESPONDENCE.get(&detected.as_str());
if let Some(r) = encoding_from_whatwg_label(&detected) {
r.whatwg_name()
.unwrap_or(alternative.unwrap_or(&r.name()))
.to_string()
} else {
String::from("None")
}
}),
);
tested_functions.insert(
"chardetng",
Box::new(|bytes: &Vec<u8>| {
let mut ed = EncodingDetector::new();
ed.feed(bytes, true);
let found = ed.guess(None, true).name();
found.to_ascii_lowercase().to_string()
}),
);
for (filename, correct_encodings) in &datasets {
println!("{}", filename);
let mut file = File::open(filename).expect(&format!("Error opening file {}", filename));
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)
.expect(&format!("Error reading from file {}", filename));
buffer = buffer.repeat(args.size_increase as usize);
for (&name, &ref foo) in &tested_functions {
if !performance_results.contains_key(name) {
performance_results.insert(name, vec![]);
}
let duration = Instant::now();
let guessed_encoding = foo(&buffer);
let duration = duration.elapsed();
performance_results
.get_mut(name)
.unwrap()
.push(PerformanceResult {
duration,
correct: correct_encodings.contains(&guessed_encoding.to_string()),
});
println!(" --> {}: {:?}", name, duration,);
if !correct_encodings.contains(&guessed_encoding.to_string()) {
trace!(
"{} WRONG DETECTION: {} not in {:?}\nSee {}",
name,
guessed_encoding,
correct_encodings,
filename,
);
}
}
}
let mut our_accuracy = 0.0;
for (&name, _) in &tested_functions {
if let Some(results) = performance_results.get(name) {
let (total_duration, mean_duration, accuracy) = calc_stat(results);
if name == "charset-normalizer-rs" {
our_accuracy = accuracy;
}
println!("\n------------------------------");
println!("--> {} Conclusions", name);
println!(" --> Accuracy: {:?}%", accuracy);
println!(" --> Total time: {:?}", total_duration);
println!(" --> Avg time: {:?}", mean_duration);
for p in [50.0, 95.0, 99.0] {
println!(" --> {}th: {:?}", p, calc_percentile(results, p));
}
}
}
if our_accuracy < 95.0 {
1
} else {
0
}
}
pub fn main() {
let args = PerformanceArgs::parse();
process::exit(performance_compare(&args));
}