Procházet zdrojové kódy

Stats: add CPU load trend series to history and web panel

Wire the 1-minute CPU load average into the stats history pipeline:

- Rename HistorySample::reserved to load_avg1_pct (uint8_t, 0-100);
  struct size unchanged.
- Populate load_avg1_pct in updateStatsHistory on ESP32 from
  _cpu_tracker.getLoadAvg1() * 100.
- Register "cpu_load" series in buildPointValue / seriesTitle /
  seriesUnit so buildSeriesJson serves it via
  /api/stats?series=cpu_load.
- Persist load_avg1_pct as a 29th CSV column in flushSummaryLog.
  parseSummaryLine accepts both 28-column (old, load_avg1_pct=0)
  and 29-column (new) formats for backward compatibility.
- Add "cpu_load" to the web panel trend card order with orange
  sparkline color (#e07b39), "N %" hover formatting, and a Y-axis
  floored at 0 with a 10% minimum ceiling.
Valentin V. Bartenev před 3 měsíci
rodič
revize
eaf3c4c263

+ 1 - 0
examples/simple_repeater/MyMesh.cpp

@@ -1941,6 +1941,7 @@ void MyMesh::updateStatsHistory(unsigned long now_ms) {
       sample.heap_min = ESP.getMinFreeHeap();
       sample.psram_free = ESP.getFreePsram();
       sample.psram_min = ESP.getMinFreePsram();
+      sample.load_avg1_pct = (uint8_t)(_cpu_tracker.getLoadAvg1() * 100.0f + 0.5f);
 #endif
       if (board.isExternalPowered()) sample.flags |= HISTORY_FLAG_EXTERNAL_POWER;
       if (board.isCharging()) sample.flags |= HISTORY_FLAG_CHARGING;

+ 19 - 5
src/helpers/StatsHistory.cpp

@@ -211,6 +211,10 @@ bool buildPointValue(const HistorySample& sample, const HistorySample* previous,
     value = static_cast<int>(sample.battery_mv);
     return true;
   }
+  if (strcmp(series, "cpu_load") == 0) {
+    value = static_cast<int>(sample.load_avg1_pct);
+    return true;
+  }
   if (strcmp(series, "memory") == 0) {
     value = static_cast<int>(sample.heap_free);
     return true;
@@ -309,6 +313,9 @@ const char* seriesTitle(const char* series) {
   if (strcmp(series, "battery") == 0) {
     return "Battery";
   }
+  if (strcmp(series, "cpu_load") == 0) {
+    return "CPU Load";
+  }
   if (strcmp(series, "memory") == 0) {
     return "Heap Free";
   }
@@ -355,6 +362,9 @@ const char* seriesUnit(const char* series) {
   if (strcmp(series, "battery") == 0) {
     return "mV";
   }
+  if (strcmp(series, "cpu_load") == 0) {
+    return "pct";
+  }
   if (strcmp(series, "memory") == 0) {
     return "bytes";
   }
@@ -639,9 +649,10 @@ bool StatsHistory::parseSummaryLine(const char* line, HistorySample& sample) con
   long gps_lon_e6 = 0;
   unsigned sensor_flags = 0;
   unsigned gps_satellites = 0;
+  unsigned load_avg1_pct = 0;
 
   int parsed = sscanf(line,
-                      "%lu,%lu,%u,%u,%d,%d,%d,%lu,%lu,%lu,%lu,%u,%u,%u,%u,%u,%u,%u,%d,%d,%u,%u,%d,%d,%ld,%ld,%u,%u",
+                      "%lu,%lu,%u,%u,%d,%d,%d,%lu,%lu,%lu,%lu,%u,%u,%u,%u,%u,%u,%u,%d,%d,%u,%u,%d,%d,%ld,%ld,%u,%u,%u",
                       &epoch_secs,
                       &uptime_secs,
                       &battery_mv,
@@ -669,8 +680,9 @@ bool StatsHistory::parseSummaryLine(const char* line, HistorySample& sample) con
                       &gps_lat_e6,
                       &gps_lon_e6,
                       &sensor_flags,
-                      &gps_satellites);
-  if (parsed == 28) {
+                      &gps_satellites,
+                      &load_avg1_pct);
+  if (parsed == 29 || parsed == 28) {
     memset(&sample, 0, sizeof(sample));
     sample.epoch_secs = static_cast<uint32_t>(epoch_secs);
     sample.uptime_secs = static_cast<uint32_t>(uptime_secs);
@@ -703,6 +715,7 @@ bool StatsHistory::parseSummaryLine(const char* line, HistorySample& sample) con
     sample.gps_satellites = static_cast<uint8_t>(gps_satellites);
     sample.flags = static_cast<uint8_t>(flags);
     sample.battery_pct = -1;
+    sample.load_avg1_pct = (parsed == 29) ? static_cast<uint8_t>(load_avg1_pct) : 0;
     return true;
   }
 
@@ -1087,7 +1100,7 @@ void StatsHistory::flushSummaryLog() {
 
   char line[384];
   snprintf(line, sizeof(line),
-           "%lu,%lu,%u,%u,%d,%d,%d,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%d,%d,%u,%u,%d,%d,%ld,%ld,%u,%u\n",
+           "%lu,%lu,%u,%u,%d,%d,%d,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%d,%d,%u,%u,%d,%d,%ld,%ld,%u,%u,%u\n",
            static_cast<unsigned long>(latest.epoch_secs),
            static_cast<unsigned long>(latest.uptime_secs),
            static_cast<unsigned>(latest.battery_mv),
@@ -1115,7 +1128,8 @@ void StatsHistory::flushSummaryLog() {
            static_cast<long>(latest.gps_lat_e6),
            static_cast<long>(latest.gps_lon_e6),
            static_cast<unsigned>(latest.sensor_flags),
-           static_cast<unsigned>(latest.gps_satellites));
+           static_cast<unsigned>(latest.gps_satellites),
+           static_cast<unsigned>(latest.load_avg1_pct));
 
   File latest_file = openArchiveWriteWithRecovery(_archive, kSummaryLatestPath);
   if (!latest_file) {

+ 1 - 1
src/helpers/StatsHistory.h

@@ -37,7 +37,7 @@ struct HistorySample {
   uint8_t gps_satellites;
   uint8_t flags;
   int8_t battery_pct;
-  uint8_t reserved;
+  uint8_t load_avg1_pct;
 };
 
 struct HistoryEvent {

+ 7 - 2
src/helpers/web/WebPanelServer.cpp

@@ -1778,6 +1778,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
       if (!Number.isFinite(value)) return "--";
       if (key === "battery") return Math.round(value) + " mV";
       if (key === "voltage") return (value / 100).toFixed(2) + " V";
+      if (key === "cpu_load") return Math.round(value) + " %";
       if (key === "memory") return formatBytes(value);
       if (key === "packets") return Math.round(value) + " pkts";
       if (key === "error_rate") return (value / 10).toFixed(1) + " %";
@@ -1817,6 +1818,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
         if (recent >= 750) return "#d7a531";  // high
         return "#2f8f4e";                     // normal
       }
+      if (key === "cpu_load") return "#e07b39";
       if (key === "error_rate") return "#ef4444";
       if (key === "gps_satellites") return "#2f8f4e";
       if (key === "signal") return "#3b82f6";
@@ -1836,6 +1838,9 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
       return baseColor || "#2f8f4e";
     }
     function sparkValueRange(key, values) {
+      if (key === "cpu_load") {
+        return { min: 0, max: Math.max(10, ...values) };
+      }
       if (key === "packets" || key === "gps_satellites") {
         const maxValue = Math.max(1, ...values);
         return { min:0, max:maxValue };
@@ -2086,9 +2091,9 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
       const sensors = summaryPayload && summaryPayload.sensors ? summaryPayload.sensors : null;
       const gpsEnabled = !!(sensors && sensors.gps_enabled === true);
       const mcuTempPresent = !!(sensors && Number.isFinite(sensors.mcu_temp_c));
-      const order = ["battery", "memory", "packets", "error_rate", "signal", "noise_floor"];
+      const order = ["battery", "cpu_load", "memory", "packets", "error_rate", "signal", "noise_floor"];
       if (mcuTempPresent) {
-        order.splice(2, 0, "mcu_temp");
+        order.splice(1, 0, "mcu_temp");
       }
       if (gpsEnabled) {
         order.push("gps_satellites");