1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-09 00:00:00 +01:00
VILLASnode/lib/nodes/test_rtt.cpp
Steffen Vogel cc44595f98 test_rtt: Fix possible use of uninitialized variable
Signed-off-by: Steffen Vogel <post@steffenvogel.de>
2024-05-28 19:45:55 -07:00

499 lines
14 KiB
C++

/* Node type: Node-type for testing Round-trip Time.
*
* Author: Steffen Vogel <post@steffenvogel.de>
* SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University
* SPDX-License-Identifier: Apache-2.0
*/
#include <cstdio>
#include <cstring>
#include <linux/limits.h>
#include <sys/stat.h>
#include <villas/exceptions.hpp>
#include <villas/format.hpp>
#include <villas/nodes/test_rtt.hpp>
#include <villas/sample.hpp>
#include <villas/super_node.hpp>
#include <villas/timing.hpp>
#include <villas/utils.hpp>
using namespace villas;
using namespace villas::node;
using namespace villas::utils;
static SuperNode *sn = nullptr;
int TestRTT::Case::start() {
node->logger->info("Starting case #{}/{}: filename={}, rate={}/s, values={}, "
"count={}smps, warmup={:.3f}s, cooldown={:.3f}s, "
"estimated_duration={:.3f}s",
id + 1, node->cases.size(), filename_formatted, rate,
values, count, warmup, cooldown, getEstimatedDuration());
// Open file
node->stream = fopen(filename_formatted.c_str(), "a+");
if (!node->stream)
return -1;
started = time_now();
node->formatter->reset();
// Start timer
node->task.setRate(rate);
return 0;
}
int TestRTT::Case::stop() {
int ret;
stopped = time_now();
// Stop timer
node->task.stop();
node->formatter->printMetadata(node->stream, getMetadata());
ret = fclose(node->stream);
if (ret)
throw SystemError("Failed to close file");
node->logger->info("Stopping case #{}/{}: count={}smps, sent={}smps, "
"received={}smps, missed={}smps, duration={:.3f}s",
id + 1, node->cases.size(), count, sent, received, missed,
time_delta(&started, &stopped));
return 0;
}
json_t *TestRTT::Case::getMetadata() {
json_t *json_warmup = nullptr;
if (count_warmup > 0) {
json_warmup = json_pack("{ s: i, s: i, s: i, s: i }", "count", count_warmup,
"sent", sent_warmup, "received", received_warmup,
"missed", missed_warmup);
}
return json_pack(
"{ s: i, s: f, s: i, s: f, s: f, s: i, s: i, s: i, s: i, s: o* }", "id",
id, "rate", rate, "values", values, "started", time_to_double(&started),
"stopped", time_to_double(&stopped), "count", count, "sent", sent,
"received", received, "missed", missed, "warmup", json_warmup);
}
double TestRTT::Case::getEstimatedDuration() const {
return (count_warmup + count) / rate + cooldown;
}
int TestRTT::prepare() {
unsigned max_values = 0;
// Take current for time for test case prefix
time_t ts = time(nullptr);
struct tm tm;
gmtime_r(&ts, &tm);
for (auto &c : cases) {
if (c.values > max_values)
max_values = c.values;
auto filename_formatted = new char[NAME_MAX];
if (!filename_formatted)
throw MemoryAllocationError();
strftime(filename_formatted, NAME_MAX, c.filename.c_str(), &tm);
c.filename_formatted = filename_formatted;
}
in.signals = std::make_shared<SignalList>(max_values, SignalType::FLOAT);
return Node::prepare();
}
static enum TestRTT::Mode parseMode(const char *mode_str) {
if (strcmp(mode_str, "min"))
return TestRTT::Mode::MIN;
else if (strcmp(mode_str, "max"))
return TestRTT::Mode::MIN;
else if (strcmp(mode_str, "stop_after_count"))
return TestRTT::Mode::STOP_COUNT;
else if (strcmp(mode_str, "stop_after_duration"))
return TestRTT::Mode::STOP_DURATION;
else if (strcmp(mode_str, "at_least_count"))
return TestRTT::Mode::AT_LEAST_COUNT;
else if (strcmp(mode_str, "at_least_duration"))
return TestRTT::Mode::AT_LEAST_DURATION;
else
return TestRTT::Mode::UNKNOWN;
}
int TestRTT::parse(json_t *json) {
int ret;
const char *output_str = ".";
const char *prefix_str = nullptr;
const char *mode_str = nullptr;
enum Mode mode_default = Mode::AT_LEAST_COUNT;
int count_default = 1000;
double duration_default = 300;
double warmup_default = 0;
double cooldown_default = 0;
int shutdown_ = -1;
size_t i;
json_t *json_cases, *json_case, *json_val, *json_format = nullptr;
json_t *json_rates_default = nullptr, *json_values_default = nullptr;
json_error_t err;
ret = json_unpack_ex(json, &err, 0,
"{ s?: s, s?: s, s?: o, s?: F, s?: F, s?: o, s?: o, s: "
"o, s?: b, s?: s, s?: i, s?: F }",
"prefix", &prefix_str, "output", &output_str, "format",
&json_format, "cooldown", &cooldown_default, "warmup",
&warmup_default, "values", &json_values_default, "rates",
&json_rates_default, "cases", &json_cases, "shutdown",
&shutdown_, "mode", &mode_str, "count", &count_default,
"duration", &duration_default);
if (ret)
throw ConfigError(json, err, "node-config-node-test-rtt");
output = output_str;
prefix = prefix_str ? prefix_str : getNameShort();
if (shutdown_ > 0)
shutdown = shutdown_ > 0;
if (mode_str) {
mode_default = parseMode(mode_str);
if (mode_default == Mode::UNKNOWN)
throw ConfigError(json, "node-config-node-test-rtt-mode",
"Invalid mode: {}. Must be 'min' or 'max'", mode_str);
}
// Formatter
auto *fmt = json_format ? FormatFactory::make(json_format)
: FormatFactory::make("villas.human");
formatter = Format::Ptr(fmt);
if (!formatter)
throw ConfigError(json_format, "node-config-node-test-rtt-format",
"Invalid format configuration");
if (json_rates_default && !json_is_array(json_rates_default) &&
!json_is_number(json_rates_default))
throw ConfigError(
json_rates_default, "node-config-node-test-rtt-rates",
"The 'rates' setting must be a real or an array of real numbers");
if (json_values_default && !json_is_array(json_values_default) &&
!json_is_integer(json_values_default))
throw ConfigError(
json_values_default, "node-config-node-test-rtt-values",
"The 'values' setting must be an integer or an array of integers");
// Construct List of test cases
if (!json_is_array(json_cases))
throw ConfigError(json_cases, "node-config-node-test-rtt-format",
"The 'cases' setting must be an array.");
int id = 0;
json_array_foreach(json_cases, i, json_case) {
const char *mode_str = nullptr;
int count = count_default; // in no of samples
double duration = duration_default; // in secs
double cooldown = cooldown_default; // in secs
double warmup = warmup_default; // in secs
std::vector<int> rates;
std::vector<int> values;
enum Mode mode = mode_default;
json_t *json_rates = json_rates_default;
json_t *json_values = json_values_default;
ret = json_unpack_ex(
json_case, &err, 0, "{ s?: o, s?: o, s?: i, s?: F, s?: s }", "rates",
&json_rates, "values", &json_values, "count", &count, "duration",
&duration, "warmup", &warmup, "cooldown", &cooldown, "mode", &mode_str);
if (!json_is_array(json_rates) && !json_is_number(json_rates))
throw ConfigError(
json_case, "node-config-node-test-rtt-rates",
"The 'rates' setting must be a real or an array of real numbers");
if (!json_is_array(json_values) && !json_is_integer(json_values))
throw ConfigError(
json_case, "node-config-node-test-rtt-values",
"The 'values' setting must be an integer or an array of integers");
if (mode_str) {
mode = parseMode(mode_str);
if (mode == Mode::UNKNOWN)
throw ConfigError(json, "node-config-node-test-rtt-mode",
"Invalid mode: {}. Must be 'min' or 'max'", mode_str);
}
if (json_is_array(json_rates)) {
size_t j;
json_array_foreach(json_rates, j, json_val) {
if (!json_is_number(json_val))
throw ConfigError(
json_val, "node-config-node-test-rtt-rates",
"The 'rates' setting must be an array of real numbers");
rates.push_back(json_integer_value(json_val));
}
} else
rates.push_back(json_number_value(json_rates));
if (json_is_array(json_values)) {
size_t j;
json_array_foreach(json_values, j, json_val) {
if (!json_is_integer(json_val))
throw ConfigError(
json_val, "node-config-node-test-rtt-values",
"The 'values' setting must be an array of integers");
values.push_back(json_integer_value(json_val));
}
} else
values.push_back(json_integer_value(json_values));
for (int rate : rates) {
for (int value : values) {
int count_effective;
int count_duration = duration * rate;
int count_warmup = warmup * rate;
switch (mode) {
case Mode::MIN:
count_effective = MIN(count, count_duration);
break;
case Mode::MAX:
count_effective = MAX(count, count_duration);
break;
case Mode::STOP_COUNT:
count_effective = count_duration;
if (count_effective > count)
count_effective = count;
break;
case Mode::STOP_DURATION:
count_effective = count;
if (count_effective > count_duration)
count_effective = count_duration;
break;
case Mode::AT_LEAST_COUNT:
count_effective = count_duration;
if (count_effective < count)
count_effective = count;
break;
case Mode::AT_LEAST_DURATION:
count_effective = count;
if (count_effective < count_duration)
count_effective = count_duration;
break;
default: {
count_effective = 0;
break;
}
}
auto filename = fmt::format("{}/{}_values{}_rate{}.log", output, prefix,
value, rate);
cases.emplace_back(this, id++, rate, warmup, cooldown, value,
count_effective, count_warmup, filename);
}
}
}
return 0;
}
const std::string &TestRTT::getDetails() {
if (details.empty()) {
details = fmt::format("output={}, prefix={}, #cases={}, shutdown={}, "
"estimated_duration={:.3f}s",
output, prefix, cases.size(), shutdown,
getEstimatedDuration());
}
return details;
}
int TestRTT::start() {
int ret;
struct stat st;
// Create output folder for results if not present
ret = stat(output.c_str(), &st);
if (ret || !S_ISDIR(st.st_mode)) {
ret = mkdir(output.c_str(), 0777);
if (ret)
throw SystemError("Failed to create output directory: {}", output);
}
formatter->start(getInputSignals(false), ~(int)SampleFlags::HAS_DATA);
current = cases.begin();
task.setRate(current->rate);
ret = Node::start();
if (!ret)
state = State::STARTED;
return 0;
}
int TestRTT::stop() {
int ret;
if (current != cases.end() && current->received > 0) {
ret = current->stop();
if (ret)
return ret;
}
return 0;
}
int TestRTT::_read(struct Sample *smps[], unsigned cnt) {
int ret;
// Wait for next sample or cooldown
auto steps = task.wait();
if (steps > 1) {
logger->warn("Skipped {} steps", steps - 1);
if (current->sent_warmup < current->count_warmup)
current->missed_warmup += steps - 1;
else
current->missed += steps - 1;
}
// Cooldown of case completed..
if (current->sent + current->sent_warmup >=
current->count + current->count_warmup) {
ret = current->stop();
if (ret)
return ret;
if (++current == cases.end()) {
logger->info("This was the last case.");
setState(State::STOPPING);
if (sn && shutdown)
sn->setState(State::STOPPING);
return -1;
}
}
// Handle start/stop of new cases
if (current->sent + current->sent_warmup == 0) {
ret = current->start();
if (ret)
return ret;
if (current->count_warmup > 0)
logger->info("Starting warmup phase. Sending {} samples...",
current->count_warmup);
} else if (current->sent + current->sent_warmup == current->count_warmup)
logger->info("Completed warmup phase. Sending {} samples...",
current->count);
auto now = time_now();
// Prepare samples
unsigned i;
for (i = 0; i < cnt; i++) {
auto smp = smps[i];
smp->length = current->values;
smp->sequence = current->sent + current->sent_warmup;
smp->ts.origin = now;
smp->flags = (int)SampleFlags::HAS_DATA | (int)SampleFlags::HAS_SEQUENCE |
(int)SampleFlags::HAS_TS_ORIGIN;
smp->signals = getInputSignals(false);
if (current->sent_warmup < current->count_warmup)
current->sent_warmup++;
else
current->sent++;
}
if (current->sent + current->sent_warmup >=
current->count + current->count_warmup) {
if (current->cooldown > 0) {
logger->info("Entering cooldown phase. Waiting {} seconds...",
current->cooldown);
task.setTimeout(current->cooldown);
} else {
task.setTimeout(0); // Start next case immediately
}
}
return i;
}
int TestRTT::_write(struct Sample *smps[], unsigned cnt) {
if (current == cases.end())
return 0;
unsigned i;
for (i = 0; i < cnt; i++) {
auto *smp = smps[i];
if (smp->length != current->values) {
logger->warn("Discarding invalid sample due to mismatching length: "
"expecting={}, has={}",
current->values, smp->length);
continue;
}
if (smp->sequence < current->count_warmup) {
// Skip samples from warmup phase
current->received_warmup++;
continue;
}
formatter->print(stream, smps[i]);
current->received++;
}
return i;
}
std::vector<int> TestRTT::getPollFDs() { return {task.getFD()}; }
double TestRTT::getEstimatedDuration() const {
double duration = 0;
for (auto &c : cases) {
duration += c.getEstimatedDuration();
}
return duration;
}
int TestRTTNodeFactory::start(SuperNode *sn_) {
sn = sn_;
return 0;
}
static TestRTTNodeFactory p;