package pl.mkaczara.bch.testbench;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import pl.mkaczara.bch.code.BCHCode;
import pl.mkaczara.bch.code.BCHCodeFactory;
import pl.mkaczara.bch.decoder.BCHDecoder;
import pl.mkaczara.bch.encoder.BCHEncoder;
import pl.mkaczara.bch.exception.UncorrectableErrorsException;
/**
* Glowna klasa aplikacji BCHlibTestbench
* @author MichaĆ
*/
public class BCHlibTestbench {
private static final String CSV_HEADER = "n,k,t,data_length[B],dec_kind,err_num,err_scope,success[%],part_success[%],avg_enc_time[ms],avg_dec_time[ms],dec_cmplx\n";
/**
* Glowna metoda programu
* @param args the command line arguments
*/
public static void main(String[] args) {
String inputPath = null;
String outputPath = null;
Options options = fillOptions();
if (args.length > 0) {
CommandLineParser parser = new BasicParser();
try {
CommandLine line = parser.parse(options, args);
if(line.hasOption("in")){
inputPath = line.getOptionValue("in");
}
if(line.hasOption("out")){
outputPath = line.getOptionValue("out");
}
}
catch (ParseException ex){
System.err.println(ex.getMessage());
}
}
if(inputPath != null){
if(outputPath == null){
outputPath = "." + File.separator + "result" + File.separator + "result_" + FilenameUtils.getBaseName(inputPath) + ".csv";
}
processTestCase(inputPath, outputPath);
}
else{
File[] testFiles = new File("." + File.separator + "test" + File.separator).listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith("test_") && name.endsWith("xml");
}
});
if (null != testFiles) {
for (File testFile : testFiles) {
outputPath = "." + File.separator + "result" + File.separator + "result_" + FilenameUtils.getBaseName(testFile.getAbsolutePath()) + ".csv";
processTestCase(testFile.getAbsolutePath(), outputPath);
}
}
}
}
/**
* Wykonuje jeden testcase
*
* @param inputPath sciezka do pliku z definicja testcase w XML
* @param outputPath sciezka gdzie maja zostac zapisane wyniki
*/
private static void processTestCase(String inputPath, String outputPath){
File outputFile = new File(outputPath);
prepareTestResultsFile(outputFile);
ArrayList<Test> tests = loadTestsFromTestCase(inputPath);
for(Test test : tests){
int positive = 0;
long encodingTimeSum = 0;
long decodingTimeSum = 0;
for(int i = 0; i < test.getRepeats(); i++){
//Random data
byte[] data = RandomStringUtils.random(test.getDataLength(), true, true).getBytes();
String inputDataHash = DigestUtils.md5Hex(data);
long start = System.currentTimeMillis();
byte[] encoded = test.getEncoder().encode(data);
long stop = System.currentTimeMillis();
encodingTimeSum += (stop - start);
encoded = test.getTransmissionSimulator().transmitData(encoded);
byte[] decoded = new byte[0];
try {
start = System.currentTimeMillis();
decoded = test.getDecoder().decode(encoded);
} catch (UncorrectableErrorsException ex) {
//System.err.println(ex.getMessage());
}
finally{
stop = System.currentTimeMillis();
decodingTimeSum += (stop - start);
}
String outputDataHash = DigestUtils.md5Hex(decoded);
if (outputDataHash.equals(inputDataHash)) {
positive++;
}
else{
//.err.println("Test failed");
}
}
saveTestResults(test, positive, encodingTimeSum, decodingTimeSum, test.getDecoder().getComplexity(), outputFile);
System.out.println(positive + " / " + test.getRepeats());
}
}
/**
* Przygotowuje plik z wynikami - zapisuje naglowek pliku CSV
*
* @param outputFile deskryptor pliku z wynikami
*/
private static void prepareTestResultsFile(File outputFile){
if(outputFile.exists()){
outputFile.delete();
}
try {
FileUtils.writeStringToFile(outputFile, CSV_HEADER, Charset.forName("UTF-8"));
} catch (IOException ex) {
System.err.println(ex.getMessage());
}
}
/**
* Dopisuje wyniki testu do pliku z wynikami
*
* @param test test, ktorego wyniki maja zostac dopisane do pliku
* @param positive wkaznik pozytywnych wykonan testu
* @param encodingTimeSum suma czasow kodowania
* @param decodingTimeSum suma czasow dekodowania
* @param outputFile deskryptor pliku z wynikami
*/
private static void saveTestResults(Test test, int positive, long encodingTimeSum, long decodingTimeSum, float decodingComplexity, File outputFile){
String line = test.getCode().getN() + ",";
line += test.getCode().getK() + ",";
line += test.getCode().getT() + ",";
line += test.getDataLength() + ",";
if(test.getDecoder().isSimpleMode()){
line += "SIMPLE,";
} else{
line += "DEDICATED,";
}
line += test.getTransmissionSimulator().getErrorsToProvide() + ",";
if(test.getTransmissionSimulator().getErrorsScope().equals(TransmissionSimulator.ErrorScope.CODEWORD_SIZE)){
line += "CODEWORD_SIZE,";
} else{
line += "CORRECTION_PART_SIZE,";
}
line += ((positive / (1.0f * test.getRepeats())) * 100.0f) + ",";
line += ((test.getDecoder().getSuccessWorkerCount() / (1.0f * test.getDecoder().getWorkerCount())) * 100.0f) + ",";
line += (encodingTimeSum / (1.0f * test.getRepeats())) + ",";
line += (decodingTimeSum / (1.0f * test.getRepeats())) + ",";
line += (int)(decodingComplexity / (1.0f * test.getDecoder().getWorkerCount())) + "\n";
try {
FileUtils.writeStringToFile(outputFile, line, Charset.forName("UTF-8"), true);
} catch (IOException ex) {
System.err.println(ex.getMessage());
}
}
/**
* Wczytuje testy z pliku z definicja testcase w XML
*
* @param inputPath sciezka do pliku z definicja XML
* @return lista testow z pliku
*/
private static ArrayList<Test> loadTestsFromTestCase(String inputPath){
ArrayList<Test> tests = new ArrayList<>();
try {
String xmlContent = FileUtils.readFileToString(new File(inputPath), Charset.forName("UTF-8"));
Document doc = DocumentHelper.parseText(xmlContent);
Iterator it = doc.getRootElement().elementIterator("test");
while(it.hasNext()){
Element testElem = (Element)it.next();
BCHCode code = BCHCodeFactory.getBCHCode(Integer.valueOf(testElem.element("code").attributeValue("codewordSize")),
Integer.valueOf(testElem.element("code").attributeValue("infoSize")));
if(null == code){
continue;
}
Test currentTest = new Test();
//Repeats
currentTest.setRepeats(Integer.valueOf(testElem.attributeValue("repeats")));
//Data length
currentTest.setDataLength(Integer.valueOf(testElem.element("data").attributeValue("length")));
//Code
currentTest.setCode(code);
//Encoder
currentTest.setEncoder(new BCHEncoder(code));
//ErrorScope
TransmissionSimulator.ErrorScope errorScope = null;
if(testElem.element("transmission").attributeValue("errorsScope").equals("CODEWORD_SIZE")){
errorScope = TransmissionSimulator.ErrorScope.CODEWORD_SIZE;
}
else if(testElem.element("transmission").attributeValue("errorsScope").equals("CORRECTION_PART_SIZE")){
errorScope = TransmissionSimulator.ErrorScope.CORRECTION_PART_SIZE;
}
currentTest.setTransmissionSimulator(new TransmissionSimulator(Integer.valueOf(testElem.element("transmission").attributeValue("errorsNumber")), errorScope, code));
//Decoder
currentTest.setDecoder(new BCHDecoder(code, Boolean.valueOf(testElem.element("decoder").attributeValue("simple"))));
tests.add(currentTest);
}
} catch (IOException | DocumentException ex) {
System.err.println(ex.getMessage());
}
return tests;
}
/**
* Konfiguruje argumenty programu ustawialne z linii komend
*
* @return metadane argumentow programu
*/
private static Options fillOptions() {
Options options = new Options();
Option input = OptionBuilder.withArgName("string").hasArg().withDescription("path to input test case in xml format").create("in");
Option output = OptionBuilder.withArgName("string").hasArg().withDescription("path to file where to write results in csv format").create("out");
options.addOption(input);
options.addOption(output);
return options;
}
}