Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various performance improvements #224

Draft
wants to merge 41 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0962fe3
Add performance checks to clang-tidy config
hikinggrass Dec 10, 2024
3f76a88
Log module startup time for JavaScript and Python modules as well
hikinggrass Dec 10, 2024
2b17dfb
Use const ref for a few more variables and parameters
hikinggrass Dec 10, 2024
f5d4333
Remove serialize function from config
hikinggrass Dec 10, 2024
9d4da56
Refactor schema loading and validation
hikinggrass Dec 10, 2024
9d5496c
Ad some moves that clang-tidy suggested
hikinggrass Dec 10, 2024
a93bdfa
Directly pass the active_modules part of a config to the parse function
hikinggrass Dec 10, 2024
c74055b
Improve yaml file loading performance by reserving the appropriate am…
hikinggrass Dec 10, 2024
b8385de
Log the number of modules started
hikinggrass Dec 10, 2024
0bc601b
Fix narrowing conversion warning in yaml error handler by claming max…
hikinggrass Dec 10, 2024
bb1c2de
Fix widening conversion warning by multiplying two std::size_t already
hikinggrass Dec 10, 2024
0acb24f
Bump version to 0.19.2
hikinggrass Dec 10, 2024
9ddb9ce
Only publish module_names (a mapping of module type to id) once and r…
hikinggrass Dec 11, 2024
d40f7c3
Publish manifest individually
hikinggrass Dec 11, 2024
dbd4dc2
async get() function for MQTT used in get_module_config
hikinggrass Dec 12, 2024
1077b00
Fix everestjs config parsing with new config entry format
hikinggrass Dec 16, 2024
250ee4a
Capturing more vars as refs
hikinggrass Dec 16, 2024
5551fed
Log which topic cause a timeout exception in get()
hikinggrass Dec 16, 2024
c2f2f60
Use get_with_timeout in get_module_config
hikinggrass Dec 18, 2024
b96933b
Fix usage of MQTTSettings uses socket in manager
hikinggrass Dec 18, 2024
e6bf11c
Use get_module_name instead of get_module_info if only the module nam…
hikinggrass Dec 18, 2024
5d63f80
More const ref usage
hikinggrass Dec 18, 2024
04ec269
Re-order config handler in manager
hikinggrass Dec 21, 2024
4b6d16e
everestpy: initialize logging in module ctor not RuntimeSession
hikinggrass Dec 21, 2024
ed5152e
everestpy: add new RuntimeSession ctor that accepts MQTTSettings and …
hikinggrass Dec 21, 2024
30cf7e2
everestpy: add short documentation comments and deprecated RuntimeSes…
hikinggrass Dec 21, 2024
9a90570
More constref and move usage
hikinggrass Dec 21, 2024
c4737e3
Add --retain-topics flag to manager to keep retained topics after sta…
hikinggrass Dec 21, 2024
61603c9
Remove old cleanup_retained_topics functions since this is now provid…
hikinggrass Dec 21, 2024
6c19fb4
clang-format
hikinggrass Dec 21, 2024
6405e8e
Erase MessageHandler for topics without any registered handlers
hikinggrass Jan 2, 2025
7c539ac
Use .at() instead of [] for map access
hikinggrass Jan 2, 2025
1e4f79e
Exit early with EXIT_FAILURE if there are no modules to start
hikinggrass Jan 3, 2025
64e9ae2
Merge remote-tracking branch 'origin/main' into feature/performance-i…
hikinggrass Jan 3, 2025
9b68f6d
Make logged duration of get_module_config less verbose
hikinggrass Jan 6, 2025
abad2b8
Remove redundant unique_ptrs in Validators struct
hikinggrass Jan 7, 2025
3814cdd
Turn load_schema and load_schemas into free functions
hikinggrass Jan 7, 2025
ad1ecda
Move get and get_async from MQTTAbstraction to module config since it…
hikinggrass Jan 7, 2025
96835c0
load_schema and load_schemas: clang-format
hikinggrass Jan 7, 2025
b42680a
Revert to old file loading code for redability reasons
hikinggrass Jan 7, 2025
560c5ab
Merge remote-tracking branch 'origin/main' into feature/performance-i…
hikinggrass Jan 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Checks: >
bugprone*,
misc-const-correctness,
performance*,
-performance-avoid-endl,
-llvmlibc*,
-fuchsia-overloaded-operator,
-fuchsia-statically-constructed-objects,
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.14)

project(everest-framework
VERSION 0.19.1
VERSION 0.19.2
DESCRIPTION "The open operating system for e-mobility charging stations"
LANGUAGES CXX C
)
Expand Down
8 changes: 8 additions & 0 deletions everestjs/conversions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ Everest::json convertToJson(const Napi::Value& value) {
napi_valuetype_strings[value.Type()]));
}

Everest::json convertToConfigMap(const Everest::json& json_config) {
json config_map;
for (auto& entry : json_config.items()) {
config_map[entry.key()] = entry.value().at("value");
}
return config_map;
}

Comment on lines +57 to +64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a data layout problem. Not sure, if this additional data restructuring is really necessary.

Everest::TelemetryMap convertToTelemetryMap(const Napi::Object& obj) {
BOOST_LOG_FUNCTION();
Everest::TelemetryMap telemetry;
Expand Down
1 change: 1 addition & 0 deletions everestjs/conversions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ static const char* const napi_valuetype_strings[] = {
};

Everest::json convertToJson(const Napi::Value& value);
Everest::json convertToConfigMap(const Everest::json& json_config);
Everest::TelemetryMap convertToTelemetryMap(const Napi::Object& obj);
Napi::Value convertToNapiValue(const Napi::Env& env, const Everest::json& value);

Expand Down
29 changes: 18 additions & 11 deletions everestjs/everestjs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,8 @@ static Napi::Value is_condition_satisfied_req(const Requirement& req, const Napi
static Napi::Value boot_module(const Napi::CallbackInfo& info) {
BOOST_LOG_FUNCTION();

const auto start_time = std::chrono::system_clock::now();

auto env = info.Env();

auto available_handlers_prop = Napi::Object::New(env);
Expand Down Expand Up @@ -612,10 +614,10 @@ static Napi::Value boot_module(const Napi::CallbackInfo& info) {
"Module with identifier '" << module_id << "' not found in config!"));
}

const std::string& module_name = config->get_main_config()[module_id]["module"].get<std::string>();
auto module_manifest = config->get_manifests()[module_name];
const std::string& module_name = config->get_module_name(module_id);
const auto& module_manifest = config->get_manifests().at(module_name);
// FIXME (aw): get_classes should be called get_units and should contain the type of class for each unit
auto module_impls = config->get_interfaces()[module_name];
const auto& module_impls = config->get_interfaces().at(module_name);

// initialize everest framework
const auto& module_identifier = config->printable_identifier(module_id);
Expand Down Expand Up @@ -733,9 +735,9 @@ static Napi::Value boot_module(const Napi::CallbackInfo& info) {
auto uses_list_reqs_prop = Napi::Object::New(env);
auto uses_cmds_prop = Napi::Object::New(env);
auto uses_list_cmds_prop = Napi::Object::New(env);
for (auto& requirement : module_manifest["requires"].items()) {
for (const auto& requirement : module_manifest.at("requires").items()) {
auto req_prop = Napi::Object::New(env);
auto const& requirement_id = requirement.key();
const auto& requirement_id = requirement.key();
json req_route_list = config->resolve_requirement(module_id, requirement_id);
// if this was a requirement with min_connections == 1 and max_connections == 1,
// this will be simply a single connection, but an array of connections otherwise
Expand All @@ -747,13 +749,13 @@ static Napi::Value boot_module(const Napi::CallbackInfo& info) {
auto req_array_prop = Napi::Array::New(env);
auto req_mod_cmds_array = Napi::Array::New(env);
for (std::size_t i = 0; i < req_route_list.size(); i++) {
auto req_route = req_route_list[i];
const auto& req_route = req_route_list[i];
const std::string& requirement_module_id = req_route["module_id"];
const std::string& requirement_impl_id = req_route["implementation_id"];
// FIXME (aw): why is const auto& not possible for the following line?
// we only want cmds/vars from the required interface to be usable, not from it's child interfaces
std::string interface_name = req_route["required_interface"].get<std::string>();
auto requirement_impl_intf = config->get_interface_definition(interface_name);
const std::string& interface_name = req_route["required_interface"].get<std::string>();
const auto& requirement_impl_intf = config->get_interface_definition(interface_name);
auto requirement_vars = Everest::Config::keys(requirement_impl_intf["vars"]);
auto requirement_cmds = Everest::Config::keys(requirement_impl_intf["cmds"]);

Expand Down Expand Up @@ -885,14 +887,15 @@ static Napi::Value boot_module(const Napi::CallbackInfo& info) {
auto module_config_prop = Napi::Object::New(env);
auto module_config_impl_prop = Napi::Object::New(env);

for (auto& config_map : module_config.items()) {
for (const auto& config_map : module_config.items()) {
const auto& json_config_map = convertToConfigMap(config_map.value());
if (config_map.key() == "!module") {
module_config_prop.DefineProperty(Napi::PropertyDescriptor::Value(
"module", convertToNapiValue(env, config_map.value()), napi_enumerable));
"module", convertToNapiValue(env, json_config_map), napi_enumerable));
continue;
}
module_config_impl_prop.DefineProperty(Napi::PropertyDescriptor::Value(
config_map.key(), convertToNapiValue(env, config_map.value()), napi_enumerable));
config_map.key(), convertToNapiValue(env, json_config_map), napi_enumerable));
}
module_config_prop.DefineProperty(
Napi::PropertyDescriptor::Value("impl", module_config_impl_prop, napi_enumerable));
Expand Down Expand Up @@ -946,6 +949,10 @@ static Napi::Value boot_module(const Napi::CallbackInfo& info) {
ctx->js_cb = std::make_unique<JsExecCtx>(env, callback_wrapper);
ctx->everest->register_on_ready_handler(framework_ready_handler);

const auto end_time = std::chrono::system_clock::now();
EVLOG_info << "Module " << fmt::format(Everest::TERMINAL_STYLE_BLUE, "{}", module_id) << " initialized ["
<< std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count() << "ms]";

ctx->everest->spawn_main_loop_thread();

} catch (std::exception& e) {
Expand Down
14 changes: 12 additions & 2 deletions everestpy/src/everest/everestpy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,20 @@ namespace py = pybind11;

PYBIND11_MODULE(everestpy, m) {

py::class_<Everest::MQTTSettings>(m, "MQTTSettings")
.def(py::init<>())
.def_readwrite("broker_socket_path", &Everest::MQTTSettings::broker_socket_path)
.def_readwrite("broker_host", &Everest::MQTTSettings::broker_host)
.def_readwrite("broker_port", &Everest::MQTTSettings::broker_port)
.def_readwrite("everest_prefix", &Everest::MQTTSettings::everest_prefix)
.def_readwrite("external_prefix", &Everest::MQTTSettings::external_prefix)
.def("uses_socket", &Everest::MQTTSettings::uses_socket);

Comment on lines +25 to +33
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why has this been added? The more we broaden the api, the harder it gets to refactor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows everest-testing to use the MQTT based config distribution mechanism since it's (as far as I know) almost impossible to set the environment variables in the pytest environment. Usage is implemented here: EVerest/everest-utils#173

// FIXME (aw): add m.doc?
py::class_<RuntimeSession>(m, "RuntimeSession")
.def(py::init<>())
.def(py::init<const std::string&, const std::string&>());
.def(py::init<const std::string&, const std::string&>())
.def(py::init<const Everest::MQTTSettings&, const std::string&>());

py::class_<ModuleInfo::Paths>(m, "ModuleInfoPaths")
.def_readonly("etc", &ModuleInfo::Paths::etc)
Expand Down Expand Up @@ -130,7 +140,7 @@ PYBIND11_MODULE(everestpy, m) {
.def(py::init<const std::string&, const RuntimeSession&>())
.def("say_hello", &Module::say_hello)
.def("init_done", py::overload_cast<>(&Module::init_done))
.def("init_done", py::overload_cast<std::function<void()>>(&Module::init_done))
.def("init_done", py::overload_cast<const std::function<void()>&>(&Module::init_done))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a matter of taste. I'm just wondering if now any appearance of any std::function will be passed by reference instead of value?

.def("call_command", &Module::call_command)
.def("publish_variable", &Module::publish_variable)
.def("implement_command", &Module::implement_command)
Expand Down
25 changes: 16 additions & 9 deletions everestpy/src/everest/misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ static Everest::MQTTSettings get_mqtt_settings_from_env() {
}
}

RuntimeSession::RuntimeSession(const Everest::MQTTSettings& mqtt_settings, const std::string& logging_config) {
this->mqtt_settings = mqtt_settings;
if (logging_config.empty()) {
this->logging_config_file = Everest::assert_dir(Everest::defaults::PREFIX, "Default prefix") /
std::filesystem::path(Everest::defaults::SYSCONF_DIR) /
Everest::defaults::NAMESPACE / Everest::defaults::LOGGING_CONFIG_NAME;
} else {
this->logging_config_file = Everest::assert_file(logging_config, "Default logging config");
}
}

/// This is just kept for compatibility
RuntimeSession::RuntimeSession(const std::string& prefix, const std::string& config_file) {
EVLOG_warning
Expand All @@ -63,27 +74,23 @@ RuntimeSession::RuntimeSession(const std::string& prefix, const std::string& con
// We extract the settings from the config file so everest-testing doesn't break
const auto ms = Everest::ManagerSettings(prefix, config_file);

Everest::Logging::init(ms.runtime_settings->logging_config_file.string());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why has this been moved? Isn't logging already used before until the module is constructed?

this->logging_config_file = ms.runtime_settings->logging_config_file;

this->mqtt_settings = ms.mqtt_settings;
}

RuntimeSession::RuntimeSession() {
const auto module_id = get_variable_from_env("EV_MODULE");

namespace fs = std::filesystem;
const fs::path logging_config_file =
this->logging_config_file =
Everest::assert_file(get_variable_from_env("EV_LOG_CONF_FILE"), "Default logging config");
Everest::Logging::init(logging_config_file.string(), module_id);

this->mqtt_settings = get_mqtt_settings_from_env();
}

ModuleSetup create_setup_from_config(const std::string& module_id, Everest::Config& config) {
ModuleSetup setup;

const std::string& module_name = config.get_main_config().at(module_id).at("module");
const auto module_manifest = config.get_manifests().at(module_name);
const std::string& module_name = config.get_module_name(module_id);
const auto& module_manifest = config.get_manifests().at(module_name);

// setup connections
for (const auto& requirement : module_manifest.at("requires").items()) {
Expand All @@ -107,7 +114,7 @@ ModuleSetup create_setup_from_config(const std::string& module_id, Everest::Conf
const auto& req_route = req_route_list[i];
const auto fulfillment =
Fulfillment{req_route["module_id"], req_route["implementation_id"], {requirement_id, i}};
fulfillment_list.emplace_back(std::move(fulfillment));
fulfillment_list.emplace_back(fulfillment);
}
}

Expand Down
13 changes: 12 additions & 1 deletion everestpy/src/everest/misc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,27 @@ const std::string get_variable_from_env(const std::string& variable, const std::

class RuntimeSession {
public:
RuntimeSession(const std::string& prefix, const std::string& config_file);
/// \brief Allows python modules to directly pass \p mqtt_settings as well as a \p logging_config
RuntimeSession(const Everest::MQTTSettings& mqtt_settings, const std::string& logging_config);

[[deprecated("Consider switching to the newer RuntimeSession() or RuntimeSession(mqtt_settings, logging_config) "
"ctors that receive module configuration via MQTT")]] RuntimeSession(const std::string& prefix,
const std::string& config_file);

/// \brief Get settings and configuration via MQTT based on certain environment variables
RuntimeSession();

const Everest::MQTTSettings& get_mqtt_settings() const {
return mqtt_settings;
}

const std::filesystem::path& get_logging_config_file() const {
return logging_config_file;
}

private:
Everest::MQTTSettings mqtt_settings;
std::filesystem::path logging_config_file;
};

struct Interface {
Expand Down
6 changes: 4 additions & 2 deletions everestpy/src/everest/module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
std::unique_ptr<Everest::Everest>
Module::create_everest_instance(const std::string& module_id, const Everest::Config& config,
const Everest::RuntimeSettings& rs,
std::shared_ptr<Everest::MQTTAbstraction> mqtt_abstraction) {
const std::shared_ptr<Everest::MQTTAbstraction>& mqtt_abstraction) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A constant reference to a shared pointer is almost the same as the pointer itself. Everest::MQTTAbstraction& would be the better choice here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it would have probably been fine because Module will outlive Everest, but I've reverted the change in #227

return std::make_unique<Everest::Everest>(module_id, config, rs.validate_schema, mqtt_abstraction,
rs.telemetry_prefix, rs.telemetry_enabled);
}
Expand All @@ -21,7 +21,9 @@ Module::Module(const RuntimeSession& session) : Module(get_variable_from_env("EV
}

Module::Module(const std::string& module_id_, const RuntimeSession& session_) :
module_id(module_id_), session(session_) {
module_id(module_id_), session(session_), start_time(std::chrono::system_clock::now()) {

Everest::Logging::init(session.get_logging_config_file().string(), module_id);

this->mqtt_abstraction = std::make_shared<Everest::MQTTAbstraction>(session.get_mqtt_settings());
this->mqtt_abstraction->connect();
Expand Down
13 changes: 10 additions & 3 deletions everestpy/src/everest/module.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#ifndef EVERESTPY_MODULE_HPP
#define EVERESTPY_MODULE_HPP

#include <chrono>
#include <deque>
#include <functional>
#include <map>
Expand All @@ -21,13 +22,18 @@ class Module {

ModuleSetup say_hello();

void init_done(std::function<void()> on_ready_handler) {
void init_done(const std::function<void()>& on_ready_handler) {
this->handle->check_code();

if (on_ready_handler) {
handle->register_on_ready_handler(std::move(on_ready_handler));
handle->register_on_ready_handler(on_ready_handler);
}

const auto end_time = std::chrono::system_clock::now();
EVLOG_info << "Module " << fmt::format(Everest::TERMINAL_STYLE_BLUE, "{}", this->module_id) << " initialized ["
<< std::chrono::duration_cast<std::chrono::milliseconds>(end_time - this->start_time).count()
<< "ms]";

handle->signal_ready();
}

Expand Down Expand Up @@ -78,6 +84,7 @@ class Module {
private:
const std::string module_id;
const RuntimeSession& session;
const std::chrono::time_point<std::chrono::system_clock> start_time;
std::unique_ptr<Everest::RuntimeSettings> rs;
std::shared_ptr<Everest::MQTTAbstraction> mqtt_abstraction;
std::unique_ptr<Everest::Config> config_;
Expand All @@ -94,7 +101,7 @@ class Module {
static std::unique_ptr<Everest::Everest>
create_everest_instance(const std::string& module_id, const Everest::Config& config,
const Everest::RuntimeSettings& rs,
std::shared_ptr<Everest::MQTTAbstraction> mqtt_abstraction);
const std::shared_ptr<Everest::MQTTAbstraction>& mqtt_abstraction);

ModuleInfo module_info{};
std::map<std::string, Interface> requirements;
Expand Down
6 changes: 3 additions & 3 deletions include/framework/everest.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ struct ErrorFactory;
class Everest {
public:
Everest(std::string module_id, const Config& config, bool validate_data_with_schema,
std::shared_ptr<MQTTAbstraction> mqtt_abstraction, const std::string& telemetry_prefix,
const std::shared_ptr<MQTTAbstraction>& mqtt_abstraction, const std::string& telemetry_prefix,
bool telemetry_enabled);

// forbid copy assignment and copy construction
Expand All @@ -66,7 +66,7 @@ class Everest {
///
/// \brief Allows a module to indicate that it provides the given command \p cmd
///
void provide_cmd(const std::string impl_id, const std::string cmd_name, const JsonCommand handler);
void provide_cmd(const std::string& impl_id, const std::string cmd_name, const JsonCommand& handler);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why const ref'ing only impl_id and not cmd_name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #227

void provide_cmd(const cmd& cmd);

///
Expand Down Expand Up @@ -217,7 +217,7 @@ class Everest {
bool telemetry_enabled;
std::optional<ModuleTierMappings> module_tier_mappings;

void handle_ready(nlohmann::json data);
void handle_ready(const nlohmann::json& data);

void heartbeat();

Expand Down
5 changes: 3 additions & 2 deletions include/framework/runtime.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,10 @@ class ModuleLoader {

public:
explicit ModuleLoader(int argc, char* argv[], ModuleCallbacks callbacks) :
ModuleLoader(argc, argv, callbacks, {"undefined project", "undefined version", "undefined git version"}){};
ModuleLoader(argc, argv, std::move(callbacks),
{"undefined project", "undefined version", "undefined git version"}){};
explicit ModuleLoader(int argc, char* argv[], ModuleCallbacks callbacks,
const VersionInformation version_information);
const VersionInformation& version_information);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should potentially passed by value, as this information is probably only moved to the ModuleLoader.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #227


int initialize();
};
Expand Down
Loading