diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 85df52f909011a6eea584be7490f7cfbae3c044e..3ce88f9b7f2c934efa15eec9e6aeaeb9fd6bd5f7 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -28,6 +28,7 @@ fwd.hh null-ptr.hh contiifstream.h debug.h +real-time-logger.h dynamic-graph-api.h diff --git a/include/dynamic-graph/real-time-logger.h b/include/dynamic-graph/real-time-logger.h new file mode 100644 index 0000000000000000000000000000000000000000..55dea17f3fe0af88181eedf74baea4d8901d86be --- /dev/null +++ b/include/dynamic-graph/real-time-logger.h @@ -0,0 +1,127 @@ +// -*- mode: c++ -*- +// Copyright 2018, Joseph Mirabel LAAS-CNRS +// +// This file is part of dynamic-graph. +// dynamic-graph 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 3 of +// the License, or (at your option) any later version. +// +// dynamic-graph 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 +// General Lesser Public License for more details. You should have +// received a copy of the GNU Lesser General Public License along with +// dynamic-graph. If not, see <http://www.gnu.org/licenses/>. + +#ifndef DYNAMIC_GRAPH_LOGGER_REAL_TIME_H +# define DYNAMIC_GRAPH_LOGGER_REAL_TIME_H +# include <sstream> +# include <vector> + +# include <boost/circular_buffer.hpp> +# include <boost/shared_ptr.hpp> + +# include <dynamic-graph/config.hh> + +namespace dynamicgraph +{ + /// \ingroup debug + /// + /// \brief Stream for the real-time logger. + class LoggerStream + { + public: + virtual void write (const char* c) = 0; + }; + + class LoggerIOStream : public LoggerStream + { + public: + LoggerIOStream (std::ostream& os) : os_ (os) {} + virtual void write (const char* c) { os_ << c; } + private: + std::ostream& os_; + }; + typedef boost::shared_ptr <LoggerStream> LoggerStreamPtr_t; + + class RealTimeLogger; + class RTLoggerStream + { + public: + RTLoggerStream (RealTimeLogger* logger, std::ostream& os) : logger_(logger), os_ (os) {} + template <typename T> RTLoggerStream& operator<< (T t) { os_ << t; return *this; } + + ~RTLoggerStream(); + private: + + RealTimeLogger* logger_; + std::ostream& os_; + }; + + /// \ingroup debug + /// + /// \brief Main class of the real-time logger. + class DYNAMIC_GRAPH_DLLAPI RealTimeLogger + { + public: + /// \todo add an argument to preallocate the internal string to a given size. + RealTimeLogger (const std::size_t& bufferSize); + + inline void clearOutputStreams () { outputs_.clear(); } + + inline void addOutputStream (const LoggerStreamPtr_t& os) { outputs_.push_back(os); } + + /// The function to be called by the thread who exports the outputs + //void spin (); + + /// Write next message to output. + /// It does nothing if the buffer is empty. + /// \return true if it wrote something + bool spinOnce (); + + /// Return an object onto which a real-time thread can write. + /// The message is considered finished when the object is destroyed. + RTLoggerStream front(); + + inline void frontReady() { backIdx_ = (backIdx_+1) % buffer_.size(); } + + inline bool empty () const + { + return frontIdx_ == backIdx_; + } + + inline bool full () const + { + return ((backIdx_ + 1) % buffer_.size()) == frontIdx_; + } + + inline std::size_t size () const + { + if (frontIdx_ <= backIdx_) + return backIdx_ - frontIdx_; + else + return backIdx_ + buffer_.size() - frontIdx_; + } + + inline std::size_t getBufferSize () { return buffer_.capacity(); } + + ~RealTimeLogger (); + + private: + + struct Data { + std::stringbuf buf; + }; + + std::vector<LoggerStreamPtr_t> outputs_; + std::vector<Data*> buffer_; + /// Index of the next value to be read. + std::size_t frontIdx_; + /// Index of the slot where to write next value (does not contain valid data). + std::size_t backIdx_; + std::ostream oss_; + }; +} // end of namespace dynamicgraph + +#endif //! DYNAMIC_GRAPH_LOGGER_REAL_TIME_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6a7124c763848bcb6eeff5b2b93882b57110faaf..3e167370cb4079ec41cd6e3c64ec89627755f273 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -40,6 +40,7 @@ link_directories(${Boost_LIBRARY_DIRS}) ADD_LIBRARY(${LIBRARY_NAME} SHARED debug/debug.cpp + debug/real-time-logger.cpp dgraph/entity.cpp dgraph/factory.cpp diff --git a/src/debug/real-time-logger.cpp b/src/debug/real-time-logger.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4386b5806bbb419a1b03d2deb8a6da87990278a5 --- /dev/null +++ b/src/debug/real-time-logger.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2018, + * Joseph Mirabel + * + * LAAS-CNRS + * + * This file is part of dynamic-graph. + * dynamic-graph 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 3 of + * the License, or (at your option) any later version. + * dynamic-graph 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 dynamic-graph. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <dynamic-graph/real-time-logger.h> + +namespace dynamicgraph +{ + RealTimeLogger::RealTimeLogger (const std::size_t& bufferSize) + : buffer_(bufferSize, NULL) + , oss_ (NULL) + { + for (std::size_t i = 0; i < buffer_.size(); ++i) + buffer_[i] = new Data; + } + + RealTimeLogger::~RealTimeLogger () + { + // Check that we are not spinning... + for (std::size_t i = 0; i < buffer_.size(); ++i) delete buffer_[i]; + } + + bool RealTimeLogger::spinOnce () + { + if (empty()) return false; + Data* data = buffer_[frontIdx_]; + frontIdx_ = (frontIdx_ + 1) % buffer_.size(); + std::string str = data->buf.str(); + // It is important to pass str.c_str() and not str + // because the str object may contains a '\0' so + // str.size() may be different from strlen(str.c_str()) + for (std::size_t i = 0; i < outputs_.size(); ++i) + outputs_[i]->write (str.c_str()); + return true; + } + + RTLoggerStream RealTimeLogger::front () + { + if (outputs_.empty() || full()) { + oss_.rdbuf(NULL); + return RTLoggerStream (NULL, oss_); + } + Data* data = buffer_[backIdx_]; + //backIdx_ = (backIdx_+1) % buffer_.size(); + // Reset position of cursor + data->buf.pubseekpos(0); + oss_.rdbuf(&data->buf); + return RTLoggerStream (this, oss_); + } + + RTLoggerStream::~RTLoggerStream() + { + os_ << std::ends; + if (logger_ != NULL) logger_->frontReady(); + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4d14693d9b7097ffddbb7356779fb4c0d7b6f021..36cd5d1c10c726da06c51bc1ee8e76af2879832c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -67,3 +67,4 @@ DYNAMIC_GRAPH_TEST(pool) DYNAMIC_GRAPH_TEST(signal-time-dependent) DYNAMIC_GRAPH_TEST(value) DYNAMIC_GRAPH_TEST(signal-ptr) +DYNAMIC_GRAPH_TEST(real-time-logger) diff --git a/tests/real-time-logger.cpp b/tests/real-time-logger.cpp new file mode 100644 index 0000000000000000000000000000000000000000..064ae1003b8a70caa7817c4954acf42f838ec34a --- /dev/null +++ b/tests/real-time-logger.cpp @@ -0,0 +1,83 @@ +/* + * Copyright 2018, + * Joseph Mirabel + * + * LAAS-CNRS + * + * This file is part of dynamic-graph. + * dynamic-graph 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 3 of + * the License, or (at your option) any later version. + * dynamic-graph 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 dynamic-graph. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include <dynamic-graph/real-time-logger.h> + +#define BOOST_TEST_MODULE real_time_logger + +#include <boost/test/unit_test.hpp> +#include <boost/test/output_test_stream.hpp> + +#include <boost/thread.hpp> +#include <boost/thread/thread.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> + +using namespace dynamicgraph; + +BOOST_AUTO_TEST_CASE (monothread) +{ + RealTimeLogger rtl (10); + rtl.addOutputStream (LoggerStreamPtr_t (new LoggerIOStream(std::cout))); + for (int i = 0; i < 9; ++i) rtl.front() << "Call number " << i << '\n'; + BOOST_CHECK (rtl.full()); + rtl.front() << "This call should not appear in the output" << '\n'; + + rtl.spinOnce(); + BOOST_CHECK (!rtl.full()); + rtl.front() << "This call should appear in the output" << '\n'; + + int spinNb = 0; + while (rtl.spinOnce()) { spinNb++; } + BOOST_CHECK(spinNb == 9); + + rtl.front() << "This msg should be short." << '\n'; + rtl.spinOnce(); +} + +bool requestShutdown = false; +void spin (RealTimeLogger* logger) +{ + while (!requestShutdown || !logger->empty()) + { + // If the logger did not write anything, it means the buffer is empty. + // Do a pause + if (!logger->spinOnce()) + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + } +} + +BOOST_AUTO_TEST_CASE (multithread) +{ + RealTimeLogger rtl (10); + rtl.addOutputStream (LoggerStreamPtr_t (new LoggerIOStream(std::cout))); + + boost::thread loggerThread (spin, &rtl); + + for (int i = 0; i < 10; ++i) { + boost::this_thread::sleep(boost::posix_time::milliseconds(20)); + rtl.front() << "Call number " << i << '\n'; + BOOST_CHECK (!rtl.full()); + } + + rtl.front() << "This call should appear in the output" << '\n'; + + requestShutdown = true; + loggerThread.join(); +}