LCOV - code coverage report
Current view: top level - src - AsyncLogger.h (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 98.7 % 150 148
Test Date: 2026-03-01 21:30:38 Functions: 100.0 % 16 16
Branches: 69.2 % 146 101

             Branch data     Line data    Source code
       1                 :             : #ifndef ASYNCLOGGER_H
       2                 :             : #define ASYNCLOGGER_H
       3                 :             : 
       4                 :             : #include <vector>
       5                 :             : #include <string>
       6                 :             : #include <fstream>
       7                 :             : #include <thread>
       8                 :             : #include <mutex>
       9                 :             : #include <condition_variable>
      10                 :             : #include <atomic>
      11                 :             : #include <chrono>
      12                 :             : #include <iomanip>
      13                 :             : #include <sstream>
      14                 :             : #include <algorithm> // For std::max
      15                 :             : #include <filesystem>
      16                 :             : 
      17                 :             : // Forward declaration
      18                 :             : struct TelemInfoV01;
      19                 :             : class FFBEngine;
      20                 :             : 
      21                 :             : // Log frame structure - captures one physics tick
      22                 :             : struct LogFrame {
      23                 :             :     double timestamp;
      24                 :             :     double delta_time;
      25                 :             :     
      26                 :             :     // Driver Inputs
      27                 :             :     float steering;
      28                 :             :     float throttle;
      29                 :             :     float brake;
      30                 :             :     
      31                 :             :     // Vehicle State
      32                 :             :     float speed;             // m/s
      33                 :             :     float lat_accel;         // m/s²
      34                 :             :     float long_accel;        // m/s²
      35                 :             :     float yaw_rate;          // rad/s
      36                 :             :     
      37                 :             :     // Front Axle - Raw Telemetry
      38                 :             :     float slip_angle_fl;
      39                 :             :     float slip_angle_fr;
      40                 :             :     float slip_ratio_fl;
      41                 :             :     float slip_ratio_fr;
      42                 :             :     float grip_fl;
      43                 :             :     float grip_fr;
      44                 :             :     float load_fl;
      45                 :             :     float load_fr;
      46                 :             :     
      47                 :             :     // Front Axle - Calculated
      48                 :             :     float calc_slip_angle_front;
      49                 :             :     float calc_grip_front;
      50                 :             :     
      51                 :             :     // Slope Detection Specific
      52                 :             :     float dG_dt;             // Derivative of lateral G
      53                 :             :     float dAlpha_dt;         // Derivative of slip angle
      54                 :             :     float slope_current;     // dG/dAlpha ratio
      55                 :             :     float slope_raw_unclamped; // NEW v0.7.38
      56                 :             :     float slope_numerator;     // NEW v0.7.38
      57                 :             :     float slope_denominator;   // NEW v0.7.38
      58                 :             :     float hold_timer;          // NEW v0.7.38
      59                 :             :     float input_slip_smoothed; // NEW v0.7.38
      60                 :             :     float slope_smoothed;    // Smoothed grip output
      61                 :             :     float confidence;        // Confidence factor (v0.7.3)
      62                 :             :     float surface_type_fl;   // NEW v0.7.39
      63                 :             :     float surface_type_fr;   // NEW v0.7.39
      64                 :             :     float slope_torque;      // NEW v0.7.40
      65                 :             :     float slew_limited_g;    // NEW v0.7.40
      66                 :             :     
      67                 :             :     // Rear Axle
      68                 :             :     float calc_grip_rear;
      69                 :             :     float grip_delta;        // Front - Rear
      70                 :             :     
      71                 :             :     // FFB Output
      72                 :             :     float ffb_total;         // Normalized output
      73                 :             :     float ffb_base;          // Base steering shaft force
      74                 :             :     float ffb_shaft_torque;  // NEW v0.7.62 (Issue #138)
      75                 :             :     float ffb_gen_torque;    // NEW v0.7.62 (Issue #138)
      76                 :             :     float ffb_sop;           // Seat of Pants force
      77                 :             :     float ffb_grip_factor;   // Applied grip modulation
      78                 :             :     float speed_gate;        // Speed gate factor
      79                 :             :     float load_peak_ref;     // NEW: Dynamic normalization reference
      80                 :             :     bool clipping;           // Output clipping flag
      81                 :             :     
      82                 :             :     // User Markers
      83                 :             :     bool marker;             // User-triggered marker
      84                 :             : };
      85                 :             : 
      86                 :             : // Session metadata for header
      87                 :             : struct SessionInfo {
      88                 :             :     std::string driver_name;
      89                 :             :     std::string vehicle_name;
      90                 :             :     std::string track_name;
      91                 :             :     std::string app_version;
      92                 :             :     
      93                 :             :     // Key settings snapshot
      94                 :             :     float gain;
      95                 :             :     float understeer_effect;
      96                 :             :     float sop_effect;
      97                 :             :     bool slope_enabled;
      98                 :             :     float slope_sensitivity;
      99                 :             :     float slope_threshold;
     100                 :             :     float slope_alpha_threshold;
     101                 :             :     float slope_decay_rate;
     102                 :             :     bool torque_passthrough; // v0.7.63
     103                 :             : };
     104                 :             : 
     105                 :             : class AsyncLogger {
     106                 :             : public:
     107                 :       16967 :     static AsyncLogger& Get() {
     108   [ +  +  +  -  :       16967 :         static AsyncLogger instance;
             +  -  -  - ]
     109                 :       16967 :         return instance;
     110                 :             :     }
     111                 :             : 
     112                 :             :     // Start logging - called from GUI
     113                 :          17 :     void Start(const SessionInfo& info, const std::string& base_path = "") {
     114         [ +  - ]:          17 :         std::lock_guard<std::mutex> lock(m_mutex);
     115         [ +  + ]:          17 :         if (m_running) return;
     116                 :             : 
     117         [ +  - ]:          16 :         m_buffer_active.reserve(BUFFER_THRESHOLD * 2);
     118         [ +  - ]:          16 :         m_buffer_writing.reserve(BUFFER_THRESHOLD * 2);
     119                 :          16 :         m_frame_count = 0;
     120                 :          16 :         m_pending_marker = false;
     121                 :          16 :         m_decimation_counter = 0;
     122                 :             : 
     123                 :             :         // Generate filename
     124                 :          16 :         auto now = std::chrono::system_clock::now();
     125                 :          16 :         auto in_time_t = std::chrono::system_clock::to_time_t(now);
     126                 :             :         
     127                 :             :         // Use localtime_s for thread safety (MSVC)
     128                 :             :         std::tm time_info;
     129                 :             :         #ifdef _WIN32
     130                 :             :             localtime_s(&time_info, &in_time_t);
     131                 :             :         #else
     132                 :          16 :             localtime_r(&in_time_t, &time_info);
     133                 :             :         #endif
     134                 :             :         
     135         [ +  - ]:          16 :         std::stringstream ss;
     136         [ +  - ]:          16 :         ss << std::put_time(&time_info, "%Y-%m-%d_%H-%M-%S");
     137         [ +  - ]:          16 :         std::string timestamp_str = ss.str();
     138                 :             :         
     139         [ +  - ]:          16 :         std::string car = SanitizeFilename(info.vehicle_name);
     140         [ +  - ]:          16 :         std::string track = SanitizeFilename(info.track_name);
     141                 :             :         
     142         [ +  - ]:          16 :         std::string path_prefix = base_path;
     143         [ +  - ]:          16 :         if (!path_prefix.empty()) {
     144                 :             :             // Ensure directory exists
     145                 :             :             try {
     146   [ +  -  +  + ]:          17 :                 std::filesystem::create_directories(path_prefix);
     147                 :           1 :             } catch (...) {
     148                 :             :                 // Ignore, let file open fail if necessary
     149         [ +  - ]:           1 :             }
     150                 :             :             
     151   [ +  +  +  -  :          16 :             if (path_prefix.back() != '/' && path_prefix.back() != '\\') {
                   +  + ]
     152         [ +  - ]:          13 :                  path_prefix += "/";
     153                 :             :             }
     154                 :             :         }
     155                 :             : 
     156   [ +  -  +  -  :          16 :         m_filename = path_prefix + "lmuffb_log_" + timestamp_str + "_" + car + "_" + track + ".csv";
          +  -  +  -  +  
             -  +  -  +  
                      - ]
     157                 :             : 
     158                 :             :         // Open file
     159         [ +  - ]:          16 :         m_file.open(m_filename);
     160         [ +  + ]:          16 :         if (m_file.is_open()) {
     161         [ +  - ]:          15 :             WriteHeader(info);
     162                 :          15 :             m_running = true;
     163         [ +  - ]:          15 :             m_worker = std::thread(&AsyncLogger::WorkerThread, this);
     164                 :             :         }
     165         [ +  + ]:          17 :     }
     166                 :             :     
     167                 :             :     // Stop logging and flush
     168                 :          26 :     void Stop() noexcept {
     169                 :             :         try {
     170                 :             :             {
     171         [ +  - ]:          26 :                 std::lock_guard<std::mutex> lock(m_mutex);
     172         [ +  + ]:          26 :                 if (!m_running) return;
     173                 :          15 :                 m_running = false;
     174         [ +  + ]:          26 :             }
     175                 :          15 :             m_cv.notify_one();
     176         [ +  - ]:          15 :             if (m_worker.joinable()) {
     177         [ +  - ]:          15 :                 m_worker.join();
     178                 :             :             }
     179         [ +  - ]:          15 :             if (m_file.is_open()) {
     180         [ +  - ]:          15 :                 m_file.close();
     181                 :             :             }
     182                 :          15 :             m_buffer_active.clear();
     183                 :          15 :             m_buffer_writing.clear();
     184                 :           0 :         } catch (...) {
     185                 :             :             // Destructor/Stop should not throw
     186                 :           0 :         }
     187                 :             :     }
     188                 :             :     
     189                 :             :     // Log a frame - called from FFB thread (must be fast!)
     190                 :        2192 :     void Log(const LogFrame& frame) {
     191         [ +  + ]:        3834 :         if (!m_running) return;
     192                 :             :         
     193                 :             :         // Decimation: 400Hz -> 100Hz
     194   [ +  +  +  +  :        2191 :         if (++m_decimation_counter < DECIMATION_FACTOR && !frame.marker && !m_pending_marker) {
             +  +  +  + ]
     195                 :        1642 :             return;
     196                 :             :         }
     197                 :         549 :         m_decimation_counter = 0;
     198                 :             : 
     199                 :         549 :         LogFrame f = frame;
     200         [ +  + ]:         549 :         if (m_pending_marker) {
     201                 :           3 :             f.marker = true;
     202                 :           3 :             m_pending_marker = false;
     203                 :             :         }
     204                 :             : 
     205                 :         549 :         bool should_notify = false;
     206                 :             :         {
     207         [ +  - ]:         549 :             std::lock_guard<std::mutex> lock(m_mutex);
     208         [ -  + ]:         549 :             if (!m_running) return; 
     209         [ +  - ]:         549 :             m_buffer_active.push_back(f);
     210                 :         549 :             should_notify = (m_buffer_active.size() >= BUFFER_THRESHOLD);
     211         [ +  - ]:         549 :         }
     212                 :             :         
     213                 :         549 :         m_frame_count++;
     214                 :             :         
     215         [ +  + ]:         549 :         if (should_notify) {
     216                 :          17 :              m_cv.notify_one();
     217                 :             :         }
     218                 :             :     }
     219                 :             :     
     220                 :             :     // Trigger a user marker
     221                 :           3 :     void SetMarker() { m_pending_marker = true; }
     222                 :             :     
     223                 :             :     // Status getters
     224                 :       14733 :     bool IsLogging() const { return m_running; }
     225                 :           8 :     size_t GetFrameCount() const { return m_frame_count; }
     226                 :           3 :     std::string GetFilename() const { return m_filename; }
     227                 :           3 :     size_t GetFileSizeBytes() const { return m_file_size_bytes; }
     228                 :             : 
     229                 :             : private:
     230                 :           1 :     AsyncLogger() : m_running(false), m_pending_marker(false), m_frame_count(0), m_decimation_counter(0), 
     231                 :           2 :                     m_file_size_bytes(0), m_last_flush_time(std::chrono::steady_clock::now()) {}
     232                 :           1 :     ~AsyncLogger() { Stop(); }
     233                 :             :     
     234                 :             :     // No copy
     235                 :             :     AsyncLogger(const AsyncLogger&) = delete;
     236                 :             :     AsyncLogger& operator=(const AsyncLogger&) = delete;
     237                 :             :     
     238                 :          15 :     void WorkerThread() {
     239                 :             :         while (true) {
     240         [ +  - ]:          16 :             std::unique_lock<std::mutex> lock(m_mutex);
     241   [ +  -  +  +  :          41 :             m_cv.wait(lock, [this] { return !m_running || !m_buffer_active.empty(); });
                   +  + ]
     242                 :             :             
     243                 :             :             // Swap buffers
     244         [ +  + ]:          16 :             if (!m_buffer_active.empty()) {
     245                 :           7 :                 std::swap(m_buffer_active, m_buffer_writing);
     246                 :             :             }
     247                 :             :             
     248                 :             :             // If stopped and empty, exit
     249   [ +  +  +  +  :          16 :             if (!m_running && m_buffer_writing.empty()) {
                   +  + ]
     250                 :           9 :                  break;
     251                 :             :             }
     252                 :             :             
     253         [ +  - ]:           7 :             lock.unlock();
     254                 :             :             
     255                 :             :             // Write buffer to disk
     256         [ +  + ]:         142 :             for (const auto& frame : m_buffer_writing) {
     257         [ +  - ]:         135 :                 WriteFrame(frame);
     258                 :             :             }
     259                 :           7 :             m_buffer_writing.clear();
     260                 :             :             
     261                 :             :             // Periodic flush to minimize data loss on crash
     262                 :           7 :             auto now = std::chrono::steady_clock::now();
     263   [ +  -  +  - ]:           7 :             auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - m_last_flush_time).count();
     264         [ +  + ]:           7 :             if (elapsed >= FLUSH_INTERVAL_SECONDS) {
     265         [ +  - ]:           1 :                 m_file.flush();
     266                 :           1 :                 m_last_flush_time = now;
     267                 :             :             }
     268                 :             :             
     269         [ +  + ]:           7 :             if (!m_running) break;
     270         [ +  + ]:          17 :         }
     271                 :          15 :     }
     272                 :             : 
     273                 :          15 :     void WriteHeader(const SessionInfo& info) {
     274                 :          15 :         m_file << "# LMUFFB Telemetry Log v1.0\n";
     275                 :          15 :         m_file << "# App Version: " << info.app_version << "\n";
     276                 :          15 :         m_file << "# ========================\n";
     277                 :          15 :         m_file << "# Session Info\n";
     278                 :          15 :         m_file << "# ========================\n";
     279                 :          15 :         m_file << "# Driver: " << info.driver_name << "\n";
     280                 :          15 :         m_file << "# Vehicle: " << info.vehicle_name << "\n";
     281                 :          15 :         m_file << "# Track: " << info.track_name << "\n";
     282                 :          15 :         m_file << "# ========================\n";
     283                 :          15 :         m_file << "# FFB Settings\n";
     284                 :          15 :         m_file << "# ========================\n";
     285                 :          15 :         m_file << "# Gain: " << info.gain << "\n";
     286                 :          15 :         m_file << "# Understeer Effect: " << info.understeer_effect << "\n";
     287                 :          15 :         m_file << "# SoP Effect: " << info.sop_effect << "\n";
     288         [ +  + ]:          15 :         m_file << "# Slope Detection: " << (info.slope_enabled ? "Enabled" : "Disabled") << "\n";
     289                 :          15 :         m_file << "# Slope Sensitivity: " << info.slope_sensitivity << "\n";
     290                 :          15 :         m_file << "# Slope Threshold: " << info.slope_threshold << "\n";
     291                 :          15 :         m_file << "# Slope Alpha Threshold: " << info.slope_alpha_threshold << "\n";
     292                 :          15 :         m_file << "# Slope Decay Rate: " << info.slope_decay_rate << "\n";
     293         [ +  + ]:          15 :         m_file << "# Torque Passthrough: " << (info.torque_passthrough ? "Enabled" : "Disabled") << "\n";
     294                 :          15 :         m_file << "# ========================\n";
     295                 :             :         
     296                 :             :         // CSV Header
     297                 :             :         m_file << "Time,DeltaTime,Speed,LatAccel,LongAccel,YawRate,Steering,Throttle,Brake,"
     298                 :             :                << "SlipAngleFL,SlipAngleFR,SlipRatioFL,SlipRatioFR,GripFL,GripFR,LoadFL,LoadFR,"
     299                 :             :                << "CalcSlipAngle,CalcGripFront,CalcGripRear,GripDelta,"
     300                 :             :                << "dG_dt,dAlpha_dt,SlopeCurrent,SlopeRaw,SlopeNum,SlopeDenom,HoldTimer,InputSlipSmooth,SlopeSmoothed,Confidence,"
     301                 :             :                << "SurfaceFL,SurfaceFR,SlopeTorque,SlewLimitedG,"
     302                 :          15 :                << "FFBTotal,FFBBase,FFBShaftTorque,FFBGenTorque,FFBSoP,GripFactor,SpeedGate,LoadPeakRef,Clipping,Marker\n";
     303                 :          15 :     }
     304                 :             : 
     305                 :         135 :     void WriteFrame(const LogFrame& frame) {
     306                 :         135 :         m_file << std::fixed << std::setprecision(4)
     307                 :         135 :                << frame.timestamp << "," << frame.delta_time << "," 
     308                 :         135 :                << frame.speed << "," << frame.lat_accel << "," << frame.long_accel << "," << frame.yaw_rate << ","
     309                 :         135 :                << frame.steering << "," << frame.throttle << "," << frame.brake << ","
     310                 :             :                
     311                 :         135 :                << frame.slip_angle_fl << "," << frame.slip_angle_fr << "," 
     312                 :         135 :                << frame.slip_ratio_fl << "," << frame.slip_ratio_fr << ","
     313                 :         135 :                << frame.grip_fl << "," << frame.grip_fr << ","
     314                 :         135 :                << frame.load_fl << "," << frame.load_fr << ","
     315                 :             :                
     316                 :         135 :                << frame.calc_slip_angle_front << "," << frame.calc_grip_front << "," << frame.calc_grip_rear << "," << frame.grip_delta << ","
     317                 :             :                
     318                 :         135 :                << frame.dG_dt << "," << frame.dAlpha_dt << "," << frame.slope_current << ","
     319                 :         135 :                << frame.slope_raw_unclamped << "," << frame.slope_numerator << "," << frame.slope_denominator << ","
     320                 :         135 :                << frame.hold_timer << "," << frame.input_slip_smoothed << ","
     321                 :         135 :                << frame.slope_smoothed << "," << frame.confidence << ","
     322                 :         135 :                << frame.surface_type_fl << "," << frame.surface_type_fr << ","
     323                 :         135 :                << frame.slope_torque << "," << frame.slew_limited_g << ","
     324                 :             :                
     325                 :         135 :                << frame.ffb_total << "," << frame.ffb_base << "," << frame.ffb_shaft_torque << "," << frame.ffb_gen_torque << "," << frame.ffb_sop << ","
     326                 :         135 :                << frame.ffb_grip_factor << "," << frame.speed_gate << "," << frame.load_peak_ref << ","
     327   [ -  +  +  + ]:         135 :                << (frame.clipping ? 1 : 0) << "," << (frame.marker ? 1 : 0) << "\n";
     328                 :             :         
     329                 :             :         // Track file size for monitoring
     330                 :         135 :         m_file_size_bytes += 200; // Approximate bytes per line
     331                 :         135 :     }
     332                 :             : 
     333                 :          32 :     std::string SanitizeFilename(const std::string& input) {
     334                 :          32 :         std::string out = input;
     335                 :             :         // Replace invalid Windows filename characters
     336                 :          32 :         std::replace(out.begin(), out.end(), ' ', '_');
     337                 :          32 :         std::replace(out.begin(), out.end(), '/', '_');
     338                 :          32 :         std::replace(out.begin(), out.end(), '\\', '_');
     339                 :          32 :         std::replace(out.begin(), out.end(), ':', '_');
     340                 :          32 :         std::replace(out.begin(), out.end(), '*', '_');
     341                 :          32 :         std::replace(out.begin(), out.end(), '?', '_');
     342                 :          32 :         std::replace(out.begin(), out.end(), '"', '_');
     343                 :          32 :         std::replace(out.begin(), out.end(), '<', '_');
     344                 :          32 :         std::replace(out.begin(), out.end(), '>', '_');
     345                 :          32 :         std::replace(out.begin(), out.end(), '|', '_');
     346                 :          32 :         return out;
     347                 :             :     }
     348                 :             :     
     349                 :             :     std::ofstream m_file;
     350                 :             :     std::string m_filename;
     351                 :             :     std::thread m_worker;
     352                 :             :     
     353                 :             :     std::vector<LogFrame> m_buffer_active;
     354                 :             :     std::vector<LogFrame> m_buffer_writing;
     355                 :             :     
     356                 :             :     std::mutex m_mutex;
     357                 :             :     std::condition_variable m_cv;
     358                 :             :     std::atomic<bool> m_running;
     359                 :             :     std::atomic<bool> m_pending_marker;
     360                 :             :     std::atomic<size_t> m_frame_count;
     361                 :             :     
     362                 :             :     int m_decimation_counter;
     363                 :             :     std::atomic<size_t> m_file_size_bytes;
     364                 :             :     std::chrono::steady_clock::time_point m_last_flush_time;
     365                 :             :     
     366                 :             :     static const int DECIMATION_FACTOR = 4; // 400Hz -> 100Hz
     367                 :             :     static const size_t BUFFER_THRESHOLD = 200; // ~0.5s of data
     368                 :             :     static const int FLUSH_INTERVAL_SECONDS = 5; // Flush every 5 seconds
     369                 :             : };
     370                 :             : 
     371                 :             : #endif // ASYNCLOGGER_H
        

Generated by: LCOV version 2.0-1