I'm working on a logging utility for a real-time application. The user of this class should be able to specify addresses and names of parameters to save during initialization, then those values will be recorded in a circular buffer during runtime, and saved to disk when a specific trigger occurs.
The output to disk should be a 2D matrix with all data converted to human-readable ascii (and separated by tabs). The horizontal axis contains the different parameters to save. The vertical axis contains time. My issue is how to define something like this with columns.
Desired interface:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
class DyingSeconds
{
public:
// logname = name of the file where the log will be saved
// maxlines = how many rows of data to store before the buffer starts over.
DyingSeconds(std::string logname, int maxlines);
// Registers a column during initialization phase
// name = what will appear in the header. Do not use tabs
// valueAddr address of the value to be recorded
template<typename T> void Register(std::string name, T* valueAddr);
// Records all registered values and stores it in a circular buffer.
void Record();
// When a trigger is encountered, this saves the data to disk
void Save();
};
|
Here's the thing:
1) I don't want to allocate any dynamic memory during the Record() function. That's called during real-time and dynamic memory allocation is forbidden during real-time operation. That includes allocating memory inside of the STL.
2) Each column could be a different primitive type. So a simple 2D array doesn't help.
3) The class needs to be designed so that the Record() function has a minimal execution time.
What I've tried is to store this data as a vector of strings. Each string represents a line in the log (all data in ascii format for a specific time).
My problems with this method are:
1) Convrting data to characters places a computation in Record() that I'd rather do during the Trigger()
2) The method I use below constantly creates and destroys strings. Strings use dynamic memory allocation and I'm trying to avoid that.
Do you have ideas on how to store a 2D table where the type of each column can vary?
Simplified code below describing the implementation described above.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
|
class DyingValueContainer
{
public:
virtual std::string ToString() = 0;
};
template <typename T>
class DyingValue : public DyingValueContainer
{
private:
T* m_value;
public:
DyingValue(T* valueAddr) :
m_value(valueAddr) { }
std::string ToString() const
{
std::stringstream iss;
iss << &m_value;
return iss.str();
}
};
class DyingSeconds
{
public:
DyingSeconds(std::string logname, int maxlines) : m_logname(logname), m_maxlines(maxlines), m_lastline( -1 )
{
m_table.insert(m_table.begin(), m_maxlines, std::string() );
}
~DyingSeconds()
{
for (std::vector<DyingValueContainer*>::iterator it = m_header.begin(); it != m_header.end(); ++it)
delete *it;
}
// Register will define the columns of our log file. Name will appear at the header, and the values will be in each time-step
template <typename T>
void Register(std::string name, T* valueAddr)
{
m_header.push_back( new DyingValue(valueAddr) );
}
// To be called at regular intervals by the scheduler during realtime loop
// Reads the value of each registered variable, and stores them
void Record()
{
if (++m_lastline >= m_maxlines) m_lastline = 0;
m_table[m_lastline].clear();
for (std::vector<DyingValueContainer*>::iterator it = m_header.begin(); it != m_header.end(); ++it)
m_table[m_lastline] += (*it)->ToString() + '\t';
}
// When called, it will dump the stored memory
void Save()
{
}
private:
const std::string m_logname
const int m_maxlines;
int m_lastline;
std::vector<DyingValueContainer*> m_header;
std::vector<std::string> m_table;
};
|