DynExp
Highly flexible laboratory automation for dynamically changing experiments.
Loading...
Searching...
No Matches
Util.cpp
Go to the documentation of this file.
1// This file is part of DynExp.
2
3#include "stdafx.h"
4#include "Util.h"
5
6namespace Util
7{
8 std::unique_lock<ILockable::MutexType> ILockable::AcquireLock(const std::chrono::milliseconds Timeout) const
9 {
10 using namespace std::chrono_literals;
11
12 // In order to compensate for spurious failures by retrying
13 // (see https://en.cppreference.com/w/cpp/thread/timed_mutex/try_lock_for)
14 constexpr int NumTries = 2;
15
16 if (Timeout == 0ms)
17 return std::unique_lock<MutexType>(LockMutex);
18 else
19 {
20 std::unique_lock<MutexType> lock(LockMutex, std::defer_lock);
21
22 for (auto i = NumTries; i > 0; --i)
23 if (lock.try_lock_for(Timeout / NumTries))
24 return lock;
25
26 throw TimeoutException("Timeout occurred while trying to lock a mutex.");
27 }
28 }
29
31 {
32 Notify();
33
34 // See OneToOneNotifier::Wait().
35 while (!MutexCanBeDestroyed)
36 std::this_thread::yield();
37 }
38
39 bool OneToOneNotifier::Wait(const std::chrono::milliseconds Timeout)
40 {
41 bool TimeoutOccurred = false;
42
43 {
44 std::unique_lock<decltype(Mutex)> Lock(Mutex);
45
48 "There is already a waiting thread. Only one thread can await this notifier.");
49 SomeoneIsWaiting = true;
50 MutexCanBeDestroyed = false;
51
52 if (Timeout.count())
53 TimeoutOccurred = !ConditionVariable.wait_for(Lock, Timeout, [this]() { return EventOccurred; });
54 else
55 ConditionVariable.wait(Lock, [this]() { return EventOccurred; });
56
57 // Reset the notifier
58 EventOccurred = false;
59 SomeoneIsWaiting = false;
60 }
61
62 // Now, OneToOneNotifier's destructor (if it was called while waiting) is allowed to end which
63 // leads to Mutex being destroyed. There is no unique_lock locking Mutex anymore at this point.
65
66 return TimeoutOccurred;
67 }
68
70 {
71 {
72 std::unique_lock<decltype(Mutex)> Lock(Mutex);
73 EventOccurred = true;
74 }
75
76 ConditionVariable.notify_one();
77 }
78
80 {
81 std::unique_lock<decltype(Mutex)> Lock(Mutex);
82 EventOccurred = false;
83 }
84
86 : DataSize(Other.DataSize)
87 {
88 if (Other.DataPtr)
89 {
90 DataPtr = std::make_unique<DataType>(DataSize);
91 std::memcpy(DataPtr.get(), Other.DataPtr.get(), DataSize);
92 }
93 }
94
96 : DataPtr(std::move(Other.DataPtr)), DataSize(Other.DataSize)
97 {
98 Other.Reset();
99 }
100
102 {
103 Reserve(Other.DataSize);
104 std::memcpy(DataPtr.get(), Other.DataPtr.get(), DataSize);
105
106 return *this;
107 }
108
110 {
111 DataSize = Other.DataSize;
112 DataPtr = std::move(Other.DataPtr);
113 Other.Reset();
114
115 return *this;
116 }
117
118 void BlobDataType::Reserve(size_t Size)
119 {
120 if (DataSize == Size)
121 return;
122
123 if (!Size)
124 DataPtr.reset();
125 else
126 DataPtr = std::make_unique<DataType>(Size);
127
128 DataSize = Size;
129 }
130
131 void BlobDataType::Assign(size_t Size, const DataType Data)
132 {
133 Reserve(Size);
134 std::memcpy(DataPtr.get(), Data, Size);
135 }
136
138 {
139 DataPtr.reset();
140 DataSize = 0;
141 }
142
143 BlobDataType::DataPtrType::element_type* BlobDataType::Release() noexcept
144 {
145 DataSize = 0;
146 return DataPtr.release();
147 }
148
149 std::strong_ordering operator<=>(const VersionType& lhs, const VersionType& rhs)
150 {
151 if (lhs.Major == rhs.Major && lhs.Minor == rhs.Minor && lhs.Patch == rhs.Patch)
152 return std::strong_ordering::equal;
153
154 if (lhs.Major > rhs.Major ||
155 (lhs.Major == rhs.Major && lhs.Minor > rhs.Minor) ||
156 (lhs.Major == rhs.Major && lhs.Minor == rhs.Minor && lhs.Patch > rhs.Patch))
157 return std::strong_ordering::greater;
158
159 return std::strong_ordering::less;
160 }
161
162 VersionType VersionFromString(std::string_view Str)
163 {
164 VersionType Version;
165
166 static const std::regex VersionRegex("(\\d+)\\.(\\d+)(?:\\.(\\d+))?");
167 std::cmatch Results;
168 std::regex_search(Str.data(), Results, VersionRegex);
169 if (Results.length() <= 1 || Results.length() > 5) // Matching a suffix (like "-beta") increases Results' length by one.
170 throw Util::InvalidDataException("Str does not contain a valid version number.");
171
172 // Results[0] contains entire match, so ignore that.
173 for (size_t i = 1; i < Results.size(); ++i)
174 {
175 if (!Results[i].matched)
176 continue;
177
178 switch (i)
179 {
180 case 1: Version.Major = Util::StrToT<decltype(VersionType::Major)>(Results[i].str()); break;
181 case 2: Version.Minor = Util::StrToT<decltype(VersionType::Minor)>(Results[i].str()); break;
182 case 3: Version.Patch = Util::StrToT<decltype(VersionType::Patch)>(Results[i].str());
183 }
184 }
185
186 return Version;
187 }
188
189 std::string ExceptionToStr(const std::exception_ptr ExceptionPtr)
190 {
191 if (!ExceptionPtr)
192 return "";
193
194 try
195 {
196 std::rethrow_exception(ExceptionPtr);
197 }
198 catch (const std::exception& e)
199 {
200 return e.what();
201 }
202 catch (...)
203 {
204 return "Unknown exception.";
205 }
206 }
207
208 std::string ToLower(std::string_view Str)
209 {
210 std::string LowerStr;
211 LowerStr.resize(Str.size());
212
213 std::transform(Str.cbegin(), Str.cend(), LowerStr.begin(),
214 [](unsigned char c) { return std::tolower(c); }
215 );
216
217 return LowerStr;
218 }
219
220 std::vector<std::complex<double>> FFT(const std::vector<std::complex<double>>& Data, bool InverseTransform)
221 {
222 if (Data.size() > std::numeric_limits<size_t>::max() / 2)
223 throw OverflowException("Size of Data vector must not exceed half the capacity of size_t.");
224
225 std::vector<double> RawData;
226 RawData.resize(2 * Data.size());
227 for (size_t i = 0; i < Data.size(); ++i)
228 {
229 RawData[2 * i] = Data[i].real();
230 RawData[2 * i + 1] = Data[i].imag();
231 }
232
233 gsl_fft_complex_workspace* Workspace = gsl_fft_complex_workspace_alloc(Data.size());
234 gsl_fft_complex_wavetable* Wavetable = gsl_fft_complex_wavetable_alloc(Data.size());
235
236 try
237 {
238 if (!Workspace || !Wavetable)
239 throw NotAvailableException("Could not reserve memory for FFT.", ErrorType::Error);
240
241 if (gsl_fft_complex_transform(RawData.data(), 1, Data.size(), Wavetable, Workspace,
242 InverseTransform ? gsl_fft_direction::gsl_fft_backward : gsl_fft_direction::gsl_fft_forward))
243 throw InvalidDataException("The FFT failed for an unknown reason.");
244
245 gsl_fft_complex_wavetable_free(Wavetable);
246 gsl_fft_complex_workspace_free(Workspace);
247
248 std::vector<std::complex<double>> Result;
249 Result.resize(Data.size());
250 for (size_t i = 0; i < Result.size(); ++i)
251 Result[i] = { RawData[2 * i], RawData[2 * i + 1] };
252
253 return Result;
254 }
255 catch (...)
256 {
257 if (Wavetable)
258 gsl_fft_complex_wavetable_free(Wavetable);
259 if (Workspace)
260 gsl_fft_complex_workspace_free(Workspace);
261
262 throw;
263 }
264 }
265
266 Warning::Warning(Warning&& Other) noexcept : Data(std::make_unique<WarningData>())
267 {
268 auto other_lock = Other.AcquireLock();
269
270 Data.swap(Other.Data);
271 }
272
274 {
275 auto lock = AcquireLock();
276
277 Data = std::make_unique<WarningData>();
278 }
279
281 {
282 auto lock = AcquireLock();
283
284 Data = std::make_unique<WarningData>(e.what(), e.ErrorCode, e.Line, e.Function, e.File);
285 return *this;
286 }
287
289 {
290 auto other_lock = Other.AcquireLock();
291 auto lock = AcquireLock();
292
293 Data.swap(Other.Data);
294 return *this;
295 }
296
298 {
299 auto lock = AcquireLock();
300
301 return *Data;
302 }
303
305 {
306 LogFile.exceptions(std::ofstream::failbit | std::ofstream::badbit);
307 }
308
309 void EventLogger::Log(const std::string& Message, const ErrorType Type,
310 const size_t Line, const std::string& Function, const std::string& File, const int ErrorCode
312 , const std::stacktrace& Trace
313#endif // DYNEXP_HAS_STACKTRACE
314 ) noexcept
315 {
316 try
317 {
318 auto Filename = FilenameFromPath(File);
319
320 auto lock = AcquireLock(LogOperationTimeout);
321
322 LogEntry Entry(FormatLog(Message, Line, Function, Filename, ErrorCode, false), Type, std::chrono::system_clock::now());
323 LogEntries.emplace_back(std::move(Entry));
324
325 if (!IsOpenUnsafe())
326 return;
327
328 LogFile << FormatLogHTML(Message, Type, Line, Function, Filename, ErrorCode
330 , Trace
331#endif // DYNEXP_HAS_STACKTRACE
332 );
333
334 LogFile.flush();
335 }
336 catch (...)
337 {
338 // Swallow every exception (refer to function declaration in Util.h)
339 }
340 }
341
342 void EventLogger::Log(const Exception& E) noexcept
343 {
344 Log(E.what(), E.Type, E.Line, E.Function, E.File, E.ErrorCode
346 , E.GetStackTrace()
347#endif // DYNEXP_HAS_STACKTRACE
348 );
349 }
350
351 void EventLogger::Log(const Warning& W) noexcept
352 {
353 try
354 {
355 auto Data = W.Get();
356
357 Log(Data.Description, ErrorType::Warning, Data.Line, Data.Function, Data.File, Data.ErrorCode);
358 }
359 catch (...)
360 {
361 // Swallow every exception (refer to function declaration in Util.h)
362 }
363 }
364
365 std::string EventLogger::FormatLog(const std::string& Message, const size_t Line,
366 const std::string& Function, const std::string& Filename, const int ErrorCode, const bool PrefixMessage)
367 {
368 std::stringstream stream;
369
370 if (!Filename.empty() || !Function.empty())
371 {
372 stream << "(";
373 if (!Filename.empty())
374 {
375 stream << Filename << ((!Line && !Function.empty()) ? " " : "");
376 if (Line)
377 stream << ":" << ToStr(Line) << (!Function.empty() ? " " : "");
378 }
379 if (!Function.empty())
380 stream << "in " << Function << "()";
381 stream << ")" << (PrefixMessage ? "" : ": ");
382 }
383 stream << (PrefixMessage ? ": " : "") << Message;
384 if (ErrorCode)
385 stream << " (Code " << ToStr(ErrorCode) << ")";
386
387 return stream.str();
388 }
389
390 std::string EventLogger::FormatLogHTML(const std::string& Message, const ErrorType Type,
391 const size_t Line, const std::string& Function, const std::string& Filename, const int ErrorCode
393 , const std::stacktrace& Trace
394#endif // DYNEXP_HAS_STACKTRACE
395 )
396 {
397 auto Label = Exception::GetErrorLabel(Type);
398 auto ColorString = Exception::GetErrorLabelColor(Type);
399
400 std::stringstream stream;
401
402 stream << "\n" <<
403#ifdef DYNEXP_HAS_STACKTRACE
404 (Trace.empty() ?
405#endif // DYNEXP_HAS_STACKTRACE
406 "<div class=\"entry_no_details\">"
407#ifdef DYNEXP_HAS_STACKTRACE
408 : "<details><summary class=\"entry_details\">")
409#endif // DYNEXP_HAS_STACKTRACE
410 << "<span class=\"entry_time\"><span class=\"time\">" << CurrentTimeAndDateString() << "</span></span>"
411 << "<span class=\"entry_label\"><span class=\"label\" style=\"color:" << ColorString << "\">" << Label << "</span></span>"
412 << "<span class=\"entry_text\">";
413 if (!Filename.empty() || !Function.empty())
414 {
415 stream << "<span class=\"origin\">(";
416 if (!Filename.empty())
417 {
418 stream << Filename;
419 if (Line)
420 stream << ":" << Line << (!Function.empty() ? " " : "");
421 }
422 if (!Function.empty())
423 stream << "in " << QString::fromStdString(Function).toHtmlEscaped().toStdString();
424 stream << ")</span><br>";
425 }
426 stream << "<span class=\"msg\">" << QString::fromStdString(Message).toHtmlEscaped().toStdString() << "</span>";
427 if (ErrorCode)
428 stream << " <span class=\"errno\">(Code " << ErrorCode << ")</span>";
429 stream << "\n</span>";
430
431#ifdef DYNEXP_HAS_STACKTRACE
432 if (!Trace.empty())
433 {
434 stream << "\n</summary><div class=\"trace\"><ol>\n";
435 for (const auto& entry : Trace)
436 stream << "<li>" << QString::fromStdString(std::to_string(entry)).toHtmlEscaped().toStdString() << "</li>\n";
437 stream << "</ol></div></details>";
438 }
439 else
440#endif // DYNEXP_HAS_STACKTRACE
441 stream << "\n</div>";
442
443 return stream.str();
444 }
445
446 void EventLogger::OpenLogFile(std::string Filename)
447 {
448 auto lock = AcquireLock(LogOperationTimeout);
449
452
453 LogFile.open(Filename, std::ios_base::out | std::ios_base::trunc);
454 this->Filename = Filename;
455
456 LogFile << "<!DOCTYPE html>\n"
457 << "<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<style>\n"
458 << "body {background-color:white; font-family:sans-serif}\n"
459 << ".version {font-style:italic}\n"
460 << ".trace {margin-left: 18em}\n"
461 << "summary::marker {content:none}\n"
462 << "summary::before {content:\"+\"; font-weight:bold; float:left; position:absolute; left:.9em}\n"
463 << "details[open] summary::before {content:\"-\"}\n"
464 << ".entry_no_details {margin-left:1.4em; margin-block:.2em}\n"
465 << ".entry_details {margin-left:1.4em; list-style-position:outside}\n"
466 << ".entry_label, .entry_text {display:table-cell; padding-right:1em; vertical-align:top; min-width:6em}\n"
467 << ".entry_time {display:table-cell; padding-right:1em; vertical-align:top; min-width:10em}\n"
468 << "div.entry_no_details:hover, summary.entry_details:hover {background-color:lightgray}\n"
469 << ".label {font-weight:bold}\n"
470 << ".origin {font-family:monospace}\n"
471 << ".errno {font-style:italic}\n"
472 << ".trace ol {font-family:monospace; margin-block-start:.2em; margin-left:2em}\n"
473 << ".end {font-style:italic}\n"
474 << "</style>\n<title>DynExp Logfile</title>\n</head>\n<body>\n"
475 << "<h1>DynExp Logfile</h1>\n"
476#ifdef DYNEXP_DEBUG
477 << "<p class=\"version\">(" << DynExp::DynExpVersion << "-Debug)</p>\n"
478#else
479 << "<p class=\"version\">(" << DynExp::DynExpVersion << "-Release)</p>\n"
480#endif // DYNEXP_DEBUG
481 << "<div class=\"entries\">";
482
483 LogFile.flush();
484 }
485
486 std::vector<LogEntry> EventLogger::GetLog(size_t FirstElement) const
487 {
488 auto lock = AcquireLock(LogOperationTimeout);
489
490 if (FirstElement >= LogEntries.size())
491 throw Util::OutOfRangeException("FirstElement exceeds number of log entries.");
492
493 return { LogEntries.cbegin() + FirstElement, LogEntries.cend() };
494 }
495
497 {
498 if (IsOpenUnsafe())
499 {
500 if (LogFile.tellp())
501 LogFile << "\n</div>\n<p class=\"end\">*** Logfile end.</p>\n</body>\n</html>";
502 LogFile.flush();
503 LogFile.close();
504 }
505
506 Filename = "";
507 }
508
510 {
511 static EventLogger EventLog;
512
513 return EventLog;
514 }
515}
Provides general utilities within DynExp's Util namespace.
Data type which manages a binary large object. The reserved memory is freed upon destruction.
Definition Util.h:517
void Reserve(size_t Size)
Reserves Size bytes of memory freeing any previously reserved memory.
Definition Util.cpp:118
size_t DataSize
Size of the stored data in bytes.
Definition Util.h:541
unsigned char[] DataType
Type of the buffer's data.
Definition Util.h:519
DataPtrType::element_type * Release() noexcept
Releases ownership of the stored buffer returning a pointer to it and leaving this instance empty.
Definition Util.cpp:143
DataPtrType DataPtr
Pointer to the buffer.
Definition Util.h:540
auto Size() const noexcept
Returns the size of the stored data in bytes.
Definition Util.h:537
void Reset()
Frees any reserved memory.
Definition Util.cpp:137
BlobDataType()=default
Constructs an empty object.
void Assign(size_t Size, const DataType Data)
Copies Size bytes from Data to the buffer freeing any previously reserved memory.
Definition Util.cpp:131
BlobDataType & operator=(const BlobDataType &Other)
Copy-assigns data from Other.
Definition Util.cpp:101
Logs events like errors and writes them immediately to a HTML file in a human-readable format....
Definition Util.h:1061
bool IsOpenUnsafe() const
Definition Util.h:1196
static constexpr auto LogOperationTimeout
Internal timeout for locking the mutex which synchronizes the calls to member function calls....
Definition Util.h:1205
void Log(const std::string &Message, const ErrorType Type=ErrorType::Info, const size_t Line=0, const std::string &Function="", const std::string &File="", const int ErrorCode=0, const std::stacktrace &Trace={}) noexcept
Logs an event from information specified manually.
Definition Util.cpp:309
void CloseLogFileUnsafe()
Definition Util.cpp:496
std::ofstream LogFile
Stream object to write to the log file on disk.
Definition Util.h:1207
std::string Filename
Filename and path to the log file on disk.
Definition Util.h:1208
static std::string FormatLogHTML(const std::string &Message, const ErrorType Type=ErrorType::Info, const size_t Line=0, const std::string &Function="", const std::string &Filename="", const int ErrorCode=0, const std::stacktrace &Trace={})
Formats a log entry as HTML code to be displayed in a web browser.
Definition Util.cpp:390
EventLogger()
Constructs the event logger without opening a log file on disk. Events are only stored in the interna...
Definition Util.cpp:304
static std::string FormatLog(const std::string &Message, const size_t Line=0, const std::string &Function="", const std::string &Filename="", const int ErrorCode=0, const bool PrefixMessage=true)
Formats a log entry as plain text to be displayed within DynExp.
Definition Util.cpp:365
std::vector< LogEntry > LogEntries
Internally stored log entries.
Definition Util.h:1210
std::vector< LogEntry > GetLog(size_t FirstElement=0) const
Returns the internal event log starting from the n-th stored element.
Definition Util.cpp:486
void ClearLogUnsafe()
Definition Util.h:1198
void OpenLogFile(std::string Filename)
Opens the HTML log file on disk. If it already exists, the file gets overwritten.
Definition Util.cpp:446
DynExp exceptions are derived from this class. It contains basic information about the cause of the e...
Definition Exception.h:51
const std::string File
Source code file where the exception occurred.
Definition Exception.h:109
const size_t Line
Line in source code where the exception occurred.
Definition Exception.h:107
constexpr const char * GetErrorLabelColor() const
Definition Exception.h:99
const std::string Function
Function in source code where the exception occurred
Definition Exception.h:108
constexpr const char * GetErrorLabel() const
Definition Exception.h:98
const int ErrorCode
DynExp error code from DynExpErrorCodes::DynExpErrorCodes
Definition Exception.h:106
LockType AcquireLock(const std::chrono::milliseconds Timeout=DefaultTimeout) const
Locks the internal mutex. Blocks until the mutex is locked or until the timeout duration is exceeded.
Definition Util.cpp:8
MutexType LockMutex
Internal mutex used for locking.
Definition Util.h:82
Thrown when a function call is not allowed to a specific thread in a multi-threading context.
Definition Exception.h:175
Data to operate on is invalid for a specific purpose. This indicates a corrupted data structure or fu...
Definition Exception.h:163
Thrown when some operation or feature is temporarily or permanently not available.
Definition Exception.h:286
std::condition_variable ConditionVariable
Definition Util.h:290
std::atomic< bool > MutexCanBeDestroyed
Definition Util.h:287
bool Wait(const std::chrono::milliseconds Timeout=std::chrono::milliseconds(0))
Makes current thread wait until it is notified or until given timeout duration is exceeded....
Definition Util.cpp:39
std::mutex Mutex
Definition Util.h:289
void Notify()
Set notification to stop waiting (sets EventOccurred to true).
Definition Util.cpp:69
void Ignore()
Ignore last notification (sets EventOccurred to false).
Definition Util.cpp:79
Thrown when an argument passed to a function exceeds the valid range.
Definition Exception.h:211
Thrown when a numeric operation would result in an overflow (e.g. due to incompatible data types)
Definition Exception.h:199
Thrown when an operation timed out before it could be completed, especially used for locking shared d...
Definition Exception.h:261
Class to store information about warnings in a thread-safe manner (deriving from ILockable)....
Definition Util.h:966
void Reset()
Clears the warning data.
Definition Util.cpp:273
WarningData Get() const
Returns a copy of the warning data.
Definition Util.cpp:297
Warning()
Constructs an empty Warning.
Definition Util.h:1000
Warning & operator=(const Exception &e)
Retrives the warning data from an exception e derived from Exception.
Definition Util.cpp:280
std::unique_ptr< WarningData > Data
Pointer to warning data. Must never be nullptr.
Definition Util.h:1038
constexpr auto DynExpVersion
DynExp's version string
DynExp's Util namespace contains commonly used functions and templates as well as extensions to Qt an...
std::string ToStr(const T &Value, int Precision=-1)
Converts a (numeric) value of type T to a std::string using operator<< of std::stringstream.
Definition Util.h:625
auto FilenameFromPath(std::string Path)
Extracts the filename from a path.
Definition Util.h:846
std::string ExceptionToStr(const std::exception_ptr ExceptionPtr)
Returns the what() information of an exception derived from std::exception and stored in an exception...
Definition Util.cpp:189
auto CurrentTimeAndDateString()
Returns a human-readable string describing the current time and date in the current time zone.
Definition Util.h:839
VersionType VersionFromString(std::string_view Str)
Extracts a program version from a string.
Definition Util.cpp:162
unsigned int Major
Definition Util.h:860
EventLogger & EventLog()
This function holds a static EventLogger instance and returns a reference to it. DynExp uses only one...
Definition Util.cpp:509
unsigned int Minor
Definition Util.h:861
std::strong_ordering operator<=>(const VersionType &lhs, const VersionType &rhs)
Compares two program version types with each other.
Definition Util.cpp:149
std::string ToLower(std::string_view Str)
Transforms a string into lower case.
Definition Util.cpp:208
unsigned int Patch
Definition Util.h:862
ErrorType
DynExp's error types
Definition Exception.h:15
std::vector< std::complex< double > > FFT(const std::vector< std::complex< double > > &Data, bool InverseTransform)
Computes the Fast Fourier Transform (FFT) a vector of complex values.
Definition Util.cpp:220
Data type describing DynExp's program version in the form Major.Minor.Patch.
Definition Util.h:859
Accumulates include statements to provide a precompiled header.
#define DYNEXP_HAS_STACKTRACE
Definition stdafx.h:60
Data type of a single entry in DynExp's log.
Definition Util.h:1045
Data associated with a warning. The class is convertible to bool (true if it describes an error/warni...
Definition Util.h:973