From 3124a7f2dbce53df7dc0e926a30dda5043c4a800 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Thu, 21 Aug 2025 20:35:53 +0200 Subject: [PATCH] feat: memberlist is now a map --- src/ApiServer.cpp | 13 +++++-- src/ClusterManager.cpp | 81 ++++++++++++++++++++++++------------------ src/ClusterManager.h | 3 +- src/NodeContext..cpp | 2 +- src/NodeContext.h | 5 ++- 5 files changed, 62 insertions(+), 42 deletions(-) diff --git a/src/ApiServer.cpp b/src/ApiServer.cpp index 341b2a8..0290417 100644 --- a/src/ApiServer.cpp +++ b/src/ApiServer.cpp @@ -6,7 +6,10 @@ void ApiServer::addEndpoint(const String& uri, int method, std::functionempty()) { - (*ctx.memberList)[0].apiEndpoints.push_back(std::make_tuple(uri, method)); + auto it = ctx.memberList->find(ctx.hostname); + if (it != ctx.memberList->end()) { + it->second.apiEndpoints.push_back(std::make_tuple(uri, method)); + } } server.on(uri.c_str(), method, requestHandler); } @@ -15,7 +18,10 @@ void ApiServer::addEndpoint(const String& uri, int method, std::function uploadHandler) { serviceRegistry.push_back(std::make_tuple(uri, method)); if (ctx.memberList && !ctx.memberList->empty()) { - (*ctx.memberList)[0].apiEndpoints.push_back(std::make_tuple(uri, method)); + auto it = ctx.memberList->find(ctx.hostname); + if (it != ctx.memberList->end()) { + it->second.apiEndpoints.push_back(std::make_tuple(uri, method)); + } } server.on(uri.c_str(), method, requestHandler, uploadHandler); } @@ -55,7 +61,8 @@ void ApiServer::onSystemStatusRequest(AsyncWebServerRequest *request) { void ApiServer::onClusterMembersRequest(AsyncWebServerRequest *request) { JsonDocument doc; JsonArray arr = doc["members"].to(); - for (const auto& node : *ctx.memberList) { + for (const auto& pair : *ctx.memberList) { + const NodeInfo& node = pair.second; JsonObject obj = arr.add(); obj["hostname"] = node.hostname; obj["ip"] = node.ip.toString(); diff --git a/src/ClusterManager.cpp b/src/ClusterManager.cpp index b456b0e..23814cf 100644 --- a/src/ClusterManager.cpp +++ b/src/ClusterManager.cpp @@ -41,19 +41,24 @@ void ClusterManager::listenForDiscovery() { void ClusterManager::addOrUpdateNode(const String& nodeHost, IPAddress nodeIP) { auto& memberList = *ctx.memberList; - for (auto& node : memberList) { - if (node.hostname == nodeHost) { - node.ip = nodeIP; - //fetchNodeInfo(nodeIP); // Do not fetch here, handled by periodic task - return; - } + + // O(1) lookup instead of O(n) search + auto it = memberList.find(nodeHost); + if (it != memberList.end()) { + // Update existing node + it->second.ip = nodeIP; + it->second.lastSeen = millis(); + //fetchNodeInfo(nodeIP); // Do not fetch here, handled by periodic task + return; } + + // Add new node NodeInfo newNode; newNode.hostname = nodeHost; newNode.ip = nodeIP; newNode.lastSeen = millis(); updateNodeStatus(newNode, newNode.lastSeen); - memberList.push_back(newNode); + memberList[nodeHost] = newNode; Serial.printf("[Cluster] Added node: %s @ %s | Status: %s | last update: 0\n", nodeHost.c_str(), newNode.ip.toString().c_str(), @@ -77,7 +82,9 @@ void ClusterManager::fetchNodeInfo(const IPAddress& ip) { DeserializationError err = deserializeJson(doc, payload); if (!err) { auto& memberList = *ctx.memberList; - for (auto& node : memberList) { + // Still need to iterate since we're searching by IP, not hostname + for (auto& pair : memberList) { + NodeInfo& node = pair.second; if (node.ip == ip) { node.resources.freeHeap = doc["freeHeap"]; node.resources.chipId = doc["chipId"]; @@ -96,6 +103,7 @@ void ClusterManager::fetchNodeInfo(const IPAddress& ip) { } } Serial.printf("[Cluster] Fetched info for node: %s @ %s\n", node.hostname.c_str(), ip.toString().c_str()); + break; } } } @@ -106,20 +114,21 @@ void ClusterManager::fetchNodeInfo(const IPAddress& ip) { } void ClusterManager::heartbeatTaskCallback() { - for (auto& node : *ctx.memberList) { - if (node.hostname == ctx.hostname) { - node.lastSeen = millis(); - node.status = NodeInfo::ACTIVE; - updateLocalNodeResources(); - ctx.fire("node_discovered", &node); - break; - } + auto& memberList = *ctx.memberList; + auto it = memberList.find(ctx.hostname); + if (it != memberList.end()) { + NodeInfo& node = it->second; + node.lastSeen = millis(); + node.status = NodeInfo::ACTIVE; + updateLocalNodeResources(); + ctx.fire("node_discovered", &node); } } void ClusterManager::updateAllMembersInfoTaskCallback() { auto& memberList = *ctx.memberList; - for (const auto& node : memberList) { + for (auto& pair : memberList) { + const NodeInfo& node = pair.second; if (node.ip != ctx.localIP) { fetchNodeInfo(node.ip); } @@ -129,7 +138,8 @@ void ClusterManager::updateAllMembersInfoTaskCallback() { void ClusterManager::updateAllNodeStatuses() { auto& memberList = *ctx.memberList; unsigned long now = millis(); - for (auto& node : memberList) { + for (auto& pair : memberList) { + NodeInfo& node = pair.second; updateNodeStatus(node, now); node.latency = now - node.lastSeen; } @@ -138,13 +148,15 @@ void ClusterManager::updateAllNodeStatuses() { void ClusterManager::removeDeadNodes() { auto& memberList = *ctx.memberList; unsigned long now = millis(); - for (size_t i = 0; i < memberList.size(); ) { - unsigned long diff = now - memberList[i].lastSeen; - if (memberList[i].status == NodeInfo::DEAD && diff > NODE_DEAD_THRESHOLD) { - Serial.printf("[Cluster] Removing node: %s\n", memberList[i].hostname.c_str()); - memberList.erase(memberList.begin() + i); + + // Use iterator to safely remove elements from map + for (auto it = memberList.begin(); it != memberList.end(); ) { + unsigned long diff = now - it->second.lastSeen; + if (it->second.status == NodeInfo::DEAD && diff > NODE_DEAD_THRESHOLD) { + Serial.printf("[Cluster] Removing node: %s\n", it->second.hostname.c_str()); + it = memberList.erase(it); } else { - ++i; + ++it; } } } @@ -156,20 +168,21 @@ void ClusterManager::printMemberList() { return; } Serial.println("[Cluster] Member List:"); - for (const auto& node : memberList) { + for (const auto& pair : memberList) { + const NodeInfo& node = pair.second; Serial.printf(" %s @ %s | Status: %s | last seen: %lu\n", node.hostname.c_str(), node.ip.toString().c_str(), statusToStr(node.status), millis() - node.lastSeen); } } void ClusterManager::updateLocalNodeResources() { - for (auto& node : *ctx.memberList) { - if (node.hostname == ctx.hostname) { - node.resources.freeHeap = ESP.getFreeHeap(); - node.resources.chipId = ESP.getChipId(); - node.resources.sdkVersion = String(ESP.getSdkVersion()); - node.resources.cpuFreqMHz = ESP.getCpuFreqMHz(); - node.resources.flashChipSize = ESP.getFlashChipSize(); - break; - } + auto& memberList = *ctx.memberList; + auto it = memberList.find(ctx.hostname); + if (it != memberList.end()) { + NodeInfo& node = it->second; + node.resources.freeHeap = ESP.getFreeHeap(); + node.resources.chipId = ESP.getChipId(); + node.resources.sdkVersion = String(ESP.getSdkVersion()); + node.resources.cpuFreqMHz = ESP.getCpuFreqMHz(); + node.resources.flashChipSize = ESP.getFlashChipSize(); } } diff --git a/src/ClusterManager.h b/src/ClusterManager.h index 1703083..5953b42 100644 --- a/src/ClusterManager.h +++ b/src/ClusterManager.h @@ -6,6 +6,7 @@ #include #include #include +#include class ClusterManager { public: @@ -16,7 +17,7 @@ public: void updateAllNodeStatuses(); void removeDeadNodes(); void printMemberList(); - const std::vector& getMemberList() const { return *ctx.memberList; } + const std::map& getMemberList() const { return *ctx.memberList; } void fetchNodeInfo(const IPAddress& ip); void updateLocalNodeResources(); void heartbeatTaskCallback(); diff --git a/src/NodeContext..cpp b/src/NodeContext..cpp index 0149778..4d2f14b 100644 --- a/src/NodeContext..cpp +++ b/src/NodeContext..cpp @@ -3,7 +3,7 @@ NodeContext::NodeContext() { scheduler = new Scheduler(); udp = new WiFiUDP(); - memberList = new std::vector(); + memberList = new std::map(); hostname = ""; } diff --git a/src/NodeContext.h b/src/NodeContext.h index 14060a3..d179b49 100644 --- a/src/NodeContext.h +++ b/src/NodeContext.h @@ -1,10 +1,9 @@ #pragma once #include #include -#include +#include #include "NodeInfo.h" #include -#include #include class NodeContext { @@ -15,7 +14,7 @@ public: WiFiUDP* udp; String hostname; IPAddress localIP; - std::vector* memberList; + std::map* memberList; using EventCallback = std::function; std::map> eventRegistry;