LCOV - code coverage report
Current view: top level - src/network - NetworkStateMonitor.cpp (source / functions) Coverage Total Hit
Test: coverage.info.cleaned Lines: 79.6 % 157 125
Test Date: 2026-01-27 22:31:25 Functions: 87.5 % 16 14

            Line data    Source code
       1              : #include "NetworkStateMonitor.h"
       2              : #include "../utilities/FangLogging.h"
       3              : #include <QVariant>
       4              : 
       5              : // Circuit breaker config.
       6              : static const int DEFAULT_FAILURE_THRESHOLD = 5; // # of failures to trip circuit
       7              : static const int DEFAULT_OPEN_DURATION = 30000;
       8              : static const int DEFAULT_WINDOW_SIZE = 60000;
       9              : static const int DEFAULT_HALF_OPEN_REQUESTS = 3; // # of successful requests to close breaker
      10              : 
      11          276 : NetworkStateMonitor& NetworkStateMonitor::instance()
      12              : {
      13          276 :     static NetworkStateMonitor instance;
      14          276 :     return instance;
      15              : }
      16              : 
      17            8 : NetworkStateMonitor::NetworkStateMonitor()
      18              :     : QObject(nullptr)
      19            8 :     , isCurrentlyOnline(true)
      20            8 :     , currentCircuitState(Closed)
      21            8 :     , successCountMetric(0)
      22            8 :     , failureCountMetric(0)
      23            8 :     , halfOpenSuccessesMetric(0)
      24            8 :     , failureThreshold(DEFAULT_FAILURE_THRESHOLD)
      25            8 :     , openDuration(DEFAULT_OPEN_DURATION)
      26            8 :     , windowSize(DEFAULT_WINDOW_SIZE)
      27            8 :     , halfOpenRequests(DEFAULT_HALF_OPEN_REQUESTS)
      28              : {
      29              :     // Monitor system network state using Qt 6 QNetworkInformation
      30              :     // Load the default backend (platform-specific)
      31            8 :     if (!QNetworkInformation::loadDefaultBackend()) {
      32            0 :         qCWarning(logNetwork) << "Failed to load network information backend, assuming online";
      33            0 :         isCurrentlyOnline = true;
      34              :     } else {
      35            8 :         QNetworkInformation* netInfo = QNetworkInformation::instance();
      36            8 :         QNetworkInformation::Reachability reachability = netInfo->reachability();
      37            8 :         isCurrentlyOnline = (reachability == QNetworkInformation::Reachability::Online);
      38              : 
      39            8 :         connect(netInfo, &QNetworkInformation::reachabilityChanged,
      40           16 :                 this, &NetworkStateMonitor::onReachabilityChanged);
      41              :     }
      42              : 
      43              :     // Circuit breaker timer.
      44            8 :     circuitTimer.setSingleShot(true);
      45            8 :     connect(&circuitTimer, &QTimer::timeout,
      46            8 :             this, &NetworkStateMonitor::onCircuitTimerTimeout);
      47              : 
      48              :     // Metrics window timer
      49            8 :     windowTimer.setInterval(windowSize);
      50            8 :     connect(&windowTimer, &QTimer::timeout,
      51            8 :             this, &NetworkStateMonitor::onWindowTimerTimeout);
      52            8 :     windowTimer.start();
      53            8 :     windowElapsed.start();
      54              : 
      55           16 :     qCInfo(logNetwork) << "Network state monitor init, online: " << isCurrentlyOnline;
      56            8 : }
      57              : 
      58            8 : NetworkStateMonitor::~NetworkStateMonitor()
      59              : {
      60            8 : }
      61              : 
      62            3 : bool NetworkStateMonitor::shouldAllowRequest() const
      63              : {
      64              :     // Always block when already offline.
      65            3 :     if (!isCurrentlyOnline) {
      66            0 :         return false;
      67              :     }
      68              : 
      69              :     // Check/re-check circuit breaker.
      70            3 :     switch (currentCircuitState) {
      71            2 :     case Closed:
      72              :         // Normal operation
      73            2 :         return true;
      74              : 
      75            1 :     case Open:
      76              :         // Block all when breaker is open.
      77            1 :         return false;
      78              : 
      79            0 :     case HalfOpen:
      80              :         // Hacky way to check if we want to stay open or close again.
      81            0 :         return true;
      82              :     }
      83              : 
      84            0 :     return true;
      85              : }
      86              : 
      87           25 : void NetworkStateMonitor::recordSuccess()
      88              : {
      89           25 :     successCountMetric++;
      90              : 
      91           25 :     switch (currentCircuitState) {
      92           13 :     case Closed:
      93           13 :         break;
      94              : 
      95           12 :     case HalfOpen:
      96           12 :         halfOpenSuccessesMetric++;
      97           24 :         qCDebug(logNetwork) << "Half open success count:" << halfOpenSuccessesMetric
      98           12 :                             << "/ threshold:" << halfOpenRequests;
      99              : 
     100              :         // Close the circuit breaker if we seem good to go.
     101           12 :         if (halfOpenSuccessesMetric >= halfOpenRequests) {
     102            4 :             closeCircuit();
     103              :         }
     104           12 :         break;
     105              : 
     106            0 :     case Open:
     107              :         // Edge case.
     108            0 :         qCWarning(logNetwork) << "Success while circuit open: closing circuit";
     109            0 :         closeCircuit();
     110            0 :         break;
     111              :     }
     112              : 
     113              :     // Update the breaker state.
     114           25 :     updateCircuitState();
     115           25 : }
     116              : 
     117          147 : void NetworkStateMonitor::recordFailure()
     118              : {
     119          147 :     failureCountMetric++;
     120              : 
     121          294 :     qCDebug(logNetwork) << "Network failure recorded. Total in window:"
     122          147 :                         << failureCountMetric << "/ threshold:" << failureThreshold;
     123              : 
     124          147 :     switch (currentCircuitState) {
     125           81 :     case Closed:
     126              :         // Check if we should trip the breaker.
     127           81 :         if (failureCountMetric >= failureThreshold) {
     128           26 :             tripCircuit();
     129              :         }
     130           81 :         break;
     131              : 
     132            2 :     case HalfOpen:
     133              :         // Trip breaker.
     134            4 :         qCInfo(logNetwork) << "Failure during half-open test: Reopening circuit breaker";
     135            2 :         tripCircuit();
     136            2 :         break;
     137              : 
     138           64 :     case Open:
     139              :         // Already open, add more to timer.
     140           64 :         circuitTimer.start(openDuration);
     141           64 :         break;
     142              :     }
     143          147 : }
     144              : 
     145            7 : float NetworkStateMonitor::failureRate() const
     146              : {
     147            7 :     int total = successCountMetric + failureCountMetric;
     148            7 :     if (total == 0) {
     149            2 :         return 0.0f;
     150              :     }
     151              : 
     152            5 :     return static_cast<float>(failureCountMetric) / static_cast<float>(total);
     153              : }
     154              : 
     155           22 : void NetworkStateMonitor::resetCircuit()
     156              : {
     157           44 :     qCInfo(logNetwork) << "Circuit breaker: Manual reset";
     158           22 :     closeCircuit();
     159           22 : }
     160              : 
     161            4 : void NetworkStateMonitor::configure(int failureThreshold, int openDuration, int windowSize)
     162              : {
     163            4 :     this->failureThreshold = failureThreshold;
     164            4 :     this->openDuration = openDuration;
     165            4 :     this->windowSize = windowSize;
     166              : 
     167              :     // Restart timer with new config.
     168            4 :     windowTimer.setInterval(windowSize);
     169              : 
     170            8 :     qCInfo(logNetwork) << "Circuit breaker configured:"
     171            4 :                        << "threshold=" << failureThreshold
     172            4 :                        << "openDuration=" << openDuration
     173            4 :                        << "windowSize=" << windowSize;
     174            4 : }
     175              : 
     176            0 : void NetworkStateMonitor::onReachabilityChanged(QNetworkInformation::Reachability reachability)
     177              : {
     178              :     // Convert to our boolean state(s)
     179            0 :     bool isOnline = (reachability == QNetworkInformation::Reachability::Online);
     180              : 
     181            0 :     if (isCurrentlyOnline == isOnline) {
     182            0 :         return; // No change
     183              :     }
     184              : 
     185            0 :     isCurrentlyOnline = isOnline;
     186              : 
     187            0 :     if (isOnline) {
     188            0 :         qCInfo(logNetwork) << "Network came online, reachability: "
     189            0 :                            << QVariant::fromValue(reachability).toString();
     190            0 :         emit networkAvailable();
     191              : 
     192              :         // Enter half-open state as we may be back online!
     193            0 :         if (currentCircuitState == Open) {
     194            0 :             currentCircuitState = HalfOpen;
     195            0 :             halfOpenSuccessesMetric = 0;
     196            0 :             emit circuitHalfOpen();
     197              :         }
     198              :     } else {
     199            0 :         qCWarning(logNetwork) << "Network went offline, reachability: "
     200            0 :                               << QVariant::fromValue(reachability).toString();
     201            0 :         emit networkUnavailable();
     202              : 
     203              :         // Error, trip breaker open.
     204            0 :         if (currentCircuitState != Open) {
     205            0 :             tripCircuit();
     206              :         }
     207              :     }
     208              : }
     209              : 
     210            6 : void NetworkStateMonitor::onCircuitTimerTimeout()
     211              : {
     212            6 :     if (currentCircuitState == Open) {
     213           12 :         qCInfo(logNetwork) << "Circuit breaker timeout: switch to half open";
     214            6 :         currentCircuitState = HalfOpen;
     215            6 :         halfOpenSuccessesMetric = 0;
     216            6 :         emit circuitHalfOpen();
     217              :     }
     218            6 : }
     219              : 
     220            2 : void NetworkStateMonitor::onWindowTimerTimeout()
     221              : {
     222              :     // Reset metrics.
     223            4 :     qCDebug(logNetwork) << "Metrics window reset. Previous window:"
     224            2 :                         << "successes: " << successCountMetric
     225            2 :                         << "failures: " << failureCountMetric
     226            2 :                         << "rate: " << (failureRate() * 100.0f) << "%";
     227              : 
     228            2 :     successCountMetric = 0;
     229            2 :     failureCountMetric = 0;
     230            2 :     windowElapsed.restart();
     231              : 
     232              :     // Check state of breaker with our new metrics.
     233            2 :     updateCircuitState();
     234            2 : }
     235              : 
     236           27 : void NetworkStateMonitor::updateCircuitState()
     237              : {
     238           27 :     if (currentCircuitState == Closed) {
     239              :         // We gonna trip...?
     240           19 :         if (failureCountMetric >= failureThreshold) {
     241            0 :             tripCircuit();
     242              :         }
     243              :     }
     244           27 : }
     245              : 
     246           28 : void NetworkStateMonitor::tripCircuit()
     247              : {
     248           28 :     if (currentCircuitState == Open) {
     249              :         // Circuit already open: extend timer.
     250            0 :         circuitTimer.start(openDuration);
     251            0 :         return;
     252              :     }
     253              : 
     254           56 :     qCWarning(logNetwork) << "Circuit breaker OPENED due to failures:"
     255           28 :                           << failureCountMetric << "failures in" << windowElapsed.elapsed() << " ms";
     256              : 
     257           28 :     currentCircuitState = Open;
     258           28 :     circuitTimer.start(openDuration);
     259              : 
     260           28 :     emit circuitOpened();
     261              : }
     262              : 
     263           26 : void NetworkStateMonitor::closeCircuit()
     264              : {
     265           26 :     if (currentCircuitState == Closed) {
     266              :         // Nothing to do!
     267            0 :         return;
     268              :     }
     269              : 
     270           52 :     qCInfo(logNetwork) << "Circuit breaker CLOSED: back to normal!";
     271              : 
     272           26 :     currentCircuitState = Closed;
     273           26 :     halfOpenSuccessesMetric = 0;
     274           26 :     circuitTimer.stop();
     275              : 
     276              :     // Reset metrics.
     277           26 :     successCountMetric = 0;
     278           26 :     failureCountMetric = 0;
     279              : 
     280           26 :     emit circuitClosed();
     281              : }
        

Generated by: LCOV version 2.0-1