Files

268 lines
10 KiB
C
Raw Permalink Normal View History

#ifndef TIME_TRACKER_H
#define TIME_TRACKER_H
#include "exec_dir_helper.h"
#include <string>
#include <sstream>
#include <vector>
#include <chrono>
#include <list>
#include <map>
#include <queue>
#include <fstream>
/**
* @brief Track function to trigger one time entry.
* @param tag The tag name to use to identify this entry.
* @details Use the "track_time" macro within a function to activate. The tag is a name used to identify the code position that
* should be tracked. For each track_time call, a time entry relative to the start of the application will be written (resolution
* is micro-seconds). The tracking file will be written on shutdown of the application and is called the same as the application
* with "_tracking.csv" attached to it.
* Example:
* @code
* void MyFunc()
* {
* track_time(enter);
*
* // ...
*
* track_timer(leave);
* }
* @endcode
*/
#define track_time(tag) GetTimeTracker().Trigger(__FUNCTION__, #tag)
/**
* @brief Time tracker class used internally by the tracker.
*/
class CTimeTracker
{
public:
/**
* @brief Default constructor.
*/
CTimeTracker();
/**
* @brief Destructor.
*/
~CTimeTracker();
/**
* @brief Trigger function to store a trigger timepoint.
* @attention Both function and tag name must be a static string in a data segment, so the strings are valid even if the
* function that caused the trigger is not within scope any more.
* @param[in] szFunction Pointer to the function name.
* @param[in] szTag Tag to identify the trigger with.
*/
void Trigger(const char* szFunction, const char* szTag);
private:
/**
* @brief Trigger value structure
*/
struct STriggerEntry
{
/**
* @brief Constructor of a trigger value.
* @param[in] szFuncParam String to function name
* @param[in] szTagParam String to tag name
*/
STriggerEntry(const char* szFuncParam, const char* szTagParam) :
szFunction(szFuncParam), szTag(szTagParam), tpTrigger(std::chrono::high_resolution_clock::now())
{}
const char* szFunction; ///< Function name (is a static string in data segment).
const char* szTag; ///< Function tag (is a static string in data segment).
std::chrono::high_resolution_clock::time_point tpTrigger; ///< Trigger timepoint.
};
std::time_t m_timeStartTime; ///< Start time of the tracker (system clock time).
std::chrono::high_resolution_clock::time_point m_tpStartTime; ///< Start time of tracker (high resolution clock).
mutable std::mutex m_mtxTriggers; ///< Queue protection.
std::queue<STriggerEntry> m_queuTriggers; ///< Queue with trigger values.
};
inline CTimeTracker& GetTimeTracker()
{
static CTimeTracker tracker_global;
return tracker_global;
}
inline CTimeTracker::CTimeTracker() :
m_timeStartTime(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())),
m_tpStartTime(std::chrono::high_resolution_clock::now())
{}
inline CTimeTracker::~CTimeTracker()
{
if (m_queuTriggers.empty()) return;
// Trigger list translated to the system time in microsecs.
using TTriggerList = std::list<uint64_t>;
struct STagInfo
{
STagInfo(const char* szFunctionParam, const char* szTagParam) :
szFunction(szFunctionParam), szTag(szTagParam)
{}
const char* szFunction;
const char* szTag;
TTriggerList lstTriggers;
TTriggerList::iterator itPosition;
};
using TOrderedTagList = std::list<STagInfo>;
TOrderedTagList lstOrderedTags;
TOrderedTagList lstDeferredTags;
// Process the trigger queue
while (!m_queuTriggers.empty())
{
// Get the entry
STriggerEntry sEntry = std::move(m_queuTriggers.front());
m_queuTriggers.pop();
// Calculate the time since start time (use system clock as reference)
uint64_t uiTimeMicrosecs = std::chrono::duration_cast<std::chrono::microseconds>(sEntry.tpTrigger - m_tpStartTime).count();
//uiTimeMicrosecs += static_cast<uint64_t>(m_timeStartTime) * 1000000ull;
// Determine the order
STagInfo* psTagInfo = nullptr;
if (lstOrderedTags.empty())
{
// First entry... add to the list
lstOrderedTags.push_back(STagInfo(sEntry.szFunction, sEntry.szTag));
psTagInfo = &lstOrderedTags.back();
}
else
{
// Check whether the tag represents the first entry of the order list. If so, add all the deferred list entries.
if (sEntry.szTag == lstOrderedTags.front().szTag && !lstDeferredTags.empty())
{
// Add the deferred tags to the end of the list
for (STagInfo& rsTag : lstDeferredTags)
lstOrderedTags.push_back(std::move(rsTag));
lstDeferredTags.clear();
}
// Search for the tag to exist in the ordered tags list.
auto itTag = std::find_if(lstOrderedTags.begin(), lstOrderedTags.end(), [&](const STagInfo& rsTag) { return rsTag.szFunction == sEntry.szFunction && rsTag.szTag == sEntry.szTag; });
if (itTag == lstOrderedTags.end())
{
// Tag is not in the ordered tags list; add to deferred tags list of not already in there
itTag = std::find_if(lstDeferredTags.begin(), lstDeferredTags.end(), [&](const STagInfo& rsTag) { return rsTag.szFunction == sEntry.szFunction && rsTag.szTag == sEntry.szTag; });
if (itTag == lstDeferredTags.end())
{
lstDeferredTags.push_back(STagInfo(sEntry.szFunction, sEntry.szTag));
psTagInfo = &lstDeferredTags.back();
}
else
psTagInfo = &(*itTag);
}
else
{
// Tag is in the ordered tags list. Are there any tags on the deferred list? Add them before.
for (STagInfo& rsTag : lstDeferredTags)
lstOrderedTags.insert(itTag, std::move(rsTag));
lstDeferredTags.clear();
psTagInfo = &(*itTag);
}
}
// Add the trigger value
if (psTagInfo)
psTagInfo->lstTriggers.push_back(uiTimeMicrosecs);
else
std::cout << "INTERNAL ERROR: tag info pointer is invalid!";
}
// Run through deferred tags lists to add them still at the end of the ordered tags lists
for (STagInfo& rsTag : lstDeferredTags)
lstOrderedTags.push_back(std::move(rsTag));
// Create CSV file
std::filesystem::path path = (GetExecDirectory() / GetExecFilename().replace_extension("")).generic_u8string() + "_tracker.csv";
std::ofstream stream(path);
if (!stream.is_open())
{
std::cout << "Cannot write tracker information to '" << path.generic_u8string() << "'..." << std::endl;
return;
}
std::cout << "Writing tracker data to '" << path.generic_u8string() << "'..." << std::endl;
// Iterate through the tags and add the header lines + assign the initial iterator position
std::stringstream sstreamFirstLine;
std::stringstream sstreamSecondLine;
std::stringstream sstreamData;
for (STagInfo& rsTagInfo : lstOrderedTags)
{
// First line is the function name
if (!sstreamFirstLine.str().empty())
sstreamFirstLine << "; ";
sstreamFirstLine << rsTagInfo.szFunction;
// Second line is the tag
if (!sstreamSecondLine.str().empty())
sstreamSecondLine << "; ";
sstreamSecondLine << rsTagInfo.szTag;
// Assign iterator
rsTagInfo.itPosition = rsTagInfo.lstTriggers.begin();
}
stream << sstreamFirstLine.str() << std::endl <<
sstreamSecondLine.str() << std::endl;
// Add the data by running though the position iterators so long until all iterators are pointing to end...
uint64_t uiMax = 0;
do
{
// Determine the maximum time that includes at the most one value of each tag - check the time of the trigger value
// following the current.
uiMax = static_cast<uint64_t>(-1);
for (const STagInfo& rsTagInfo : lstOrderedTags)
{
if (rsTagInfo.itPosition != rsTagInfo.lstTriggers.end())
{
// Get the current timestamp to determine the minimal amount to include this timestamp
uint64_t uiPotentialMax = *rsTagInfo.itPosition + 1;
auto itNextPosition = rsTagInfo.itPosition;
itNextPosition++;
// If the next timestamp is larger than the current timestamp, take the next timestamp
if (itNextPosition != rsTagInfo.lstTriggers.end() && uiPotentialMax < *itNextPosition)
uiPotentialMax = *itNextPosition;
// If the max timestamp should not exceed the next coming timestamp.
if (uiMax > uiPotentialMax)
uiMax = uiPotentialMax;
}
}
// Run through the values and stream those values that are smaller than the next position.
bool bFirst = true;
for (STagInfo& rsTagInfo : lstOrderedTags)
{
if (!bFirst)
stream << "; ";
bFirst = false;
if (rsTagInfo.itPosition != rsTagInfo.lstTriggers.end() && *rsTagInfo.itPosition < uiMax)
{
stream << *rsTagInfo.itPosition;
rsTagInfo.itPosition++;
}
}
stream << std::endl;
} while (uiMax != static_cast<uint64_t>(-1));
// Finalize the stream
stream.close();
}
inline void CTimeTracker::Trigger(const char* szFunction, const char* szTag)
{
std::unique_lock<std::mutex> lock(m_mtxTriggers);
m_queuTriggers.push(STriggerEntry(szFunction, szTag));
}
#endif // !defined TIME_TRACKER_H