From 01e522ef84a7371630554edc4d9cb7add85cd608 Mon Sep 17 00:00:00 2001 From: Terraneo Federico <fede.tft@miosix.org> Date: Sat, 18 Aug 2018 15:25:18 +0200 Subject: [PATCH] Added logger example --- .gitmodules | 3 + miosix/_examples/datalogger/ExampleData.h | 55 +++ miosix/_examples/datalogger/LogStats.h | 72 ++++ miosix/_examples/datalogger/Logger.cpp | 327 ++++++++++++++++++ miosix/_examples/datalogger/Logger.h | 208 +++++++++++ miosix/_examples/datalogger/Makefile | 108 ++++++ miosix/_examples/datalogger/Readme.txt | 30 ++ .../_examples/datalogger/logdecoder/Makefile | 6 + .../datalogger/logdecoder/logdecoder.cpp | 59 ++++ miosix/_examples/datalogger/main.cpp | 72 ++++ miosix/_examples/datalogger/tscpp | 1 + 11 files changed, 941 insertions(+) create mode 100644 .gitmodules create mode 100644 miosix/_examples/datalogger/ExampleData.h create mode 100644 miosix/_examples/datalogger/LogStats.h create mode 100644 miosix/_examples/datalogger/Logger.cpp create mode 100644 miosix/_examples/datalogger/Logger.h create mode 100644 miosix/_examples/datalogger/Makefile create mode 100644 miosix/_examples/datalogger/Readme.txt create mode 100644 miosix/_examples/datalogger/logdecoder/Makefile create mode 100644 miosix/_examples/datalogger/logdecoder/logdecoder.cpp create mode 100644 miosix/_examples/datalogger/main.cpp create mode 160000 miosix/_examples/datalogger/tscpp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..7c6898ac --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "miosix/_examples/datalogger/tscpp"] + path = miosix/_examples/datalogger/tscpp + url = https://github.com/fedetft/tscpp.git diff --git a/miosix/_examples/datalogger/ExampleData.h b/miosix/_examples/datalogger/ExampleData.h new file mode 100644 index 00000000..c70f7d3d --- /dev/null +++ b/miosix/_examples/datalogger/ExampleData.h @@ -0,0 +1,55 @@ +/*************************************************************************** + * Copyright (C) 2018 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see <http://www.gnu.org/licenses/> * + ***************************************************************************/ + +#pragma once + +#include <ostream> + +/** + * Example class for data to be logged. + * Start from this class to define classes with the information you want to log. + */ +class ExampleData +{ +public: + ExampleData() {} + + ExampleData(int a, int b, long long timestamp) : a(a), b(b), timestamp(timestamp) {} + + /** + * Print the class fields to an ostream. + * Used by the program that decodes the logged data. + * \param os ostream where to print the class fields + */ + void print(std::ostream& os) const + { + os << "timestamp=" << timestamp << " a=" << a << " b=" << b; + } + + int a, b; + long long timestamp; +}; diff --git a/miosix/_examples/datalogger/LogStats.h b/miosix/_examples/datalogger/LogStats.h new file mode 100644 index 00000000..5c897af4 --- /dev/null +++ b/miosix/_examples/datalogger/LogStats.h @@ -0,0 +1,72 @@ +/*************************************************************************** + * Copyright (C) 2018 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see <http://www.gnu.org/licenses/> * + ***************************************************************************/ + +#pragma once + +#include <ostream> + +/** + * Statistics for the logger + */ +class LogStats +{ +public: + /** + * Constructor + */ + LogStats() {} + + /** + * Set timestamp for this class + * \param timestamp timestamp + */ + void setTimestamp(long long timestamp) { this->timestamp = timestamp; } + + /** + * Print the class fields to an ostream. + * Used by the program that decodes the logged data. + * \param os ostream where to print the class fields + */ + void print(std::ostream& os) const + { + os << "timestamp=" << timestamp + << " ls=" << statTooLargeSamples << " ds=" << statDroppedSamples + << " qs=" << statQueuedSamples << " bf=" << statBufferFilled + << " bw=" << statBufferWritten << " wf=" << statWriteFailed + << " wt=" << statWriteTime << " mwt=" << statMaxWriteTime; + } + + long long timestamp; ///< Timestamp + int statTooLargeSamples = 0; ///< Number of dropped samples because too large + int statDroppedSamples = 0; ///< Number of dropped samples due to fifo full + int statQueuedSamples = 0; ///< Number of samples written to buffer + int statBufferFilled = 0; ///< Number of buffers filled + int statBufferWritten = 0; ///< Number of buffers written to disk + int statWriteFailed = 0; ///< Number of fwrite() that failed + int statWriteTime = 0; ///< Time to perform an fwrite() of a buffer + int statMaxWriteTime = 0; ///< Max time to perform an fwrite() of a buffer +}; diff --git a/miosix/_examples/datalogger/Logger.cpp b/miosix/_examples/datalogger/Logger.cpp new file mode 100644 index 00000000..a8036bb0 --- /dev/null +++ b/miosix/_examples/datalogger/Logger.cpp @@ -0,0 +1,327 @@ +/*************************************************************************** + * Copyright (C) 2018 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see <http://www.gnu.org/licenses/> * + ***************************************************************************/ + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <stdexcept> +#include <interfaces/atomic_ops.h> +#include <tscpp/buffer.h> +#include "Logger.h" + +using namespace std; +using namespace miosix; +using namespace tscpp; + +// +// class Logger +// + +Logger& Logger::instance() +{ + static Logger logger; + return logger; +} + +void Logger::start() +{ + if (started) + return; + + char filename[32]; + for (unsigned int i = 0; i < filenameMaxRetry; i++) + { + sprintf(filename, "/sd/%02d.dat", i); + struct stat st; + if (stat(filename, &st) != 0) + break; + // File exists + if (i == filenameMaxRetry - 1) + puts("Too many files, appending to last"); + } + + file = fopen(filename, "ab"); + if (file == NULL) + throw runtime_error("Error opening log file"); + setbuf(file, NULL); + + // The boring part, start threads one by one and if they fail, undo + // Perhaps excessive defensive programming as thread creation failure is + // highly unlikely (only if ram is full) + + packT = Thread::create(packThreadLauncher, 1536, 1, this, Thread::JOINABLE); + if (!packT) + { + fclose(file); + throw runtime_error("Error creating pack thread"); + } + + writeT = + Thread::create(writeThreadLauncher, 2048, 1, this, Thread::JOINABLE); + if (!writeT) + { + fullQueue.put(nullptr); // Signal packThread to stop + packT->join(); + // packThread has pushed a buffer and a nullptr to writeThread, remove + // it + while (fullList.front() != nullptr) + { + emptyList.push(fullList.front()); + fullList.pop(); + } + fullList.pop(); // Remove nullptr + fclose(file); + throw runtime_error("Error creating write thread"); + } + if(logStatsEnabled) + { + statsT = + Thread::create(statsThreadLauncher, 1536, 1, this, Thread::JOINABLE); + if (!statsT) + { + fullQueue.put(nullptr); // Signal packThread to stop + packT->join(); + writeT->join(); + fclose(file); + throw runtime_error("Error creating stats thread"); + } + } + started = true; +} + +void Logger::stop() +{ + if(started == false) return; + if(logStatsEnabled) logStats(); + started = false; + fullQueue.put(nullptr); // Signal packThread to stop + packT->join(); + writeT->join(); + if(logStatsEnabled) statsT->join(); + fclose(file); +} + +Logger::Logger() +{ + // Allocate buffers and put them in the empty list + for (unsigned int i = 0; i < numBuffers; i++) + emptyList.push(new Buffer); + for (unsigned int i = 0; i < numRecords; i++) + emptyQueue.put(new Record); +} + +void Logger::packThreadLauncher(void* argv) +{ + reinterpret_cast<Logger*>(argv)->packThread(); +} + +void Logger::writeThreadLauncher(void* argv) +{ + reinterpret_cast<Logger*>(argv)->writeThread(); +} + +void Logger::statsThreadLauncher(void* argv) +{ + reinterpret_cast<Logger*>(argv)->statsThread(); +} + +LogResult Logger::logImpl(const char* name, const void* data, unsigned int size) +{ + if (started == false) + return LogResult::Ignored; + + Record* record = nullptr; + { + FastInterruptDisableLock dLock; + // We disable interrupts because IRQget() is nonblocking, unlike get() + if (emptyQueue.IRQget(record) == false) + { + s.statDroppedSamples++; + return LogResult::Dropped; + } + } + + auto result = serializeImpl(record->data, maxRecordSize, name, data, size); + if (result == BufferTooSmall) + { + emptyQueue.put(record); + atomicAdd(&s.statTooLargeSamples, 1); + return LogResult::TooLarge; + } + + record->size = result; + fullQueue.put(record); + atomicAdd(&s.statQueuedSamples, 1); + return LogResult::Queued; +} + +void Logger::packThread() +{ + /* + * The first implementation of this class had the log() function write + * directly the serialized data to the buffers. So, no Records nor + * packThread existed. However, to be able to call log() concurrently + * without messing up the buffer, a mutex was needed. Thus, if many + * threads call log(), contention on that mutex would occur, serializing + * accesses and slowing down the (potentially real-time) callers. For this + * reason Records and the pack thread were added. + * Now each log() works independently on its own Record, and log() accesses + * can proceed in parallel. + */ + try + { + for (;;) + { + Buffer* buffer = nullptr; + { + Lock<FastMutex> l(mutex); + // Get an empty buffer, wait if none is available + while (emptyList.empty()) + cond.wait(l); + buffer = emptyList.front(); + emptyList.pop(); + buffer->size = 0; + } + + do + { + Record* record = nullptr; + fullQueue.get(record); + + // When stop() is called, it pushes a nullptr signaling to stop + if (record == nullptr) + { + Lock<FastMutex> l(mutex); + fullList.push(buffer); // Don't lose the buffer + fullList.push(nullptr); // Signal writeThread to stop + cond.broadcast(); + s.statBufferFilled++; + return; + } + + memcpy(buffer->data + buffer->size, record->data, record->size); + buffer->size += record->size; + emptyQueue.put(record); + } while (bufferSize - buffer->size >= maxRecordSize); + + { + Lock<FastMutex> l(mutex); + // Put back full buffer + fullList.push(buffer); + cond.broadcast(); + s.statBufferFilled++; + } + } + } + catch (exception& e) + { + printf("Error: packThread failed due to an exception: %s\n", e.what()); + } +} + +void Logger::writeThread() +{ + try + { + for (;;) + { + Buffer* buffer = nullptr; + { + Lock<FastMutex> l(mutex); + // Get a full buffer, wait if none is available + while (fullList.empty()) + cond.wait(l); + buffer = fullList.front(); + fullList.pop(); + } + + // When packThread stops, it pushes a nullptr signaling to stop + if (buffer == nullptr) + return; + + // Write data to disk + Timer timer; + timer.start(); +// ledOn(); + + size_t result = fwrite(buffer->data, 1, buffer->size, file); + if (result != buffer->size) + { + // If this fails and your board uses SDRAM, + // define and increase OVERRIDE_SD_CLOCK_DIVIDER_MAX + // perror("fwrite"); + s.statWriteFailed++; + } + else + s.statBufferWritten++; + +// ledOff(); + timer.stop(); + s.statWriteTime = timer.interval(); + s.statMaxWriteTime = max(s.statMaxWriteTime, s.statWriteTime); + + { + Lock<FastMutex> l(mutex); + // Put back empty buffer + emptyList.push(buffer); + cond.broadcast(); + } + } + } + catch (exception& e) + { + printf("Error: writeThread failed due to an exception: %s\n", e.what()); + } +} + +/** + * This thread prints stats + */ +void Logger::statsThread() +{ + if(logStatsEnabled) + { + try + { + for (;;) + { + Thread::sleep(1000); + if (started == false) + return; + logStats(); +// printf("ls:%d ds:%d qs:%d bf:%d bw:%d wf:%d wt:%d mwt:%d\n", +// s.statTooLargeSamples, s.statDroppedSamples, +// s.statQueuedSamples, s.statBufferFilled, s.statBufferWritten, +// s.statWriteFailed, s.statWriteTime, s.statMaxWriteTime); + } + } + catch (exception& e) + { + printf("Error: statsThread failed due to an exception: %s\n", e.what()); + } + } +} diff --git a/miosix/_examples/datalogger/Logger.h b/miosix/_examples/datalogger/Logger.h new file mode 100644 index 00000000..af882136 --- /dev/null +++ b/miosix/_examples/datalogger/Logger.h @@ -0,0 +1,208 @@ +/*************************************************************************** + * Copyright (C) 2018 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see <http://www.gnu.org/licenses/> * + ***************************************************************************/ + +#pragma once + +#include <miosix.h> +#include <cstdio> +#include <list> +#include <queue> +#include <type_traits> +#include "LogStats.h" + +/** + * Possible outcomes of Logger::log() + */ +enum class LogResult +{ + Queued, ///< Data has been accepted by the logger and will be written + Dropped, ///< Buffers are currently full, data will not be written. Sorry + Ignored, ///< Logger is currently stopped, data will not be written + TooLarge ///< Data is too large to be logged. Increase maxRecordSize +}; + +/** + * Buffered logger. Needs to be started before it can be used. + */ +class Logger +{ +public: + /** + * \return an instance to the logger + */ + static Logger &instance(); + + /** + * Blocking call. May take a long time. + * + * Call this function to start the logger. + * When this function returns, the logger is started, and subsequent calls + * to log will actually log the data. + * + * Do not call concurrently from multiple threads. + * + * \throws runtime_error if the log could not be opened + */ + void start(); + + /** + * Blocking call. May take a very long time (seconds). + * + * Call this function to stop the logger. + * When this function returns, all log buffers have been flushed to disk, + * and it is safe to power down the board without losing log data or + * corrupting the filesystem. + * + * Do not call concurrently from multiple threads. + */ + void stop(); + + /** + * Nonblocking call. Safe to be called concurrently from multiple threads. + * + * \return true if the logger is started and ready to accept data. + */ + bool isStarted() const { return started; } + + /** + * Nonblocking call. Safe to be called concurrently from multiple threads. + * + * Call this function to log a class. + * \param t the class to be logged. This class has the following + * requirements: + * - it must be trivially_copyable, so no pointers or references inside + * the class, no stl containers, no virtual functions, no base classes. + * - it must have a "void print(std::ostream& os) const" member function + * that prints all its data fields in text form (this is not used by the + * logger, but by the log decoder program) + * \return whether the class has been logged + */ + template<typename T> + LogResult log(const T& t) + { + //static_assert(std::is_trivially_copyable<T>::value,""); + return logImpl(typeid(t).name(),&t,sizeof(t)); + } + + /** + * \return logger stats. + * Only safe to be called when the logger is stopped. + */ + LogStats getStats() const { return s; } + +private: + Logger(); + Logger(const Logger &) = delete; + Logger &operator=(const Logger &) = delete; + + static void packThreadLauncher(void *argv); + static void writeThreadLauncher(void *argv); + static void statsThreadLauncher(void *argv); + + /** + * Non-template dependente part of log + * \param name class anem + * \param data pointer to class data + * \param size class size + */ + LogResult logImpl(const char *name, const void *data, unsigned int size); + + /** + * This thread packs logged data into buffers + */ + void packThread(); + + /** + * This thread writes packed buffers to disk + */ + void writeThread(); + + /** + * This thread prints stats + */ + void statsThread(); + + /** + * Log logger stats using the logger itself + */ + void logStats() + { + s.setTimestamp(miosix::getTick()); + log(s); + } + + static const unsigned int filenameMaxRetry = 100; ///< Limit on new filename + static const unsigned int maxRecordSize = 128; ///< Limit on logged data + static const unsigned int numRecords = 128; ///< Size of record queues + static const unsigned int bufferSize = 4096;///< Size of each buffer + static const unsigned int numBuffers = 4; ///< Number of buffers + static constexpr bool logStatsEnabled = true;///< Log logger stats? + + /** + * A record is a single serialized logged class. Records are used to + * make log() lock-free. Since each call to log() works on its independent + * Record, calls to log do not need a global mutex which could block + * threads calling log() concurrently + */ + class Record + { + public: + Record() : size(0) {} + char data[maxRecordSize]; + unsigned int size; + }; + + /** + * A buffer is what is written on disk. It is filled by packing records. + * The reason why we don't write records directly is that they are too + * small to efficiently use disk bandwidth. SD cards are much faster when + * data is written in large chunks. + */ + class Buffer + { + public: + Buffer() : size(0) {} + char data[bufferSize]; + unsigned int size; + }; + + miosix::Queue<Record *, numRecords> fullQueue; ///< Full records + miosix::Queue<Record *, numRecords> emptyQueue; ///< Empty Records + std::queue<Buffer *, std::list<Buffer *>> fullList; ///< Full buffers + std::queue<Buffer *, std::list<Buffer *>> emptyList; ///< Empty buffers + miosix::FastMutex mutex; ///< To allow concurrent access to the queues + miosix::ConditionVariable cond; ///< To lock when buffers are all empty + + miosix::Thread *packT; ///< Thread packing logged data + miosix::Thread *writeT; ///< Thread writing data to disk + miosix::Thread *statsT; ///< Thred printing stats + + volatile bool started = false; ///< Logger is started and accepting data + + FILE *file; ///< Log file + LogStats s; ///< Logger stats +}; diff --git a/miosix/_examples/datalogger/Makefile b/miosix/_examples/datalogger/Makefile new file mode 100644 index 00000000..1e70f8f9 --- /dev/null +++ b/miosix/_examples/datalogger/Makefile @@ -0,0 +1,108 @@ +## +## Makefile for Miosix embedded OS +## +MAKEFILE_VERSION := 1.07 +## Path to kernel directory (edited by init_project_out_of_git_repo.pl) +KPATH := miosix +## Path to config directory (edited by init_project_out_of_git_repo.pl) +CONFPATH := $(KPATH) +include $(CONFPATH)/config/Makefile.inc + +## +## List here subdirectories which contains makefiles +## +SUBDIRS := $(KPATH) + +## +## List here your source files (both .s, .c and .cpp) +## +SRC := \ +main.cpp Logger.cpp tscpp/buffer.cpp + +## +## List here additional static libraries with relative path +## +LIBS := + +## +## List here additional include directories (in the form -Iinclude_dir) +## +INCLUDE_DIRS := + +############################################################################## +## You should not need to modify anything below ## +############################################################################## + +ifeq ("$(VERBOSE)","1") +Q := +ECHO := @true +else +Q := @ +ECHO := @echo +endif + +## Replaces both "foo.cpp"-->"foo.o" and "foo.c"-->"foo.o" +OBJ := $(addsuffix .o, $(basename $(SRC))) + +## Includes the miosix base directory for C/C++ +## Always include CONFPATH first, as it overrides the config file location +CXXFLAGS := $(CXXFLAGS_BASE) -I$(CONFPATH) -I$(CONFPATH)/config/$(BOARD_INC) \ + -I. -I$(KPATH) -I$(KPATH)/arch/common -I$(KPATH)/$(ARCH_INC) \ + -I$(KPATH)/$(BOARD_INC) $(INCLUDE_DIRS) +CFLAGS := $(CFLAGS_BASE) -I$(CONFPATH) -I$(CONFPATH)/config/$(BOARD_INC) \ + -I. -I$(KPATH) -I$(KPATH)/arch/common -I$(KPATH)/$(ARCH_INC) \ + -I$(KPATH)/$(BOARD_INC) $(INCLUDE_DIRS) +AFLAGS := $(AFLAGS_BASE) +LFLAGS := $(LFLAGS_BASE) +DFLAGS := -MMD -MP + +LINK_LIBS := $(LIBS) -L$(KPATH) -Wl,--start-group -lmiosix -lstdc++ -lc \ + -lm -lgcc -Wl,--end-group + +all: all-recursive main + +clean: clean-recursive clean-topdir + +program: + $(PROGRAM_CMDLINE) + +all-recursive: + $(foreach i,$(SUBDIRS),$(MAKE) -C $(i) \ + KPATH=$(shell perl $(KPATH)/_tools/relpath.pl $(i) $(KPATH)) \ + CONFPATH=$(shell perl $(KPATH)/_tools/relpath.pl $(i) $(CONFPATH)) \ + || exit 1;) + +clean-recursive: + $(foreach i,$(SUBDIRS),$(MAKE) -C $(i) \ + KPATH=$(shell perl $(KPATH)/_tools/relpath.pl $(i) $(KPATH)) \ + CONFPATH=$(shell perl $(KPATH)/_tools/relpath.pl $(i) $(CONFPATH)) \ + clean || exit 1;) + +clean-topdir: + -rm -f $(OBJ) main.elf main.hex main.bin main.map $(OBJ:.o=.d) + +main: main.elf + $(ECHO) "[CP ] main.hex" + $(Q)$(CP) -O ihex main.elf main.hex + $(ECHO) "[CP ] main.bin" + $(Q)$(CP) -O binary main.elf main.bin + $(Q)$(SZ) main.elf + +main.elf: $(OBJ) all-recursive + $(ECHO) "[LD ] main.elf" + $(Q)$(CXX) $(LFLAGS) -o main.elf $(OBJ) $(KPATH)/$(BOOT_FILE) $(LINK_LIBS) + +%.o: %.s + $(ECHO) "[AS ] $<" + $(Q)$(AS) $(AFLAGS) $< -o $@ + +%.o : %.c + $(ECHO) "[CC ] $<" + $(Q)$(CC) $(DFLAGS) $(CFLAGS) $< -o $@ + +%.o : %.cpp + $(ECHO) "[CXX ] $<" + $(Q)$(CXX) $(DFLAGS) $(CXXFLAGS) $< -o $@ + +#pull in dependecy info for existing .o files +-include $(OBJ:.o=.d) diff --git a/miosix/_examples/datalogger/Readme.txt b/miosix/_examples/datalogger/Readme.txt new file mode 100644 index 00000000..29c092e3 --- /dev/null +++ b/miosix/_examples/datalogger/Readme.txt @@ -0,0 +1,30 @@ +High performance logging for Miosix + +Some applications - possibly real-time - require to log data, and do so from +multiple threads. While a simple library that opens a file and writes to is +sufficient for simple applications, there are some problems: +- the Miosix filesystem uses typically an SD card as storage, and SD and other + FLASH based storage devices may pause to do wear leveling. SD cards, even good + quality class 10 ones, may pause for up to 1 second. +- the Miosix OS, contrary to Linux, does not do much buffering at the filesystem + layer. This is desirable, since when an OS runs on as little as a few tens of + KB of RAM, the last thing you want is an OS that uses an unquatifiable amount + of memory. + +This example code shows a high-performance logging class that +- has a nonblocking log() member function, which can be called concurrently from + multiple threads, to log a user-defined class or struct. + Tscpp (https://github.com/fedetft/tscpp) is used to serialize data. + Being nonblocking, it can be called also in real-time threads of your codebase + with confidence. +- buffers data to compensate for the delays of the storage medium + +To configure the logger for your application, to trade off buffer space vs write +data rate, uoy can edit Logger.h + + static const unsigned int filenameMaxRetry = 100; ///< Limit on new filename + static const unsigned int maxRecordSize = 128; ///< Limit on logged data + static const unsigned int numRecords = 128; ///< Size of record queues + static const unsigned int bufferSize = 4096;///< Size of each buffer + static const unsigned int numBuffers = 4; ///< Number of buffers + static constexpr bool logStatsEnabled = true;///< Log logger stats? diff --git a/miosix/_examples/datalogger/logdecoder/Makefile b/miosix/_examples/datalogger/logdecoder/Makefile new file mode 100644 index 00000000..88443dde --- /dev/null +++ b/miosix/_examples/datalogger/logdecoder/Makefile @@ -0,0 +1,6 @@ + +all: + g++ -std=c++11 -O2 -o logdecoder logdecoder.cpp ../tscpp/stream.cpp -I .. + +clean: + rm logdecoder diff --git a/miosix/_examples/datalogger/logdecoder/logdecoder.cpp b/miosix/_examples/datalogger/logdecoder/logdecoder.cpp new file mode 100644 index 00000000..412465bb --- /dev/null +++ b/miosix/_examples/datalogger/logdecoder/logdecoder.cpp @@ -0,0 +1,59 @@ +/*************************************************************************** + * Copyright (C) 2018 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see <http://www.gnu.org/licenses/> * + ***************************************************************************/ + +/* + * This is a stub program for the program that will decode the logged data. + * Fill in the TODO to make it work. + */ + +#include <iostream> +#include <fstream> +#include <stdexcept> +#include <tscpp/stream.h> + +//TODO: add here include files of serialized classes +#include "../LogStats.h" +#include "../ExampleData.h" + +using namespace std; +using namespace tscpp; + +int main(int argc, char *argv[]) +try { + TypePoolStream tp; + //TODO: Register the serialized classes + tp.registerType<LogStats>([](LogStats& t){ t.print(cout); cout<<'\n'; }); + tp.registerType<ExampleData>([](ExampleData& t){ t.print(cout); cout<<'\n'; }); + + if(argc!=2) return 1; + ifstream in(argv[1]); + in.exceptions(ios::eofbit); + UnknownInputArchive ia(in,tp); + for(;;) ia.unserialize(); +} catch(exception&) { + return 0; +} diff --git a/miosix/_examples/datalogger/main.cpp b/miosix/_examples/datalogger/main.cpp new file mode 100644 index 00000000..b44d9c7c --- /dev/null +++ b/miosix/_examples/datalogger/main.cpp @@ -0,0 +1,72 @@ + +#include <cstdio> +#include <miosix.h> +#include "Logger.h" +#include "ExampleData.h" + +using namespace std; +using namespace miosix; + +volatile bool stop=false; + +void loggerDemo(void*) +{ + /* + * Logger is configured as: + * maxRecordSize = 128 + * numRecords = 128 + * bufferSize = 4096 + * numBuffers = 4 + * + * Serialized ExampleData is 30 bytes + * There are 128 entries in the record queue, and (4-1)=3 4096 buffers for + * buffering (the fourth buffer is the one being written). + * Thus, the buffering system can hold 4096*3/30+128=537 ExampleData before + * filling. Considering the rule of thumb that a high quality SD card may + * block for up to 1s, the maximum data rate is 537Hz. + * + * An estimate of the memory occupied by the logger is: + * buffers 4*(4096+4)+128*(128+4)=33296 + * thread stacks 1536+1536+2048=5120 + * so a total of 38KB. The actual memory occupied will be a bit larger + * due to unaccounted variables and overheads. + * + * Note: although this demo is simple, the logger allows to: + * - log data from multiple threads while being nonblocking + * - log different classes/structs in any order, provided their serialized + * size is less than maxRecordSize and that they meet the requirements + * to be serialized with tscpp (github.com/fedetft/tscpp) + */ + auto& logger=Logger::instance(); + logger.start(); + + int a=0,b=0; + auto period=2; //2ms, 500Hz + for(auto t=getTick();;) + { + if(stop) break; + auto tNew=t+period; + Thread::sleepUntil(tNew); + t=getTick(); + if(t>tNew) b++; //Deadline miss + ExampleData ed(a++,b,t); + logger.log(ed); + } + + logger.stop(); + + iprintf("Lost %d samples, missed %d deadlines\n", + logger.getStats().statDroppedSamples,b); +} + +int main() +{ + puts("Type enter to start logger demo"); + getchar(); + auto t=Thread::create(loggerDemo,2048,PRIORITY_MAX-1,nullptr,Thread::JOINABLE); + puts("Type enter to stop logger demo"); + getchar(); + stop=true; + t->join(); + puts("Bye"); +} diff --git a/miosix/_examples/datalogger/tscpp b/miosix/_examples/datalogger/tscpp new file mode 160000 index 00000000..4a5f5407 --- /dev/null +++ b/miosix/_examples/datalogger/tscpp @@ -0,0 +1 @@ +Subproject commit 4a5f54076ea924dacb5414c0f6183a669efc1eff -- GitLab