diff --git a/CMakeLists.txt b/CMakeLists.txt index 98b536095bcf261e5596edc26d0f639e02e15037..0f353a3a9533aa8eac20067f01f6665f45dbb853 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,6 +105,7 @@ project( ${MODULE_NAME} CXX ) # Add cpprestsdk find_package(cpprestsdk REQUIRED) +find_package(libpqxx REQUIRED) # Get the Python executable (for help generation). execute_process( @@ -283,7 +284,7 @@ if ( BUILD_SHARED_LIBS ) install( TARGETS ${MODULE_NAME}_module DESTINATION ${CMAKE_INSTALL_LIBDIR} ) - target_link_libraries(${MODULE_NAME}_module PRIVATE cpprestsdk::cpprest) + target_link_libraries(${MODULE_NAME}_module PRIVATE cpprestsdk::cpprest pqxx) endif () # Build dynamic/static library for standard linking from NEST. diff --git a/Dockerfile b/Dockerfile index 2581d9c4f0567b4eced2f10b5748ddabccf0e6b3..b5121457b25a5000a5dfbb1c2c87c3ae3e485130 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ RUN apt-get update && apt-get install -y \ libwebsocketpp-dev openssl libssl-dev ninja-build \ openmpi-bin libopenmpi-dev RUN pip3 install Cython + RUN git clone --single-branch --branch nest-3 https://github.com/nest/nest-simulator.git nest && \ cd nest && \ git checkout 5c0f41230dda9e4b99b8df89729ea43b340246ad && \ @@ -20,6 +21,7 @@ RUN cmake \ -DCMAKE_BUILD_TYPE=Release \ /nest RUN ninja && ninja install + RUN git clone --single-branch --branch v2.10.14 --recurse-submodules https://github.com/microsoft/cpprestsdk.git /cpprestsdk WORKDIR /cpprestsdk-build RUN cmake \ @@ -28,6 +30,17 @@ RUN cmake \ -DBUILD_TESTS=OFF \ /cpprestsdk RUN ninja && ninja install + +RUN apt-get update && apt-get install -y libpq-dev postgresql-server-dev-all +RUN git clone --single-branch --branch 6.4.6 https://github.com/jtv/libpqxx.git /libpqxx +WORKDIR /libpqxx-build +RUN cmake \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_TESTS=OFF \ + /libpqxx +RUN ninja && ninja install + COPY . /insite WORKDIR /insite-build RUN cmake \ diff --git a/recording_backend_insite.cpp b/recording_backend_insite.cpp index 5a8b6791505b82eccac53c696b9afad7bfdcd13a..1fe1114c726116f369a51d97c401abd7c088ce87 100644 --- a/recording_backend_insite.cpp +++ b/recording_backend_insite.cpp @@ -20,26 +20,18 @@ namespace insite { RecordingBackendInsite::RecordingBackendInsite() : data_storage_("tgest"), http_server_("http://0.0.0.0:" + get_port_string(), &data_storage_), - info_node_("http://info-node:8080"), - address_("insite-nest-module:" + get_port_string()) { - web::uri_builder builder("/node"); - builder.append_query("node_type", "nest_simulation", true); - builder.append_query("address", address_, true); - - try { - info_node_.request(web::http::methods::PUT, builder.to_string()) - .then([](const web::http::http_response& response) { - if (response.status_code() != web::http::status_codes::OK) { - throw std::runtime_error(response.to_string()); - } - }) - .wait(); - } catch (const std::exception& exception) { - std::cerr << "Failed to register to info node: \n" - << exception.what() << "\n" - << std::endl; - throw; - } + database_connection_("postgresql://postgres@database") { + pqxx::work txn(database_connection_); + simulation_node_id_ = txn.exec1( + "INSERT INTO nest_simulation_node (address) " + "VALUES ('http://insite-nest-module:" + + get_port_string() + + "') " + "RETURNING id;")[0] + .as<int>(); + std::cout << "Simulation node registered to database. Node ID: " + << simulation_node_id_ << std::endl; + txn.commit(); } RecordingBackendInsite::~RecordingBackendInsite() throw() {} @@ -55,7 +47,7 @@ void RecordingBackendInsite::finalize() { void RecordingBackendInsite::enroll(const nest::RecordingDevice& device, const DictionaryDatum& params) { std::cout << "RecordingBackendInsite::enroll(" << device.get_label() << ")\n"; - + if (device.get_type() == nest::RecordingDevice::MULTIMETER) { auto id = device.get_node_id(); multimeter_infos_.emplace(std::make_pair(id, MultimeterInfo{id, true})); @@ -75,13 +67,33 @@ void RecordingBackendInsite::set_value_names( if (device.get_type() == nest::RecordingDevice::MULTIMETER) { auto& multimeter = multimeter_infos_.at(device.get_node_id()); - - for (auto& name : double_value_names) - multimeter.double_attributes.push_back(name.toString()); - for (auto& name : long_value_names) - multimeter.long_attributes.push_back(name.toString()); - multimeter.needs_update = true; + std::stringstream multimeter_query; + multimeter_query << "INSERT INTO nest_multimeter (id, attributes) " + << "VALUES (" << device.get_node_id() << ",\'{"; + + bool first = true; + for (auto& name : double_value_names) { + const auto& name_string = name.toString(); + multimeter.double_attributes.push_back(name_string); + + multimeter_query << (first ? "" : ",") << '\"' << name_string << "\""; + first = false; + } + for (auto& name : long_value_names) { + const auto& name_string = name.toString(); + multimeter.long_attributes.push_back(name_string); + + multimeter_query << (first ? "" : ",") << '\"' << name_string << "\""; + first = false; + } + multimeter_query << "}\') ON CONFLICT DO NOTHING;"; + + multimeter.needs_update = true; + + pqxx::work txn(database_connection_); + txn.exec0(multimeter_query.str()); + txn.commit(); } } @@ -102,63 +114,58 @@ void RecordingBackendInsite::post_run_hook() { void RecordingBackendInsite::post_step_hook() { // Send simulation time { - web::uri_builder builder("/current_time"); - builder.append_query("time", latest_simulation_time_, false); - builder.append_query("node_address", address_, true); - - info_node_.request(web::http::methods::PUT, builder.to_string()) - .then([](const web::http::http_response& response) { - if (response.status_code() != web::http::status_codes::OK) { - std::cerr << "Failed to send time to info node: \n" - << response.to_string() << "\n" - << std::endl; - throw std::runtime_error(response.to_string()); - } - }).wait(); // TODO: this wait definitely needs to go! + pqxx::work txn(database_connection_); + txn.exec0( + "UPDATE nest_simulation_node " + "SET current_simulation_time = " + + std::to_string(latest_simulation_time_) + + "" + "WHERE id = " + + std::to_string(simulation_node_id_)); + txn.commit(); } if (new_neuron_infos_.size() > 0) { - // Send new gids - web::uri_builder builder("/gids"); + std::stringstream neuron_query; + neuron_query << "INSERT INTO nest_neuron (id, simulation_node_id, " + "population_id, position) " + << "VALUES "; for (auto& neuron_info : new_neuron_infos_) { - builder.append_query("gids", neuron_info.gid, false); - } - builder.append_query("address", address_, true); - - info_node_.request(web::http::methods::PUT, builder.to_string()) - .then([](const web::http::http_response& response) { - if (response.status_code() != web::http::status_codes::OK) { - std::cerr << "Failed to send gids to info node: \n" - << response.to_string() << "\n" - << std::endl; - throw std::runtime_error(response.to_string()); + const bool first = neuron_info.gid == new_neuron_infos_[0].gid; + if (!first) { + neuron_query << ","; + } + + uint64_t population_id = 0; + for (const nest::NodeIDTriple& node_id_triple : + *neuron_info.gid_collection.get()) { + population_id ^= node_id_triple.node_id * 938831; + } + + neuron_query << "(" << neuron_info.gid << "," << simulation_node_id_ + << "," << population_id % 0x800000; + + const auto position_size = neuron_info.position.size(); + if (position_size > 0) { + assert(position_size <= 3); + neuron_query << ",\'{"; + for (size_t i = 0; i < position_size; ++i) { + if (i > 0) { + neuron_query << ","; } - }).wait(); - - // Send new properties - builder = web::uri_builder("/neuron_properties"); - web::json::value request_body = - web::json::value::array(new_neuron_infos_.size()); - for (std::size_t i = 0; i < new_neuron_infos_.size(); ++i) { - auto& neuron_info = new_neuron_infos_[i]; - request_body[i]["gid"] = neuron_info.gid; - request_body[i]["properties"] = web::json::value(); - if (!neuron_info.position.empty()) - request_body[i]["properties"]["position"] = - web::json::value::array(std::vector<web::json::value>( - neuron_info.position.begin(), neuron_info.position.end())); + neuron_query << neuron_info.position[i]; + } + neuron_query << "}\'"; + } else { + neuron_query << ",NULL"; + } + neuron_query << ")"; } + neuron_query << ";"; - info_node_ - .request(web::http::methods::PUT, builder.to_string(), request_body) - .then([](const web::http::http_response& response) { - if (response.status_code() != web::http::status_codes::OK) { - std::cerr << "Failed to send neuron properties to info node: \n" - << response.to_string() << "\n" - << std::endl; - throw std::runtime_error(response.to_string()); - } - }).wait(); + pqxx::work txn(database_connection_); + txn.exec0(neuron_query.str()); + txn.commit(); neuron_infos_.insert(neuron_infos_.end(), new_neuron_infos_.begin(), new_neuron_infos_.end()); @@ -169,64 +176,28 @@ void RecordingBackendInsite::post_step_hook() { // Send multimeter info for (auto& kvp : multimeter_infos_) { auto& multimeter = kvp.second; - if (!multimeter.needs_update) - continue; + if (!multimeter.needs_update) continue; multimeter.needs_update = false; - - web::uri_builder builder("/multimeter_info"); - builder.append_query("id", multimeter.device_id, true); - for (auto attribute : multimeter.double_attributes) - builder.append_query("attributes", attribute, true); - for (auto attribute : multimeter.long_attributes) - builder.append_query("attributes", attribute, true); - for (auto gid : multimeter.gids) - builder.append_query("gids", gid, false); - - try { - info_node_.request(web::http::methods::PUT, builder.to_string()) - .then([](const web::http::http_response& response) { - if (response.status_code() != web::http::status_codes::OK) { - throw std::runtime_error(response.to_string()); - } - }) - .wait(); - } catch (const std::exception& exception) { - std::cerr << "Failed to put multimeter info: \n" - << exception.what() << "\n" - << std::endl; - throw; - } - } - // Send new collections - for (const auto& node_collection : node_collections_to_register_) { - web::uri_builder builder("/populations"); - for (auto node_id_triple : *node_collection) { - builder.append_query("gids", node_id_triple.node_id, false); - } - // Initialize to "invalid" state - registered_node_collections_[node_collection] = -1; - info_node_.request(web::http::methods::PUT, builder.to_string()) - .then([this, - node_collection](const web::http::http_response& response) { - if (response.status_code() != web::http::status_codes::OK) { - std::cerr << "Failed to send population to info node: \n" - << response.to_string() << "\n" - << std::endl; - throw std::runtime_error(response.to_string()); - } else { - response.extract_json().then( - [this, node_collection](const web::json::value& population_id) { - std::cout << "Got id for node collection " << node_collection - << ": " << population_id << std::endl; - registered_node_collections_[node_collection] = - population_id.as_number().to_int64(); - }); - } - }) - .wait(); // Wait because it may cause a race condition + if (multimeter.gids.size() > 0) { + std::stringstream neuron_multimeter_query; + neuron_multimeter_query + << "INSERT INTO nest_neuron_multimeter (neuron_id, multimeter_id) " + << "VALUES "; + + for (const auto& neuron_id : multimeter.gids) { + const bool first = neuron_id == multimeter.gids[0]; + neuron_multimeter_query << (first ? "" : ",") << "(" << neuron_id << "," + << multimeter.device_id << ")"; + } + + neuron_multimeter_query << " ON CONFLICT DO NOTHING;"; + + pqxx::work txn(database_connection_); + txn.exec0(neuron_multimeter_query.str()); + txn.commit(); + } } - node_collections_to_register_.clear(); } void RecordingBackendInsite::write(const nest::RecordingDevice& device, @@ -245,19 +216,19 @@ void RecordingBackendInsite::write(const nest::RecordingDevice& device, // If the measurement is from a GID we previously do not know, add. if (!binary_search(gids.begin(), gids.end(), sender_gid)) { - gids.insert(std::lower_bound(gids.begin(), gids.end(), sender_gid), - sender_gid); + gids.insert(std::lower_bound(gids.begin(), gids.end(), sender_gid), + sender_gid); multimeter.needs_update = true; } for (std::size_t i = 0; i < double_values.size(); ++i) - data_storage_.AddMultimeterMeasurement(device_id, - multimeter.double_attributes[i], time_stamp, sender_gid, - double_values[i]); + data_storage_.AddMultimeterMeasurement( + device_id, multimeter.double_attributes[i], time_stamp, sender_gid, + double_values[i]); for (std::size_t i = 0; i < long_values.size(); ++i) - data_storage_.AddMultimeterMeasurement(device_id, - multimeter.long_attributes[i], time_stamp, sender_gid, - double(long_values[i])); + data_storage_.AddMultimeterMeasurement( + device_id, multimeter.long_attributes[i], time_stamp, sender_gid, + double(long_values[i])); } latest_simulation_time_ = std::max(latest_simulation_time_, time_stamp); @@ -278,20 +249,6 @@ void RecordingBackendInsite::write(const nest::RecordingDevice& device, std::lower_bound(new_neuron_infos_.begin(), new_neuron_infos_.end(), neuron_info), neuron_info); - - // Check if the node collection (population) is already sent to the - // info-node - const auto sender_node_collection = event.get_sender().get_nc(); - if (registered_node_collections_.count(sender_node_collection) == 0 && - !binary_search(node_collections_to_register_.begin(), - node_collections_to_register_.end(), - sender_node_collection)) { - node_collections_to_register_.insert( - std::lower_bound(node_collections_to_register_.begin(), - node_collections_to_register_.end(), - sender_node_collection), - sender_node_collection); - } } } diff --git a/recording_backend_insite.h b/recording_backend_insite.h index aeaefd31ebbfedc7bf755af8a99aac2841251ab9..aa8ec277c0b42494f00ca8a632c7cf690c100d52 100644 --- a/recording_backend_insite.h +++ b/recording_backend_insite.h @@ -3,6 +3,7 @@ #include <unordered_map> #include <cpprest/http_client.h> +#include <pqxx/pqxx> #include "data_storage.hpp" #include "http_server.hpp" @@ -62,12 +63,10 @@ class RecordingBackendInsite : public nest::RecordingBackend { DataStorage data_storage_; HttpServer http_server_; - web::http::client::http_client info_node_; - std::string address_; + pqxx::connection database_connection_; + int simulation_node_id_; std::vector<NeuronInfo> neuron_infos_; std::vector<NeuronInfo> new_neuron_infos_; - std::unordered_map<nest::NodeCollectionPTR, int64_t> registered_node_collections_; - std::vector<nest::NodeCollectionPTR> node_collections_to_register_; std::unordered_map<nest::index, MultimeterInfo> multimeter_infos_; double latest_simulation_time_ = 0; };