diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9ee43d4..6906de1 100755 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -51,6 +51,22 @@ set(INTEGRATIONTESTS_SRCS ${INTEGRATIONTESTS_SOURCE_DIR}/TestingAdaptor.h ${INTEGRATIONTESTS_SOURCE_DIR}/TestingProxy.h) +set(PERFTESTS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/perftests) +set(STRESSTESTS_CLIENT_SRCS + ${PERFTESTS_SOURCE_DIR}/client.cpp + ${PERFTESTS_SOURCE_DIR}/perftest-proxy.h) +set(STRESSTESTS_SERVER_SRCS + ${PERFTESTS_SOURCE_DIR}/server.cpp + ${PERFTESTS_SOURCE_DIR}/perftest-adaptor.h) + +set(STRESSTESTS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/stresstests) +set(STRESSTESTS_SRCS + ${STRESSTESTS_SOURCE_DIR}/stresstests.cpp + ${STRESSTESTS_SOURCE_DIR}/fahrenheit-thermometer-adaptor.h + ${STRESSTESTS_SOURCE_DIR}/fahrenheit-thermometer-proxy.h + ${STRESSTESTS_SOURCE_DIR}/concatenator-adaptor.h + ${STRESSTESTS_SOURCE_DIR}/concatenator-proxy.h) + #------------------------------- # GENERAL COMPILER CONFIGURATION #------------------------------- @@ -74,12 +90,22 @@ target_link_libraries(libsdbus-c++_integrationtests sdbus-c++ gmock gmock_main) # Manual performance tests option(ENABLE_PERFTESTS "Build and install manual performance tests (default OFF)" OFF) if(ENABLE_PERFTESTS) - add_executable(libsdbus-c++_perftests_client perftests/client.cpp perftests/perftest-proxy.h) + add_executable(libsdbus-c++_perftests_client ${STRESSTESTS_CLIENT_SRCS}) target_link_libraries(libsdbus-c++_perftests_client sdbus-c++) - add_executable(libsdbus-c++_perftests_server perftests/server.cpp perftests/perftest-adaptor.h) + add_executable(libsdbus-c++_perftests_server ${STRESSTESTS_SERVER_SRCS}) target_link_libraries(libsdbus-c++_perftests_server sdbus-c++) endif() +# Manual stress tests +option(ENABLE_STRESSTESTS "Build and install manual stress tests (default OFF)" ON) +if(ENABLE_STRESSTESTS) + set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(Threads REQUIRED) + + add_executable(sdbus-c++-stress-tests ${STRESSTESTS_SRCS}) + target_link_libraries(sdbus-c++-stress-tests sdbus-c++ Threads::Threads) +endif() + #---------------------------------- # INSTALLATION #---------------------------------- @@ -94,6 +120,11 @@ if(ENABLE_PERFTESTS) install(FILES perftests/files/org.sdbuscpp.perftest.conf DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/dbus-1/system.d) endif() +if(ENABLE_STRESSTESTS) + install(TARGETS sdbus-c++-stress-tests DESTINATION /opt/test/bin) + install(FILES perftests/files/org.sdbuscpp.stresstest.conf DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/dbus-1/system.d) +endif() + #---------------------------------- # RUNNING THE TESTS UPON BUILD #---------------------------------- diff --git a/test/stresstests/celsius-thermometer-adaptor.h b/test/stresstests/celsius-thermometer-adaptor.h new file mode 100755 index 0000000..372efdd --- /dev/null +++ b/test/stresstests/celsius-thermometer-adaptor.h @@ -0,0 +1,39 @@ + +/* + * This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__celsius_thermometer_adaptor_h__adaptor__H__ +#define __sdbuscpp__celsius_thermometer_adaptor_h__adaptor__H__ + +#include +#include +#include + +namespace org { +namespace sdbuscpp { +namespace stresstest { +namespace celsius { + +class thermometer_adaptor +{ +public: + static constexpr const char* interfaceName = "org.sdbuscpp.stresstest.celsius.thermometer"; + +protected: + thermometer_adaptor(sdbus::IObject& object) + : object_(object) + { + object_.registerMethod("getCurrentTemperature").onInterface(interfaceName).implementedAs([this](){ return this->getCurrentTemperature(); }); + } + +private: + virtual uint32_t getCurrentTemperature() = 0; + +private: + sdbus::IObject& object_; +}; + +}}}} // namespaces + +#endif diff --git a/test/stresstests/celsius-thermometer-proxy.h b/test/stresstests/celsius-thermometer-proxy.h new file mode 100755 index 0000000..b17c4b8 --- /dev/null +++ b/test/stresstests/celsius-thermometer-proxy.h @@ -0,0 +1,43 @@ + +/* + * This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__celsius_thermometer_proxy_h__proxy__H__ +#define __sdbuscpp__celsius_thermometer_proxy_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace sdbuscpp { +namespace stresstest { +namespace celsius { + +class thermometer_proxy +{ +public: + static constexpr const char* interfaceName = "org.sdbuscpp.stresstest.celsius.thermometer"; + +protected: + thermometer_proxy(sdbus::IObjectProxy& object) + : object_(object) + { + } + +public: + uint32_t getCurrentTemperature() + { + uint32_t result; + object_.callMethod("getCurrentTemperature").onInterface(interfaceName).storeResultsTo(result); + return result; + } + +private: + sdbus::IObjectProxy& object_; +}; + +}}}} // namespaces + +#endif diff --git a/test/stresstests/concatenator-adaptor.h b/test/stresstests/concatenator-adaptor.h new file mode 100644 index 0000000..d59b8fc --- /dev/null +++ b/test/stresstests/concatenator-adaptor.h @@ -0,0 +1,45 @@ + +/* + * This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__concatenator_adaptor_h__adaptor__H__ +#define __sdbuscpp__concatenator_adaptor_h__adaptor__H__ + +#include +#include +#include + +namespace org { +namespace sdbuscpp { +namespace stresstest { + +class concatenator_adaptor +{ +public: + static constexpr const char* interfaceName = "org.sdbuscpp.stresstest.concatenator"; + +protected: + concatenator_adaptor(sdbus::IObject& object) + : object_(object) + { + object_.registerMethod("concatenate").onInterface(interfaceName).implementedAs([this](sdbus::Result&& result, std::map params){ this->concatenate(std::move(result), std::move(params)); }); + object_.registerSignal("concatenatedSignal").onInterface(interfaceName).withParameters(); + } + +public: + void concatenatedSignal(const std::string& concatenatedString) + { + object_.emitSignal("concatenatedSignal").onInterface(interfaceName).withArguments(concatenatedString); + } + +private: + virtual void concatenate(sdbus::Result&& result, std::map params) = 0; + +private: + sdbus::IObject& object_; +}; + +}}} // namespaces + +#endif diff --git a/test/stresstests/concatenator-proxy.h b/test/stresstests/concatenator-proxy.h new file mode 100644 index 0000000..5fbf526 --- /dev/null +++ b/test/stresstests/concatenator-proxy.h @@ -0,0 +1,45 @@ + +/* + * This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__concatenator_proxy_h__proxy__H__ +#define __sdbuscpp__concatenator_proxy_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace sdbuscpp { +namespace stresstest { + +class concatenator_proxy +{ +public: + static constexpr const char* interfaceName = "org.sdbuscpp.stresstest.concatenator"; + +protected: + concatenator_proxy(sdbus::IObjectProxy& object) + : object_(object) + { + object_.uponSignal("concatenatedSignal").onInterface(interfaceName).call([this](const std::string& concatenatedString){ this->onConcatenatedSignal(concatenatedString); }); + } + + virtual void onConcatenatedSignal(const std::string& concatenatedString) = 0; + + virtual void onConcatenateReply(const std::string& result, const sdbus::Error* error) = 0; + +public: + void concatenate(const std::map& params) + { + object_.callMethodAsync("concatenate").onInterface(interfaceName).withArguments(params).uponReplyInvoke([this](const sdbus::Error* error, const std::string& result){ this->onConcatenateReply(result, error); }); + } + +private: + sdbus::IObjectProxy& object_; +}; + +}}} // namespaces + +#endif diff --git a/test/stresstests/fahrenheit-thermometer-adaptor.h b/test/stresstests/fahrenheit-thermometer-adaptor.h new file mode 100644 index 0000000..8a6f8ac --- /dev/null +++ b/test/stresstests/fahrenheit-thermometer-adaptor.h @@ -0,0 +1,39 @@ + +/* + * This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__fahrenheit_thermometer_adaptor_h__adaptor__H__ +#define __sdbuscpp__fahrenheit_thermometer_adaptor_h__adaptor__H__ + +#include +#include +#include + +namespace org { +namespace sdbuscpp { +namespace stresstest { +namespace fahrenheit { + +class thermometer_adaptor +{ +public: + static constexpr const char* interfaceName = "org.sdbuscpp.stresstest.fahrenheit.thermometer"; + +protected: + thermometer_adaptor(sdbus::IObject& object) + : object_(object) + { + object_.registerMethod("getCurrentTemperature").onInterface(interfaceName).implementedAs([this](){ return this->getCurrentTemperature(); }); + } + +private: + virtual uint32_t getCurrentTemperature() = 0; + +private: + sdbus::IObject& object_; +}; + +}}}} // namespaces + +#endif diff --git a/test/stresstests/fahrenheit-thermometer-proxy.h b/test/stresstests/fahrenheit-thermometer-proxy.h new file mode 100644 index 0000000..5cfe898 --- /dev/null +++ b/test/stresstests/fahrenheit-thermometer-proxy.h @@ -0,0 +1,43 @@ + +/* + * This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__fahrenheit_thermometer_proxy_h__proxy__H__ +#define __sdbuscpp__fahrenheit_thermometer_proxy_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace sdbuscpp { +namespace stresstest { +namespace fahrenheit { + +class thermometer_proxy +{ +public: + static constexpr const char* interfaceName = "org.sdbuscpp.stresstest.fahrenheit.thermometer"; + +protected: + thermometer_proxy(sdbus::IObjectProxy& object) + : object_(object) + { + } + +public: + uint32_t getCurrentTemperature() + { + uint32_t result; + object_.callMethod("getCurrentTemperature").onInterface(interfaceName).storeResultsTo(result); + return result; + } + +private: + sdbus::IObjectProxy& object_; +}; + +}}}} // namespaces + +#endif diff --git a/test/stresstests/files/org.sdbuscpp.stresstest.conf b/test/stresstests/files/org.sdbuscpp.stresstest.conf new file mode 100755 index 0000000..3810e4f --- /dev/null +++ b/test/stresstests/files/org.sdbuscpp.stresstest.conf @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/test/stresstests/org.sdbuscpp.stresstest.celsius.thermometer.xml b/test/stresstests/org.sdbuscpp.stresstest.celsius.thermometer.xml new file mode 100755 index 0000000..59c3f9c --- /dev/null +++ b/test/stresstests/org.sdbuscpp.stresstest.celsius.thermometer.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/test/stresstests/org.sdbuscpp.stresstest.concatenator.xml b/test/stresstests/org.sdbuscpp.stresstest.concatenator.xml new file mode 100755 index 0000000..d77a817 --- /dev/null +++ b/test/stresstests/org.sdbuscpp.stresstest.concatenator.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/test/stresstests/org.sdbuscpp.stresstest.fahrenheit.thermometer.xml b/test/stresstests/org.sdbuscpp.stresstest.fahrenheit.thermometer.xml new file mode 100755 index 0000000..17c6b0c --- /dev/null +++ b/test/stresstests/org.sdbuscpp.stresstest.fahrenheit.thermometer.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/test/stresstests/stresstests.cpp b/test/stresstests/stresstests.cpp new file mode 100755 index 0000000..424039e --- /dev/null +++ b/test/stresstests/stresstests.cpp @@ -0,0 +1,322 @@ +/** + * (C) 2019 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * + * @file stresstests.cpp + * + * Created on: Jan 25, 2019 + * Project: sdbus-c++ + * Description: High-level D-Bus IPC C++ library based on sd-bus + * + * This file is part of sdbus-c++. + * + * sdbus-c++ is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * sdbus-c++ is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with sdbus-c++. If not, see . + */ + +#include "celsius-thermometer-adaptor.h" +#include "celsius-thermometer-proxy.h" +#include "fahrenheit-thermometer-adaptor.h" +#include "fahrenheit-thermometer-proxy.h" +#include "concatenator-adaptor.h" +#include "concatenator-proxy.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; +using namespace std::string_literals; + +#define SERVICE_1_BUS_NAME "org.sdbuscpp.stresstest.service1" +#define SERVICE_2_BUS_NAME "org.sdbuscpp.stresstest.service2" +#define CELSIUS_THERMOMETER_OBJECT_PATH "/org/sdbuscpp/stresstest/celsius/thermometer" +#define FAHRENHEIT_THERMOMETER_OBJECT_PATH "/org/sdbuscpp/stresstest/fahrenheit/thermometer" +#define CONCATENATOR_OBJECT_PATH "/org/sdbuscpp/stresstest/concatenator" + +class CelsiusThermometerAdaptor : public sdbus::Interfaces +{ +public: + using sdbus::Interfaces::Interfaces; + +protected: + virtual uint32_t getCurrentTemperature() override + { + return m_currentTemperature++; + } + +private: + uint32_t m_currentTemperature{}; +}; + +class CelsiusThermometerProxy : public sdbus::ProxyInterfaces +{ +public: + using sdbus::ProxyInterfaces::ProxyInterfaces; +}; + +class FahrenheitThermometerAdaptor : public sdbus::Interfaces +{ +public: + FahrenheitThermometerAdaptor(sdbus::IConnection& connection, std::string objectPath) + : sdbus::Interfaces(connection, std::move(objectPath)) + , celsiusProxy_(connection, SERVICE_2_BUS_NAME, CELSIUS_THERMOMETER_OBJECT_PATH) + { + } + +protected: + virtual uint32_t getCurrentTemperature() override + { + // In this D-Bus call, make yet another D-Bus call to another service over the same connection + return static_cast(celsiusProxy_.getCurrentTemperature() * 1.8 + 32.); + } + +private: + CelsiusThermometerProxy celsiusProxy_; +}; + +class FahrenheitThermometerProxy : public sdbus::ProxyInterfaces +{ +public: + using sdbus::ProxyInterfaces::ProxyInterfaces; +}; + +class ConcatenatorAdaptor : public sdbus::Interfaces +{ +public: + ConcatenatorAdaptor(sdbus::IConnection& connection, std::string objectPath) + : sdbus::Interfaces(connection, std::move(objectPath)) + { + unsigned int workers = std::thread::hardware_concurrency(); + if (workers < 4) + workers = 4; + + for (unsigned int i = 0; i < workers; ++i) + workers_.emplace_back([this]() + { + //std::cout << "Created worker thread 0x" << std::hex << std::this_thread::get_id() << std::dec << std::endl; + + while(!exit_) + { + // Pop a work item from the queue + std::unique_lock lock(mutex_); + cond_.wait(lock, [this]{return !requests_.empty() || exit_;}); + if (exit_) + break; + auto request = std::move(requests_.front()); + requests_.pop(); + lock.unlock(); + + // Do concatenation work, return results and fire signal + auto aString = request.input.at("key1").get(); + auto aNumber = request.input.at("key2").get(); + auto resultString = aString + " " + std::to_string(aNumber); + + request.result.returnResults(resultString); + + concatenatedSignal(resultString); + } + }); + } + + ~ConcatenatorAdaptor() + { + exit_ = true; + cond_.notify_all(); + for (auto& worker : workers_) + worker.join(); + } + +protected: + virtual void concatenate(sdbus::Result&& result, std::map params) override + { + std::unique_lock lock(mutex_); + requests_.push(WorkItem{std::move(params), std::move(result)}); + lock.unlock(); + cond_.notify_one(); + } + +private: + struct WorkItem + { + std::map input; + sdbus::Result result; + }; + std::mutex mutex_; + std::condition_variable cond_; + std::queue requests_; + std::vector workers_; + std::atomic exit_{}; +}; + +class ConcatenatorProxy : public sdbus::ProxyInterfaces +{ +public: + using sdbus::ProxyInterfaces::ProxyInterfaces; + +private: + virtual void onConcatenateReply(const std::string& result, const sdbus::Error* error) override + { + assert(error == nullptr); + + std::stringstream str(result); + std::string aString; + str >> aString; + assert(aString == "sdbus-c++-stress-tests"); + + uint32_t aNumber; + str >> aNumber; + assert(aNumber >= 0); + + ++repliesReceived_; + } + + virtual void onConcatenatedSignal(const std::string& concatenatedString) override + { + std::stringstream str(concatenatedString); + std::string aString; + str >> aString; + assert(aString == "sdbus-c++-stress-tests"); + + uint32_t aNumber; + str >> aNumber; + assert(aNumber >= 0); + + ++signalsReceived_; + } + +public: + std::atomic repliesReceived_; + std::atomic signalsReceived_; +}; + +//----------------------------------------- +int main(int /*argc*/, char */*argv*/[]) +{ + auto service2Connection = sdbus::createSystemBusConnection(SERVICE_2_BUS_NAME); + std::thread service2Thread([&con = *service2Connection]() + { + CelsiusThermometerAdaptor thermometer(con, CELSIUS_THERMOMETER_OBJECT_PATH); + con.enterProcessingLoop(); + }); + + auto service1Connection = sdbus::createSystemBusConnection(SERVICE_1_BUS_NAME); + std::thread service1Thread([&con = *service1Connection]() + { + ConcatenatorAdaptor concatenator(con, CONCATENATOR_OBJECT_PATH); + FahrenheitThermometerAdaptor thermometer(con, FAHRENHEIT_THERMOMETER_OBJECT_PATH); + con.enterProcessingLoop(); + }); + + std::this_thread::sleep_for(100ms); + + std::atomic concatenationCallsMade{0}; + std::atomic concatenationRepliesReceived{0}; + std::atomic concatenationSignalsReceived{0}; + std::atomic thermometerCallsMade{0}; + + auto clientConnection = sdbus::createSystemBusConnection(); + std::thread clientThread([&, &con = *clientConnection]() + { + std::atomic stopClients{false}; + + std::thread concatenatorThread([&]() + { + ConcatenatorProxy concatenator(con, SERVICE_1_BUS_NAME, CONCATENATOR_OBJECT_PATH); + + uint32_t localCounter{}; + + // Issue async concatenate calls densely one after another + while (!stopClients) + { + std::map param; + param["key1"] = "sdbus-c++-stress-tests"; + param["key2"] = localCounter++; + + concatenator.concatenate(param); + + if ((localCounter % 10) == 0) + { + // Make sure the system is catching up with our async requests, + // otherwise sleep a bit to slow down flooding the server. + assert(localCounter >= concatenator.repliesReceived_); + while ((localCounter - concatenator.repliesReceived_) > 20 && !stopClients) + std::this_thread::sleep_for(2ms); + + // Update statistics + concatenationCallsMade = localCounter; + concatenationRepliesReceived = (uint32_t)concatenator.repliesReceived_; + concatenationSignalsReceived = (uint32_t)concatenator.signalsReceived_; + } + } + }); + + std::thread thermometerThread([&]() + { + FahrenheitThermometerProxy thermometer(con, SERVICE_1_BUS_NAME, FAHRENHEIT_THERMOMETER_OBJECT_PATH); + uint32_t localCounter{}; + uint32_t previousTemperature{}; + + while (!stopClients) + { + localCounter++; + auto temperature = thermometer.getCurrentTemperature(); + assert(temperature >= previousTemperature); // The temperature shall rise continually + previousTemperature = temperature; + std::this_thread::sleep_for(5ms); + + if ((localCounter % 10) == 0) + thermometerCallsMade = localCounter; + } + }); + + con.enterProcessingLoop(); + + stopClients = true; + concatenatorThread.join(); + thermometerThread.join(); + }); + + std::atomic exitLogger{}; + std::thread loggerThread([&]() + { + while (!exitLogger) + { + std::this_thread::sleep_for(1s); + + std::cout << "Made " << concatenationCallsMade << " concatenation calls, received " << concatenationRepliesReceived << " replies and " << concatenationSignalsReceived << " signals so far." << std::endl; + std::cout << "Made " << thermometerCallsMade << " thermometer calls so far." << std::endl << std::endl; + } + }); + + getchar(); + + exitLogger = true; + loggerThread.join(); + clientConnection->leaveProcessingLoop(); + clientThread.join(); + service1Connection->leaveProcessingLoop(); + service1Thread.join(); + service2Connection->leaveProcessingLoop(); + service2Thread.join(); + + return 0; +}