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 <interfaces/atomic_ops.h>
31#include <sys/stat.h>
32#include <sys/types.h>
33#include <tscpp/buffer.h>
34#include <utils/Debug.h>
35
36#include <chrono>
37#include <fstream>
38#include <stdexcept>
39
40using namespace std;
41using namespace miosix;
42
43namespace Boardcore
44{
45
47{
48 if (started)
49 return true;
50
51 // Find the proper log filename base on the current files on the disk
52 string filename;
53 for (fileNumber = 0; fileNumber < (int)maxFilenameNumber; fileNumber++)
54 {
55 // Check if the current file does not exists
56 filename = getFileName(fileNumber);
57 struct stat st;
58 if (stat(filename.c_str(), &st) != 0)
59 break;
60
61 if (fileNumber == maxFilenameNumber - 1)
62 TRACE("Too many log files, appending data to last\n");
63 }
64
65 file = fopen(filename.c_str(), "ab"); // b for binary
66 if (file == NULL)
67 {
68 fileNumber = -1;
69 TRACE("Error opening %s file\n", filename.c_str());
70 return false;
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 getFileName(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
174 for (unsigned int i = 0; i < numRecords; i++)
175 emptyRecordsQueue.put(new Record);
176
177 // Allocate buffers and put them in the empty list
178 for (unsigned int i = 0; i < numBuffers; i++)
179 emptyBufferList.push(new Buffer);
180}
181
182string Logger::getFileName(int logNumber)
183{
184 char filename[32];
185 sprintf(filename, "/sd/log%02d.dat", logNumber);
186
187 return string(filename);
188}
189
190void Logger::packThreadLauncher(void* argv)
191{
192 reinterpret_cast<Logger*>(argv)->packThread();
193}
194
195void Logger::writeThreadLauncher(void* argv)
196{
197 reinterpret_cast<Logger*>(argv)->writeThread();
198}
199
200void Logger::packThread()
201{
202 /*
203 * The first implementation of this class had the log() function write
204 * directly the serialized data to the buffers. So, no Records nor
205 * packThread existed. However, to be able to call log() concurrently
206 * without messing up the buffer, a mutex was needed. Thus, if many
207 * threads call log(), contention on that mutex would occur, serializing
208 * accesses and slowing down the (potentially real-time) callers. For this
209 * reason Records and the pack thread were added.
210 * Now each log() works independently on its own Record, and log() accesses
211 * can proceed in parallel.
212 */
213
214 try
215 {
216 for (;;)
217 {
219
220 Buffer* buffer = nullptr;
221 {
222 Lock<FastMutex> l(mutex);
223 // Get an empty buffer, wait if none is available
224 while (emptyBufferList.empty())
225 cond.wait(l);
226 buffer = emptyBufferList.front();
227 emptyBufferList.pop();
228 buffer->size = 0;
229 }
230
231 do
232 {
233 Record* record = nullptr;
234 fullRecordsQueue.get(record);
235
236 // When stop() is called, it pushes a nullptr signaling to stop
237 if (record == nullptr)
238 {
239 Lock<FastMutex> l(mutex);
240 fullBufferList.push(buffer); // Don't lose the buffer
241 fullBufferList.push(nullptr); // Signal writeThread to stop
242 cond.broadcast();
243 stats.buffersFilled++;
244 return;
245 }
246
247 memcpy(buffer->data + buffer->size, record->data, record->size);
248 buffer->size += record->size;
249 emptyRecordsQueue.put(record);
250 } while (bufferSize - buffer->size >= maxRecordSize);
251
252 {
253 Lock<FastMutex> l(mutex);
254 // Put back full buffer
255 fullBufferList.push(buffer);
256 cond.broadcast();
257 stats.buffersFilled++;
258 }
259 }
260 }
261 catch (exception& e)
262 {
263 TRACE("Error: packThread failed due to an exception: %s\n", e.what());
264 }
265}
266
267void Logger::writeThread()
268{
269 try
270 {
271 for (;;)
272 {
274
275 Buffer* buffer = nullptr;
276 {
277 Lock<FastMutex> l(mutex);
278 // Get a full buffer, wait if none is available
279 while (fullBufferList.empty())
280 cond.wait(l);
281 buffer = fullBufferList.front();
282 fullBufferList.pop();
283 }
284
285 // When packThread stops, it pushes a nullptr signaling to
286 // stop
287 if (buffer == nullptr)
288 return;
289
290 // Write data to disk
291 using namespace std::chrono;
292 auto start = system_clock::now();
293
294 size_t result = fwrite(buffer->data, 1, buffer->size, file);
295 if (result != buffer->size)
296 {
297 // If this fails and your board uses SDRAM,
298 // define and increase OVERRIDE_SD_CLOCK_DIVIDER_MAX
299 stats.writesFailed++;
300 stats.lastWriteError = ferror(file);
301 }
302 else
303 {
304 stats.buffersWritten++;
305 }
306
307 auto interval = system_clock::now() - start;
308 stats.averageWriteTime =
309 duration_cast<milliseconds>(interval).count();
310 stats.maxWriteTime =
311 max(stats.maxWriteTime, stats.averageWriteTime);
312
313 {
314 Lock<FastMutex> l(mutex);
315
316 // Put back empty buffer
317 emptyBufferList.push(buffer);
318 cond.broadcast();
319 }
320 }
321 }
322 catch (exception& e)
323 {
324 TRACE("Error: writeThread failed due to an exception: %s\n", e.what());
325 }
326}
327
328LoggerResult Logger::logImpl(const char* name, const void* data,
329 unsigned int size)
330{
331 if (started == false)
332 {
333 stats.droppedSamples++;
334
335 // Signal that we are trying to write to a closed log
336 stats.lastWriteError = -1;
337
339 }
340
341 Record* record = nullptr;
342
343 // Retrieve a record from the empty queue, if available
344 {
345 // We disable interrupts because IRQget() is nonblocking, unlike get()
346 FastInterruptDisableLock dLock;
347 if (emptyRecordsQueue.IRQget(record) == false)
348 {
349 stats.droppedSamples++;
351 }
352 }
353
354 // Copy the data in the record
355 int result =
356 tscpp::serializeImpl(record->data, maxRecordSize, name, data, size);
357
358 // If the record is too small, move the record in the empty queue and error
359 if (result == tscpp::BufferTooSmall)
360 {
361 emptyRecordsQueue.put(record);
362 atomicAdd(&stats.tooLargeSamples, 1);
363 TRACE("The current record size is not enough to store %s\n", name);
365 }
366
367 record->size = result;
368
369 // Move the record to the full queue, where the pack thread will read and
370 // store it in a buffer
371 fullRecordsQueue.put(record);
372
373 atomicAdd(&stats.queuedSamples, 1);
374
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:224
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:46
static StackLogger & getInstance()
Definition Singleton.h:52
PrintLogger l
Definition CanDriver.cpp:41
uint64_t getTimestamp()
Returns the current timer value in microseconds.
This file includes all the types the logdecoder script will decode.
LoggerResult
Possible outcomes of Logger::log().
Definition Logger.h:44
@ 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.
@ THID_LOGGER_WRITE
Definition StackData.h:41
@ THID_LOGGER_PACK
Definition StackData.h:40
Definition WIZ5500.h:318
Statistics for the logger.
Definition LoggerStats.h:35
int droppedSamples
Number of dropped samples due to fifo full.
Definition LoggerStats.h:43
int logNumber
Number of dropped samples because they where too large.
Definition LoggerStats.h:38
int averageWriteTime
Average time for an fwrite() of a buffer.
Definition LoggerStats.h:49
int maxWriteTime
Max time for an fwrite() of a buffer.
Definition LoggerStats.h:50
int lastWriteError
Error of the last fwrite() that failed.
Definition LoggerStats.h:48
int buffersWritten
Number of buffers written to disk.
Definition LoggerStats.h:46
int buffersFilled
Number of buffers filled.
Definition LoggerStats.h:45
int writesFailed
Number of fwrite() that failed.
Definition LoggerStats.h:47
int queuedSamples
Number of samples written to buffer.
Definition LoggerStats.h:44