Skyward boardcore
Loading...
Searching...
No Matches
Logger.cpp
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#include "Logger.h"
24
28#include <errno.h>
29#include <fcntl.h>
30#include <fmt/format.h>
31#include <sys/stat.h>
32#include <sys/types.h>
33
34#include <chrono>
35#include <cstring>
36#include <fstream>
37#include <stdexcept>
38
39using namespace std;
40using namespace miosix;
41
42namespace Boardcore
43{
44
46{
47 if (started)
48 return true;
49
50 // Find the next log filename based on the current files on the disk
51 int logNumber = findNextLogNumber();
52 if (logNumber < 0)
53 {
54 TRACE("Too many log files, aborting\n");
55 return false;
56 }
57
58 string logName = getLogName(logNumber);
59
60 file = fopen(logName.c_str(), "ab"); // b for binary
61 if (file == NULL)
62 {
63 fileNumber = -1;
64 TRACE("Error opening %s file\n", logName.c_str());
65 return false;
66 }
67 else
68 {
69 fileNumber = logNumber;
70 }
71
72 setbuf(file, NULL); // Disable buffering for the file stream
73
74 // The boring part, start threads one by one and if they fail, undo.
75 // Perhaps excessive defensive programming as thread creation failure is
76 // highly unlikely (only if ram is full).
77
78 packTh = Thread::create(packThreadLauncher, STACK_MIN_FOR_SKYWARD, 1, this,
79 Thread::JOINABLE);
80 if (!packTh)
81 {
82 fclose(file);
83 TRACE("Error creating pack thread\n");
84 return false;
85 }
86
87 writeTh = Thread::create(writeThreadLauncher, STACK_MIN_FOR_SKYWARD, 1,
88 this, Thread::JOINABLE);
89 if (!writeTh)
90 {
91 fullRecordsQueue.put(nullptr); // Signal packThread to stop
92 packTh->join();
93 // packThread has pushed a buffer and a nullptr to writeThread, remove
94 // it
95 while (fullBufferList.front() != nullptr)
96 {
97 emptyBufferList.push(fullBufferList.front());
98 fullBufferList.pop();
99 }
100 fullBufferList.pop(); // Remove nullptr
101 fclose(file);
102 TRACE("Error creating write thread\n");
103 return false;
104 }
105
106 started = true;
107
108 return true;
109}
110
112{
113 if (started == false)
114 return;
115 logStats();
116
117 started = false;
118
119 fullRecordsQueue.put(nullptr); // Signal packThread to stop
120
121 packTh->join();
122 writeTh->join();
123
124 fclose(file);
125
126 stats = {};
127
128 fileNumber = -1; // Reset the fileNumber to an invalid value
129}
130
132{
133 bool result = ofstream("/sd/test").good();
134 std::remove("/sd/test");
135 return result;
136}
137
138int Logger::getCurrentLogNumber() { return fileNumber; }
139
140string Logger::getCurrentFileName() { return getLogName(fileNumber); }
141
143{
145 stats.logNumber = fileNumber;
146 return stats;
147}
148
150{
151 // Keep some of the statistics persistent
152 int buffersWritten = stats.buffersWritten;
153 int writesFailed = stats.writesFailed;
154
155 // Reset
156 stats = {};
157
158 // Put back
159 stats.buffersWritten = buffersWritten;
160 stats.writesFailed = writesFailed;
161}
162
163bool Logger::isStarted() const { return started; }
164
166{
167 log(getStats());
168 resetStats();
169}
170
171Logger::Logger()
172{
173 // Allocate the records for the log
174 for (unsigned int i = 0; i < numRecords; i++)
175 emptyRecordsQueue.put(new Record);
176
177 // Allocate the records for the mappings
178 for (unsigned int i = 0; i < numMappings; i++)
179 emptyMappingsQueue.put(new MappingRecord);
180
181 // Allocate buffers for the log and put them in the empty list
182 for (unsigned int i = 0; i < numBuffers; i++)
183 emptyBufferList.push(new Buffer);
184}
185
186int Logger::findNextLogNumber()
187{
188 int low = 1;
189 int high = maxFilenameNumber;
190
191 while (low <= high)
192 {
193 int mid = low + (high - low) / 2;
194 std::string logName = getLogName(mid);
195
196 struct stat st;
197 if (stat(logName.c_str(), &st) == 0)
198 {
199 // File exists, so the next available number must be higher
200 low = mid + 1;
201 }
202 else
203 {
204 // File does not exist, so this could be the one, or it could be a
205 // lower number
206 high = mid - 1;
207 }
208 }
209
210 // After the loop `low` holds the first number for which a log was not found
211 if (low > maxFilenameNumber)
212 {
213 // If we reached the maximum number, return -1 to indicate no available
214 // log numbers
215 return -1;
216 }
217
218 return low;
219}
220
221string Logger::getLogName(int logNumber)
222{
223 return fmt::format("/sd/log{:02d}.dat", logNumber);
224}
225
226void Logger::packThreadLauncher(void* argv)
227{
228 reinterpret_cast<Logger*>(argv)->packThread();
229}
230
231void Logger::writeThreadLauncher(void* argv)
232{
233 reinterpret_cast<Logger*>(argv)->writeThread();
234}
235
236void Logger::packThread()
237{
238 /*
239 * The first implementation of this class had the log() function write
240 * directly the serialized data to the buffers. So, no Records nor
241 * packThread existed. However, to be able to call log() concurrently
242 * without messing up the buffer, a mutex was needed. Thus, if many
243 * threads call log(), contention on that mutex would occur, serializing
244 * accesses and slowing down the (potentially real-time) callers. For this
245 * reason Records and the pack thread were added.
246 * Now each log() works independently on its own Record, and log() accesses
247 * can proceed in parallel.
248 */
249
250 try
251 {
252 for (;;)
253 {
255
256 // packing for the samples
257 Buffer* buffer = nullptr;
258 {
259 Lock<FastMutex> l(mutex);
260 // Get an empty buffer, wait if none is available
261 while (emptyBufferList.empty())
262 cond.wait(l);
263 buffer = emptyBufferList.front();
264 emptyBufferList.pop();
265 buffer->size = 0;
266 }
267
268 do
269 {
270 Record* record = nullptr;
271 fullRecordsQueue.get(record);
272
273 // When stop() is called, it pushes a nullptr signaling to stop
274 if (record == nullptr)
275 {
276 Lock<FastMutex> l(mutex);
277 fullBufferList.push(buffer); // Don't lose the buffer
278 fullBufferList.push(nullptr); // Signal writeThread to stop
279
280 cond.broadcast();
281 stats.buffersFilled++;
282 return; // Stop the thread
283 }
284
285 // If the record has a mapping, we need to write it before
286 // writing the data
287 if (record->mapping)
288 {
289 auto* mapping = record->mapping;
290 memcpy(buffer->data + buffer->size, mapping->data,
291 mapping->size);
292 buffer->size += mapping->size;
293 emptyMappingsQueue.put(mapping);
294 }
295
296 memcpy(buffer->data + buffer->size, record->data, record->size);
297 buffer->size += record->size;
298 emptyRecordsQueue.put(record);
299 } while (bufferSize - buffer->size >=
300 maxRecordSize + maxMappingSize);
301
302 {
303 Lock<FastMutex> l(mutex);
304 // Put back full buffer
305 fullBufferList.push(buffer);
306 cond.broadcast();
307 stats.buffersFilled++;
308 }
309 }
310 }
311 catch (exception& e)
312 {
313 TRACE("Error: packThread failed due to an exception: %s\n", e.what());
314 }
315}
316
317void Logger::writeThread()
318{
319 try
320 {
321 for (;;)
322 {
324
325 Buffer* buffer = nullptr;
326 {
327 Lock<FastMutex> l(mutex);
328 // Get a full buffer, wait if none is available
329 while (fullBufferList.empty())
330 cond.wait(l);
331 buffer = fullBufferList.front();
332 fullBufferList.pop();
333 }
334
335 // When packThread stops, it pushes a nullptr signaling to
336 // stop
337 if (buffer == nullptr)
338 return;
339
340 // Write data to disk
341 using namespace std::chrono;
342 auto start = system_clock::now();
343
344 size_t result = fwrite(buffer->data, 1, buffer->size, file);
345 if (result != buffer->size)
346 {
347 // If this fails and your board uses SDRAM,
348 // define and increase OVERRIDE_SD_CLOCK_DIVIDER_MAX
349 stats.writesFailed++;
350 stats.lastWriteError = ferror(file);
351 }
352 else
353 {
354 stats.buffersWritten++;
355 }
356
357 auto interval = system_clock::now() - start;
358 stats.averageWriteTime =
359 duration_cast<milliseconds>(interval).count();
360 stats.maxWriteTime =
361 max(stats.maxWriteTime, stats.averageWriteTime);
362
363 {
364 Lock<FastMutex> l(mutex);
365
366 // Put back empty buffer
367 emptyBufferList.push(buffer);
368 cond.broadcast();
369 }
370 }
371 }
372 catch (exception& e)
373 {
374 TRACE("Error: writeThread failed due to an exception: %s\n", e.what());
375 }
376}
377
378} // namespace Boardcore
#define TRACE(...)
Definition Debug.h:58
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
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
static StackLogger & getInstance()
Definition Singleton.h:52
void updateStack(uint8_t threadId)
Definition StackLogger.h:55
PrintLogger l
Definition CanDriver.cpp:43
uint64_t getTimestamp()
Returns the current timer value in microseconds.
Driver for the VN100S IMU.
@ THID_LOGGER_WRITE
Definition StackData.h:42
@ THID_LOGGER_PACK
Definition StackData.h:41
Definition WIZ5500.h:339
Statistics for the logger.
Definition LoggerStats.h:35
int32_t buffersWritten
Number of buffers written to disk.
Definition LoggerStats.h:48
int32_t buffersFilled
Number of buffers filled.
Definition LoggerStats.h:47
int32_t logNumber
Number of dropped samples because they where too large.
Definition LoggerStats.h:38
int32_t lastWriteError
Error of the last fwrite() that failed.
Definition LoggerStats.h:50
int32_t maxWriteTime
Max time for an fwrite() of a buffer.
Definition LoggerStats.h:53
int32_t averageWriteTime
Average time for an fwrite() of a buffer.
Definition LoggerStats.h:51
int32_t writesFailed
Number of fwrite() that failed.
Definition LoggerStats.h:49