Skip to content
Snippets Groups Projects
Commit 01e522ef authored by Federico's avatar Federico
Browse files

Added logger example

parent 6a5d4d4f
Branches
Tags
No related merge requests found
[submodule "miosix/_examples/datalogger/tscpp"]
path = miosix/_examples/datalogger/tscpp
url = https://github.com/fedetft/tscpp.git
/***************************************************************************
* 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;
};
/***************************************************************************
* 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
};
/***************************************************************************
* 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());
}
}
}
/***************************************************************************
* 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
};
##
## 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)
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?
all:
g++ -std=c++11 -O2 -o logdecoder logdecoder.cpp ../tscpp/stream.cpp -I ..
clean:
rm logdecoder
/***************************************************************************
* 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;
}
#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");
}
Subproject commit 4a5f54076ea924dacb5414c0f6183a669efc1eff
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment