mirror of
https://github.com/meshtastic/firmware.git
synced 2025-06-08 14:12:05 +00:00
Host metrics (#6817)
* Add std::string exec() function to PortduinoGlue for future work * MVP for HostMetrics module. * Fix compilation for other targets * Remove extra debug calls * Big numbers don't do well as INTs. * Add HostMetrics to config.yaml
This commit is contained in:
parent
7d8f9c7f6d
commit
1ef4caea05
@ -193,6 +193,12 @@ Webserver:
|
||||
# SSLKey: /etc/meshtasticd/ssl/private_key.pem # Path to SSL Key, generated if not present
|
||||
# SSLCert: /etc/meshtasticd/ssl/certificate.pem # Path to SSL Certificate, generated if not present
|
||||
|
||||
|
||||
HostMetrics:
|
||||
# ReportInterval: 30 # Interval in minutes between HostMetrics report packets, or 0 for disabled
|
||||
# Channel: 0 # channel to send Host Metrics over. Defaults to the primary channel.
|
||||
|
||||
|
||||
General:
|
||||
MaxNodes: 200
|
||||
MaxMessageQueue: 100
|
||||
|
@ -49,6 +49,7 @@
|
||||
#endif
|
||||
#if ARCH_PORTDUINO
|
||||
#include "input/LinuxInputImpl.h"
|
||||
#include "modules/Telemetry/HostMetrics.h"
|
||||
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
|
||||
#include "modules/StoreForwardModule.h"
|
||||
#endif
|
||||
@ -196,6 +197,9 @@ void setupModules()
|
||||
#if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES
|
||||
cannedMessageModule = new CannedMessageModule();
|
||||
#endif
|
||||
#if ARCH_PORTDUINO
|
||||
new HostMetricsModule();
|
||||
#endif
|
||||
#if HAS_TELEMETRY
|
||||
new DeviceTelemetryModule();
|
||||
#endif
|
||||
|
131
src/modules/Telemetry/HostMetrics.cpp
Normal file
131
src/modules/Telemetry/HostMetrics.cpp
Normal file
@ -0,0 +1,131 @@
|
||||
#include "HostMetrics.h"
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "MeshService.h"
|
||||
#if ARCH_PORTDUINO
|
||||
#include "PortduinoGlue.h"
|
||||
#include <filesystem>
|
||||
#endif
|
||||
|
||||
int32_t HostMetricsModule::runOnce()
|
||||
{
|
||||
#if ARCH_PORTDUINO
|
||||
if (settingsMap[hostMetrics_interval] == 0) {
|
||||
return disable();
|
||||
} else {
|
||||
sendMetrics();
|
||||
return 60 * 1000 * settingsMap[hostMetrics_interval];
|
||||
}
|
||||
#else
|
||||
return disable();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool HostMetricsModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
|
||||
{
|
||||
// Don't worry about storing telemetry in NodeDB if we're a repeater
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER)
|
||||
return false;
|
||||
|
||||
if (t->which_variant == meshtastic_Telemetry_host_metrics_tag) {
|
||||
#ifdef DEBUG_PORT
|
||||
const char *sender = getSenderShortName(mp);
|
||||
|
||||
LOG_INFO("(Received Host Metrics from %s): uptime=%u, diskfree=%lu, memory free=%lu, load=%04.2f, %04.2f, %04.2f", sender,
|
||||
t->variant.host_metrics.uptime_seconds, t->variant.host_metrics.diskfree1_bytes,
|
||||
t->variant.host_metrics.freemem_bytes, static_cast<float>(t->variant.host_metrics.load1) / 100,
|
||||
static_cast<float>(t->variant.host_metrics.load5) / 100,
|
||||
static_cast<float>(t->variant.host_metrics.load15) / 100);
|
||||
#endif
|
||||
}
|
||||
return false; // Let others look at this message also if they want
|
||||
}
|
||||
|
||||
/*
|
||||
meshtastic_MeshPacket *HostMetricsModule::allocReply()
|
||||
{
|
||||
if (currentRequest) {
|
||||
auto req = *currentRequest;
|
||||
const auto &p = req.decoded;
|
||||
meshtastic_Telemetry scratch;
|
||||
meshtastic_Telemetry *decoded = NULL;
|
||||
memset(&scratch, 0, sizeof(scratch));
|
||||
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_HostMetrics_msg, &scratch)) {
|
||||
decoded = &scratch;
|
||||
} else {
|
||||
LOG_ERROR("Error decoding HostMetrics module!");
|
||||
return NULL;
|
||||
}
|
||||
// Check for a request for device metrics
|
||||
if (decoded->which_variant == meshtastic_Telemetry_host_metrics_tag) {
|
||||
LOG_INFO("Device telemetry reply to request");
|
||||
return allocDataProtobuf(getHostMetrics());
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
*/
|
||||
|
||||
#if ARCH_PORTDUINO
|
||||
meshtastic_Telemetry HostMetricsModule::getHostMetrics()
|
||||
{
|
||||
std::string file_line;
|
||||
meshtastic_Telemetry t = meshtastic_HostMetrics_init_zero;
|
||||
t.which_variant = meshtastic_Telemetry_host_metrics_tag;
|
||||
|
||||
if (access("/proc/uptime", R_OK) == 0) {
|
||||
std::ifstream proc_uptime("/proc/uptime");
|
||||
if (proc_uptime.is_open()) {
|
||||
std::getline(proc_uptime, file_line, ' ');
|
||||
proc_uptime.close();
|
||||
t.variant.host_metrics.uptime_seconds = stoul(file_line);
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::space_info root = std::filesystem::space("/");
|
||||
t.variant.host_metrics.diskfree1_bytes = root.available;
|
||||
|
||||
if (access("/proc/meminfo", R_OK) == 0) {
|
||||
std::ifstream proc_meminfo("/proc/meminfo");
|
||||
if (proc_meminfo.is_open()) {
|
||||
do {
|
||||
std::getline(proc_meminfo, file_line);
|
||||
} while (file_line.find("MemAvailable") == std::string::npos);
|
||||
proc_meminfo.close();
|
||||
t.variant.host_metrics.freemem_bytes = stoull(file_line.substr(file_line.find_first_of("0123456789"))) * 1024;
|
||||
}
|
||||
}
|
||||
if (access("/proc/loadavg", R_OK) == 0) {
|
||||
std::ifstream proc_loadavg("/proc/loadavg");
|
||||
if (proc_loadavg.is_open()) {
|
||||
std::getline(proc_loadavg, file_line, ' ');
|
||||
t.variant.host_metrics.load1 = stof(file_line) * 100;
|
||||
std::getline(proc_loadavg, file_line, ' ');
|
||||
t.variant.host_metrics.load5 = stof(file_line) * 100;
|
||||
std::getline(proc_loadavg, file_line, ' ');
|
||||
t.variant.host_metrics.load15 = stof(file_line) * 100;
|
||||
proc_loadavg.close();
|
||||
}
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
bool HostMetricsModule::sendMetrics()
|
||||
{
|
||||
meshtastic_Telemetry telemetry = getHostMetrics();
|
||||
LOG_INFO("Send: uptime=%u, diskfree=%lu, memory free=%lu, load=%04.2f, %04.2f, %04.2f",
|
||||
telemetry.variant.host_metrics.uptime_seconds, telemetry.variant.host_metrics.diskfree1_bytes,
|
||||
telemetry.variant.host_metrics.freemem_bytes, static_cast<float>(telemetry.variant.host_metrics.load1) / 100,
|
||||
static_cast<float>(telemetry.variant.host_metrics.load5) / 100,
|
||||
static_cast<float>(telemetry.variant.host_metrics.load15) / 100);
|
||||
|
||||
meshtastic_MeshPacket *p = allocDataProtobuf(telemetry);
|
||||
p->to = NODENUM_BROADCAST;
|
||||
p->decoded.want_response = false;
|
||||
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||
p->channel = settingsMap[hostMetrics_channel];
|
||||
LOG_INFO("Send packet to mesh");
|
||||
service->sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
return true;
|
||||
}
|
||||
#endif
|
40
src/modules/Telemetry/HostMetrics.h
Normal file
40
src/modules/Telemetry/HostMetrics.h
Normal file
@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "ProtobufModule.h"
|
||||
|
||||
class HostMetricsModule : private concurrency::OSThread, public ProtobufModule<meshtastic_Telemetry>
|
||||
{
|
||||
CallbackObserver<HostMetricsModule, const meshtastic::Status *> nodeStatusObserver =
|
||||
CallbackObserver<HostMetricsModule, const meshtastic::Status *>(this, &HostMetricsModule::handleStatusUpdate);
|
||||
|
||||
public:
|
||||
HostMetricsModule()
|
||||
: concurrency::OSThread("HostMetrics"),
|
||||
ProtobufModule("HostMetrics", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg)
|
||||
{
|
||||
uptimeWrapCount = 0;
|
||||
uptimeLastMs = millis();
|
||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||
setIntervalFromNow(setStartDelay()); // Wait until NodeInfo is sent
|
||||
}
|
||||
virtual bool wantUIFrame() { return false; }
|
||||
|
||||
protected:
|
||||
/** Called to handle a particular incoming message
|
||||
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
|
||||
*/
|
||||
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *p) override;
|
||||
// virtual meshtastic_MeshPacket *allocReply() override;
|
||||
virtual int32_t runOnce() override;
|
||||
/**
|
||||
* Send our Telemetry into the mesh
|
||||
*/
|
||||
bool sendMetrics();
|
||||
|
||||
private:
|
||||
meshtastic_Telemetry getHostMetrics();
|
||||
|
||||
uint32_t lastSentToMesh = 0;
|
||||
uint32_t uptimeWrapCount;
|
||||
uint32_t uptimeLastMs;
|
||||
};
|
@ -600,6 +600,11 @@ bool loadConfig(const char *configPath)
|
||||
(yamlConfig["Webserver"]["SSLCert"]).as<std::string>("/etc/meshtasticd/ssl/certificate.pem");
|
||||
}
|
||||
|
||||
if (yamlConfig["HostMetrics"]) {
|
||||
settingsMap[hostMetrics_channel] = (yamlConfig["HostMetrics"]["Channel"]).as<int>(0);
|
||||
settingsMap[hostMetrics_interval] = (yamlConfig["HostMetrics"]["ReportInterval"]).as<int>(0);
|
||||
}
|
||||
|
||||
if (yamlConfig["General"]) {
|
||||
settingsMap[maxnodes] = (yamlConfig["General"]["MaxNodes"]).as<int>(200);
|
||||
settingsMap[maxtophone] = (yamlConfig["General"]["MaxMessageQueue"]).as<int>(100);
|
||||
@ -650,4 +655,18 @@ bool MAC_from_string(std::string mac_str, uint8_t *dmac)
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string exec(const char *cmd)
|
||||
{ // https://stackoverflow.com/a/478960
|
||||
std::array<char, 128> buffer;
|
||||
std::string result;
|
||||
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
|
||||
if (!pipe) {
|
||||
throw std::runtime_error("popen() failed!");
|
||||
}
|
||||
while (fgets(buffer.data(), static_cast<int>(buffer.size()), pipe.get()) != nullptr) {
|
||||
result += buffer.data();
|
||||
}
|
||||
return result;
|
||||
}
|
@ -100,7 +100,9 @@ enum configNames {
|
||||
ascii_logs,
|
||||
config_directory,
|
||||
available_directory,
|
||||
mac_address
|
||||
mac_address,
|
||||
hostMetrics_interval,
|
||||
hostMetrics_channel
|
||||
};
|
||||
enum { no_screen, x11, fb, st7789, st7735, st7735s, st7796, ili9341, ili9342, ili9486, ili9488, hx8357d };
|
||||
enum { no_touchscreen, xpt2046, stmpe610, gt911, ft5x06 };
|
||||
@ -114,4 +116,5 @@ int initGPIOPin(int pinNum, std::string gpioChipname, int line);
|
||||
bool loadConfig(const char *configPath);
|
||||
static bool ends_with(std::string_view str, std::string_view suffix);
|
||||
void getMacAddr(uint8_t *dmac);
|
||||
bool MAC_from_string(std::string mac_str, uint8_t *dmac);
|
||||
bool MAC_from_string(std::string mac_str, uint8_t *dmac);
|
||||
std::string exec(const char *cmd);
|
Loading…
Reference in New Issue
Block a user