Skyward boardcore
Loading...
Searching...
No Matches
Logger.h
Go to the documentation of this file.
1/* Copyright (c) 2018 Skyward Experimental Rocketry
2 * Author: Federico Terraneo
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#pragma once
24
25#include <Singleton.h>
26#include <interfaces/atomic_ops.h>
27#include <miosix.h>
28#include <stdint.h>
29#include <utils/Debug.h>
30
31#include <atomic>
32#include <cstdio>
33#include <list>
34#include <queue>
35#include <string>
36#include <type_traits>
37#include <userde.hpp>
38
39#include "LoggerStats.h"
40
41namespace Boardcore
42{
43
50constexpr char MappingMarker = '!';
51
52enum TypeIDByte : char
53{
54 Unknown = '?',
55 Bool = 'b',
56 Char = 'c',
57 Int8 = 'h',
58 UInt8 = 'H',
59 Int16 = 'i',
60 UInt16 = 'I',
61 Int32 = 'j',
62 UInt32 = 'J',
63 Int64 = 'l',
64 UInt64 = 'L',
65 Float = 'f',
66 Double = 'd',
67};
68
69template <typename T, typename = void>
70struct TypeID
71{
72 static constexpr char VALUE = TypeIDByte::Unknown;
73};
74
75#define TYPEID_STRUCT(type, character) \
76 template <> \
77 struct TypeID<type> \
78 { \
79 static constexpr char VALUE = character; \
80 };
81
94
95template <typename T>
96struct TypeID<T, std::enable_if_t<std::is_enum<T>::value>>
97{
99};
100
101template <typename T>
103{
104 static constexpr void print(std::string& mappingString,
105 uint8_t* numberOfTypes, const char* name)
106 {
107 static_assert(TypeID<T>::VALUE != TypeIDByte::Unknown,
108 "Unknown TypeID '?'. This type is not supported for "
109 "logging, refer to the error log above for details.");
110
111 mappingString += std::string(name);
112 mappingString += '\0';
113 mappingString += TypeID<T>::VALUE;
114 mappingString += '\0';
115
116 (*numberOfTypes) += 1;
117 }
118};
119
120template <typename T, size_t I>
121struct TypePrinter<T[I]>
122{
123 static constexpr void print(std::string& mappingString,
124 uint8_t* numberOfTypes, const char* name)
125 {
126 for (unsigned int i = 0; i < I; i++)
127 {
128 mappingString += std::string(name) + "[" + std::to_string(i) + "]";
129 mappingString += '\0';
130 mappingString += TypeID<T>::VALUE;
131 mappingString += '\0';
132
133 (*numberOfTypes) += 1;
134 }
135 }
136};
137
141enum class LoggerResult
142{
143 Queued,
144 Dropped,
145 Ignored,
146 TooLarge
147};
148
152class Logger : public Singleton<Logger>
153{
154 friend class Singleton<Logger>;
155
156public:
170 bool start();
171
181 void stop();
182
186 static bool testSDCard();
187
189
190 std::string getCurrentFileName();
191
193
194 void resetStats();
195
196 bool isStarted() const;
197
212 template <typename T>
213 LoggerResult log(const T& t);
214
220 void logStats();
221
225 static constexpr unsigned int getMaxFilenameNumber()
226 {
227 return maxFilenameNumber;
228 }
229
230private:
231 Logger();
232
239 static int findNextLogNumber();
240
241 static std::string getLogName(int logNumber);
242
243 static void packThreadLauncher(void* argv);
244
245 static void writeThreadLauncher(void* argv);
246
250 void packThread();
251
255 void writeThread();
256
257 static constexpr int maxFilenameNumber = 1024;
258
259#ifndef _ARCH_CORTEXM3_STM32F2
260 static constexpr int maxRecordSize = 512;
261 static constexpr int maxMappingSize = 1024;
262 static constexpr int numRecords = 512;
263 static constexpr int numMappings = 32;
264 static constexpr int numBuffers = 8;
265 static constexpr int bufferSize = 64 * 1024;
266#else
267 static constexpr int maxRecordSize = 512;
268 static constexpr int maxMappingSize = 1024;
269 static constexpr int numRecords = 64;
270 static constexpr int numMappings = 16;
271 static constexpr int numBuffers = 8;
272 static constexpr int bufferSize = 4 * 1024;
273#endif
274
278 class MappingRecord
279 {
280 public:
281 MappingRecord() : size(0) {}
282 uint8_t data[maxMappingSize] = {};
283 unsigned int size;
284 };
285
292 class Record
293 {
294 public:
295 Record() : size(0) {}
296 uint8_t data[maxRecordSize] = {};
297 unsigned int size;
298 MappingRecord* mapping = nullptr;
299 };
300
309 template <typename T>
310 LoggerResult mapType(const T& t, Record*& record);
311
318 class Buffer
319 {
320 public:
321 Buffer() : size(0) {}
322 uint8_t data[bufferSize] = {};
323 unsigned int size;
324 };
325
326 int fileNumber = -1;
327
328 miosix::Queue<Record*, numRecords> fullRecordsQueue;
329 miosix::Queue<Record*, numRecords> emptyRecordsQueue;
330 miosix::Queue<MappingRecord*, numMappings> emptyMappingsQueue;
331 std::queue<Buffer*, std::list<Buffer*>> fullBufferList;
332 std::queue<Buffer*, std::list<Buffer*>> emptyBufferList;
333
334 miosix::FastMutex mutex;
335 miosix::ConditionVariable cond;
336
337 miosix::Thread* packTh = nullptr;
338 miosix::Thread* writeTh = nullptr;
339
340 std::atomic<bool> started{
341 false};
342
343 FILE* file = nullptr;
344 LoggerStats stats;
345
346 static_assert(
347 sizeof(int32_t) == sizeof(int),
348 "Int type should be 32 bits in size, if that is not the case "
349 "consider updating the reinterpret_cast in the atomicAdd calls");
350};
351
352template <typename T>
354{
355 // The last log number for which mapping information was written
356 // A boolean is not enough because the logger can be restarted many times
357 static int lastMappedLog = -1;
358
359 if (started == false)
360 {
361 stats.droppedSamples++;
362 // Signal that we are trying to write to a closed log
363 stats.lastWriteError = -1;
365 }
366
367 T mutableCopy = t; // TODO: update socrate to remove this
368 Record* record = nullptr;
369 // Retrieve a record from the empty queue, if available
370 {
371 // We disable interrupts because IRQget() is nonblocking, unlike get()
372 miosix::FastInterruptDisableLock dLock;
373 if (emptyRecordsQueue.IRQget(record) == false)
374 {
375 stats.droppedSamples++;
377 }
378 }
379
380 // Copy the data in the record
381 auto result = socrate::userde::serialize_with_name<T>(
382 mutableCopy, record->data, maxRecordSize);
383
384 // If the record is too small, move the record in the empty queue and
385 // error
386 if (result == socrate::userde::Error::BufferTooSmall)
387 {
388 emptyRecordsQueue.put(record);
389 miosix::atomicAdd(reinterpret_cast<int*>(&stats.tooLargeSamples), 1);
390 TRACE("The current record size is not enough to store %s\n",
391 T::reflect().type_name());
393 }
394
395 record->size = socrate::userde::Serde<T>::size() +
396 strlen(T::reflect().type_name()) + 1;
397
398 if (lastMappedLog != fileNumber)
399 {
400 auto mapResult = mapType(t, record);
401
402 if (mapResult != LoggerResult::Queued)
403 {
404 // Mapping failed, scrap the logged data and return the error
405 emptyRecordsQueue.put(record);
406 return mapResult;
407 }
408
409 // Mark mapping successful
410 lastMappedLog = fileNumber;
411 }
412 else
413 {
414 record->mapping = nullptr; // No mapping for this record
415 }
416
417 // Move the record to the full queue, where the pack thread will read and
418 // store it in a buffer
419 fullRecordsQueue.put(record);
420
421 miosix::atomicAdd(reinterpret_cast<int*>(&stats.queuedSamples), 1);
422
424}
425
426template <typename T>
427LoggerResult Logger::mapType(const T& t, Record*& record)
428{
429 MappingRecord* mapping = nullptr;
430 // Retrieve a record from the empty queue, if available
431 {
432 // We disable interrupts because IRQget() is nonblocking, unlike get()
433 miosix::FastInterruptDisableLock dLock;
434 if (emptyMappingsQueue.IRQget(mapping) == false)
435 {
436 stats.droppedSamples++;
438 }
439 }
440
441 std::string mappingString; // This is the complete mapping string
442 std::string partialMappingString; // This string only conains the type
443 // names and their typeIDs
444 uint8_t numberOfTypes =
445 0; // This is used to get the number of fields in the type
446 // (we want to count all of the elements of an array)
447
448 mappingString += MappingMarker; // Marks the beginning of a mapping
449 mappingString += T::reflect().type_name();
450 mappingString += '\0';
451
452 T::reflect().for_each_field_type(
453 [&](const char* name, auto type)
454 {
455 using Type = typename decltype(type)::Type;
456 TypePrinter<Type>::print(partialMappingString, &numberOfTypes,
457 name);
458 });
459
460 // now that we have all of the information we need we can merge the mapping
461 // strings
462 mappingString += numberOfTypes;
463 mappingString += '\0';
464 mappingString += partialMappingString;
465
466 // check if the mapping size is too large for the record, if it's too small
467 // move the record to the empty queue
468 if (mappingString.size() > maxRecordSize)
469 {
470 emptyMappingsQueue.put(mapping);
471 TRACE(
472 "The current record size is not enough to store the mapping of "
473 "%s\n",
474 T::reflect().type_name());
476 }
477
478 // If the record is big enough, copy the mapping string in the record
479 memcpy(mapping->data, mappingString.c_str(), mappingString.size());
480 mapping->size = mappingString.size();
481
482 // Add the mapping to the record
483 record->mapping = mapping;
484
485 miosix::atomicAdd(reinterpret_cast<int*>(&stats.queuedMappings), 1);
486
488}
489
490} // namespace Boardcore
491
#define TRACE(...)
Definition Debug.h:58
#define TYPEID_STRUCT(type, character)
Definition Logger.h:75
Buffered logger. Needs to be started before it can be used.
Definition Logger.h:153
LoggerResult log(const T &t)
Call this function to log a class.
Definition Logger.h:353
bool isStarted() const
Definition Logger.cpp:163
void logStats()
Log logger stats using the logger itself.
Definition Logger.cpp:165
std::string getCurrentFileName()
Definition Logger.cpp:140
static constexpr unsigned int getMaxFilenameNumber()
Returns the Max Filename number.
Definition Logger.h:225
int getCurrentLogNumber()
Definition Logger.cpp:138
LoggerStats getStats()
Definition Logger.cpp:142
static bool testSDCard()
Tests if the Logger can write to the SD card by opening a file.
Definition Logger.cpp:131
void stop()
Call this function to stop the logger.
Definition Logger.cpp:111
bool start()
Call this function to start the logger.
Definition Logger.cpp:45
Driver for the VN100S IMU.
constexpr char MappingMarker
Marker character for a type mapping.
Definition Logger.h:50
@ UInt64
Definition Logger.h:64
@ Int32
Definition Logger.h:61
@ UInt16
Definition Logger.h:60
@ Float
Definition Logger.h:65
@ UInt32
Definition Logger.h:62
@ Unknown
Definition Logger.h:54
@ Double
Definition Logger.h:66
@ Int16
Definition Logger.h:59
@ Int64
Definition Logger.h:63
@ UInt8
Definition Logger.h:58
LoggerResult
Possible outcomes of Logger::log().
Definition Logger.h:142
@ Dropped
Buffers are currently full, data will not be written. Sorry.
@ Queued
Data has been accepted by the logger and will be written.
@ TooLarge
Data is too large to be logged. Increase maxRecordSize.
@ Ignored
Logger is currently stopped, data will not be written.
Definition WIZ5500.h:339
Statistics for the logger.
Definition LoggerStats.h:35
static constexpr char VALUE
Definition Logger.h:72
static constexpr void print(std::string &mappingString, uint8_t *numberOfTypes, const char *name)
Definition Logger.h:123
static constexpr void print(std::string &mappingString, uint8_t *numberOfTypes, const char *name)
Definition Logger.h:104