2017-03-12 17:10:45 -03:00
|
|
|
/* Compare two data files.
|
|
|
|
*
|
2022-03-15 09:18:01 -04:00
|
|
|
* Author: Steffen Vogel <post@steffenvogel.de>
|
2022-03-15 09:28:57 -04:00
|
|
|
* SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University
|
2022-07-04 18:20:03 +02:00
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
2017-03-12 17:10:45 -03:00
|
|
|
*/
|
|
|
|
|
2018-07-03 21:04:28 +02:00
|
|
|
#include <iostream>
|
2021-05-10 00:12:30 +02:00
|
|
|
#include <unistd.h>
|
2017-03-12 17:10:45 -03:00
|
|
|
|
|
|
|
#include <jansson.h>
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
#include <villas/exceptions.hpp>
|
2021-05-10 00:12:30 +02:00
|
|
|
#include <villas/format.hpp>
|
2018-10-20 14:20:51 +02:00
|
|
|
#include <villas/log.hpp>
|
2021-08-10 10:12:48 -04:00
|
|
|
#include <villas/node/config.hpp>
|
2023-09-07 11:46:39 +02:00
|
|
|
#include <villas/pool.hpp>
|
|
|
|
#include <villas/sample.hpp>
|
|
|
|
#include <villas/tool.hpp>
|
|
|
|
#include <villas/utils.hpp>
|
2017-03-29 06:01:50 +02:00
|
|
|
|
2018-10-20 14:20:51 +02:00
|
|
|
using namespace villas;
|
|
|
|
|
2019-04-19 14:52:52 +02:00
|
|
|
namespace villas {
|
|
|
|
namespace node {
|
|
|
|
namespace tools {
|
|
|
|
|
2021-05-10 00:12:30 +02:00
|
|
|
class CompareSide {
|
2018-10-20 14:20:51 +02:00
|
|
|
|
|
|
|
public:
|
2023-09-07 11:46:39 +02:00
|
|
|
std::string path;
|
|
|
|
std::string dtypes;
|
|
|
|
std::string format;
|
2017-09-04 18:04:48 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
struct Sample *sample;
|
2017-09-04 18:04:48 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
Format *formatter;
|
2021-05-10 00:12:30 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
FILE *stream;
|
2021-05-10 00:12:30 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
CompareSide(const CompareSide &) = delete;
|
|
|
|
CompareSide &operator=(const CompareSide &) = delete;
|
2018-10-20 14:20:51 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
CompareSide(const std::string &pth, const std::string &fmt,
|
|
|
|
const std::string &dt, struct Pool *p)
|
|
|
|
: path(pth), dtypes(dt), format(fmt) {
|
|
|
|
json_t *json_format;
|
|
|
|
json_error_t err;
|
2018-10-20 14:20:51 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
// Try parsing format config as JSON
|
|
|
|
json_format = json_loads(format.c_str(), 0, &err);
|
|
|
|
formatter = json_format ? FormatFactory::make(json_format)
|
|
|
|
: FormatFactory::make(format);
|
|
|
|
if (!formatter)
|
|
|
|
throw RuntimeError("Failed to initialize formatter");
|
2018-10-20 14:20:51 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
formatter->start(dtypes);
|
2021-05-10 00:12:30 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
stream = fopen(path.c_str(), "r");
|
|
|
|
if (!stream)
|
|
|
|
throw SystemError("Failed to open file: {}", path);
|
2018-10-20 14:20:51 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
sample = sample_alloc(p);
|
|
|
|
if (!sample)
|
|
|
|
throw RuntimeError("Failed to allocate samples");
|
|
|
|
}
|
2018-10-20 14:20:51 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
~CompareSide() noexcept(false) {
|
|
|
|
int ret __attribute((unused));
|
2018-10-20 14:20:51 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
ret = fclose(stream);
|
2021-05-10 00:12:30 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
delete formatter;
|
2018-10-20 14:20:51 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
sample_decref(sample);
|
|
|
|
}
|
2017-09-04 18:04:48 +02:00
|
|
|
};
|
|
|
|
|
2021-05-10 00:12:30 +02:00
|
|
|
class Compare : public Tool {
|
2017-03-12 17:10:45 -03:00
|
|
|
|
2019-04-19 14:52:52 +02:00
|
|
|
public:
|
2023-09-07 11:46:39 +02:00
|
|
|
Compare(int argc, char *argv[])
|
|
|
|
: Tool(argc, argv, "test-cmp"), pool(), epsilon(1e-6),
|
|
|
|
format("villas.human"), dtypes("64f"),
|
|
|
|
flags((int)SampleFlags::HAS_SEQUENCE | (int)SampleFlags::HAS_DATA |
|
|
|
|
(int)SampleFlags::HAS_TS_ORIGIN) {
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = memory::init(DEFAULT_NR_HUGEPAGES);
|
|
|
|
if (ret)
|
|
|
|
throw RuntimeError("Failed to initialize memory");
|
|
|
|
}
|
2019-04-17 18:46:18 +02:00
|
|
|
|
2019-04-19 14:52:52 +02:00
|
|
|
protected:
|
2023-09-07 11:46:39 +02:00
|
|
|
struct Pool pool;
|
|
|
|
|
|
|
|
double epsilon;
|
|
|
|
std::string format;
|
|
|
|
std::string dtypes;
|
|
|
|
int flags;
|
|
|
|
|
|
|
|
std::vector<std::string> filenames;
|
|
|
|
|
|
|
|
void usage() {
|
|
|
|
std::cout << "Usage: villas-compare [OPTIONS] FILE1 FILE2 ... FILEn"
|
|
|
|
<< std::endl
|
|
|
|
<< " FILE a list of files to compare" << std::endl
|
|
|
|
<< " OPTIONS is one or more of the following options:"
|
|
|
|
<< std::endl
|
|
|
|
<< " -d LVL adjust the debug level" << std::endl
|
|
|
|
<< " -e EPS set epsilon for floating point comparisons to EPS"
|
|
|
|
<< std::endl
|
|
|
|
<< " -v ignore data values" << std::endl
|
|
|
|
<< " -T ignore timestamp" << std::endl
|
|
|
|
<< " -s ignore sequence no" << std::endl
|
|
|
|
<< " -f FMT file format for all files" << std::endl
|
|
|
|
<< " -t DT the data-type format string" << std::endl
|
|
|
|
<< " -h show this usage information" << std::endl
|
|
|
|
<< " -V show the version of the tool" << std::endl
|
|
|
|
<< std::endl
|
|
|
|
<< "Return codes:" << std::endl
|
|
|
|
<< " 0 files are equal" << std::endl
|
|
|
|
<< " 1 file length not equal" << std::endl
|
|
|
|
<< " 2 sequence no not equal" << std::endl
|
|
|
|
<< " 3 timestamp not equal" << std::endl
|
|
|
|
<< " 4 number of values is not equal" << std::endl
|
|
|
|
<< " 5 data is not equal" << std::endl
|
|
|
|
<< std::endl;
|
|
|
|
|
|
|
|
printCopyright();
|
|
|
|
}
|
|
|
|
|
|
|
|
void parse() {
|
|
|
|
// Parse Arguments
|
|
|
|
int c;
|
|
|
|
char *endptr;
|
|
|
|
while ((c = getopt(argc, argv, "he:vTsf:t:Vd:")) != -1) {
|
|
|
|
switch (c) {
|
|
|
|
case 'e':
|
|
|
|
epsilon = strtod(optarg, &endptr);
|
|
|
|
goto check;
|
|
|
|
|
|
|
|
case 'v':
|
|
|
|
flags &= ~(int)SampleFlags::HAS_DATA;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'T':
|
|
|
|
flags &= ~(int)SampleFlags::HAS_TS_ORIGIN;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 's':
|
|
|
|
flags &= ~(int)SampleFlags::HAS_SEQUENCE;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'f':
|
|
|
|
format = optarg;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 't':
|
|
|
|
dtypes = optarg;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'V':
|
|
|
|
printVersion();
|
|
|
|
exit(EXIT_SUCCESS);
|
|
|
|
|
|
|
|
case 'd':
|
2024-07-29 12:35:42 +02:00
|
|
|
Log::getInstance().setLevel(optarg);
|
2023-09-07 11:46:39 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'h':
|
|
|
|
case '?':
|
|
|
|
usage();
|
|
|
|
exit(c == '?' ? EXIT_FAILURE : EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
check:
|
|
|
|
if (optarg == endptr)
|
|
|
|
throw RuntimeError("Failed to parse parse option argument '-{} {}'", c,
|
|
|
|
optarg);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (argc - optind < 2) {
|
|
|
|
usage();
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open files
|
|
|
|
for (int i = 0; i < argc - optind; i++)
|
|
|
|
filenames.push_back(argv[optind + i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
int main() {
|
2024-03-27 12:10:54 +01:00
|
|
|
int ret, rc = 0, failed;
|
2023-09-07 11:46:39 +02:00
|
|
|
unsigned eofs;
|
|
|
|
|
|
|
|
ret = pool_init(&pool, filenames.size(),
|
|
|
|
SAMPLE_LENGTH(DEFAULT_SAMPLE_LENGTH), &memory::heap);
|
|
|
|
if (ret)
|
|
|
|
throw RuntimeError("Failed to initialize pool");
|
|
|
|
|
|
|
|
// Open files
|
|
|
|
std::vector<CompareSide *> sides;
|
|
|
|
for (auto filename : filenames) {
|
|
|
|
auto *s = new CompareSide(filename, format, dtypes, &pool);
|
|
|
|
if (!s)
|
|
|
|
throw MemoryAllocationError();
|
|
|
|
|
|
|
|
sides.push_back(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
// Read next sample from all files
|
|
|
|
retry:
|
|
|
|
eofs = 0;
|
|
|
|
for (auto side : sides) {
|
|
|
|
ret = feof(side->stream);
|
|
|
|
if (ret)
|
|
|
|
eofs++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (eofs) {
|
|
|
|
if (eofs == sides.size())
|
|
|
|
ret = 0;
|
|
|
|
else {
|
|
|
|
std::cout << "length unequal" << std::endl;
|
|
|
|
rc = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
failed = 0;
|
|
|
|
for (auto side : sides) {
|
|
|
|
ret = side->formatter->scan(side->stream, side->sample);
|
|
|
|
if (ret <= 0)
|
|
|
|
failed++;
|
|
|
|
}
|
|
|
|
if (failed)
|
|
|
|
goto retry;
|
|
|
|
|
|
|
|
// We compare all files against the first one
|
|
|
|
for (auto side : sides) {
|
|
|
|
ret = sample_cmp(sides[0]->sample, side->sample, epsilon, flags);
|
|
|
|
if (ret) {
|
|
|
|
rc = ret;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
for (auto side : sides)
|
|
|
|
delete side;
|
|
|
|
|
|
|
|
ret = pool_destroy(&pool);
|
|
|
|
if (ret)
|
|
|
|
throw RuntimeError("Failed to destroy pool");
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
2019-04-19 14:52:52 +02:00
|
|
|
};
|
2017-03-12 17:10:45 -03:00
|
|
|
|
2023-08-28 09:34:02 +02:00
|
|
|
} // namespace tools
|
|
|
|
} // namespace node
|
|
|
|
} // namespace villas
|
2019-04-19 14:52:52 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int main(int argc, char *argv[]) {
|
|
|
|
villas::node::tools::Compare t(argc, argv);
|
2019-04-19 14:52:52 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return t.run();
|
2017-07-24 19:33:35 +02:00
|
|
|
}
|