27#include <SimoxUtility/algorithm/string.h>
33#include <RobotAPI/components/armem/server/ExampleMemory/aron/ExampleData.aron.generated.h>
34#include <RobotAPI/components/armem/server/ExampleMemory/aron/PythonExampleData.aron.generated.h>
49 defs->topic(debugObserver);
53 p.core._defaultSegmentsStr = simox::alg::join(p.core.defaultCoreSegments,
", ");
54 defs->optional(p.core._defaultSegmentsStr,
55 "core.DefaultSegments",
56 "Core segments to add on start up (just as example).");
60 "If enabled, core segments are added when required by a new provider segment."
61 "This will usually be off for most memory servers.");
63 defs->optional(p.enableRemoteGui,
65 "If true, the remote gui is enabled.");
66 defs->optional(p.showMemoryContent,
67 "p.showMemoryContent",
68 "If true, memory content is shown in remote gui (requires enableRemoteGui=true). "
69 "Can be very slow for high-frequency updates and may cause nullptr errors!");
70 defs->optional(p.showClientStats,
72 "If true, client statistics are shown in the remote gui (requires enableRemoteGui=true).");
73 defs->optional(p.publishToDebugObserver,
74 "p.publishToDebugObserver",
75 "If true, client statistics are published to DebugObserver. "
76 "Channel format: '<ServerName> | Client<N>' with datafields: commits, queries, address, port, pid (if available).");
83 return "ExampleMemory";
94 armem::example::PythonExampleData::ToAronType());
100 {
return this->predictLatest(request); });
104 p.core.defaultCoreSegments = simox::alg::split(p.core._defaultSegmentsStr,
",", trim);
105 p.core._defaultSegmentsStr.clear();
107 for (
const std::string& name : p.core.defaultCoreSegments)
110 c.setMaxHistorySize(100);
117 if (p.enableRemoteGui)
129 logClientStatistics();
139 armem::data::AddSegmentsResult
143 armem::data::AddSegmentsResult result =
144 ReadWritePluginUser::addSegments(input, p.core.addOnUsage);
149 armem::data::CommitResult
153 recordCommit(current);
156 armem::data::CommitResult result = ReadWritePluginUser::commit(
commit);
161 armem::query::data::Result
165 recordQuery(current);
168 return ReadWritePluginUser::query(input);
177 armem::actions::GetActionsOutputSeq
181 Action greeting{
"hi",
"Say hello to " + input[0].id.entityName};
182 Action
failure{
"fail",
"Fail dramatically"};
183 Action nothing{
"null",
"Do nothing, but deeply nested"};
185 SubMenu one{
"one",
"One", {nothing}};
186 SubMenu two{
"two",
"Two", {one}};
187 SubMenu three{
"three",
"Three", {two}};
188 SubMenu four{
"four",
"Four", {three}};
193 SubMenu{
"mut",
"Mutate", {Action{
"copy",
"Copy latest instance"}}}};
195 return {{menu.toIce()}};
198 armem::actions::ExecuteActionOutputSeq
203 ExecuteActionOutputSeq output;
204 for (
const auto& [
id, path] : input)
207 if (path == ActionPath{
"hi"})
210 output.emplace_back(
true,
"");
212 else if (path == ActionPath{
"fail"})
215 output.emplace_back(
false,
"Why would you do that to him?");
217 else if (not path.empty() and path.front() ==
"four" and path.back() ==
"null")
220 ARMARX_INFO <<
"Nested action (path: " << path <<
")";
221 output.emplace_back(
true,
"");
223 else if (path == ActionPath{
"mut",
"copy"})
226 if (instance !=
nullptr)
233 update.entityID = newID;
235 update.instancesData = {instance->data()};
238 newCommit.
add(update);
243 output.emplace_back(
true,
"");
247 output.emplace_back(
false,
"Couldn't duplicate " + memoryID.
str());
267 if (queryResult.success)
270 auto* latest = readMemory.findLatestSnapshot(memID);
271 if (latest !=
nullptr)
273 auto instance = memID.hasInstanceIndex()
274 ? latest->getInstance(memID)
275 : latest->getInstance(latest->getInstanceIndices().at(0));
283 "Could not find entity referenced by MemoryID '" + memID.str() +
"'.";
290 "Could not find entity referenced by MemoryID '" + memID.str() +
"'.";
306 if (p.showMemoryContent)
314 catch (
const std::exception& e)
316 ARMARX_WARNING <<
"Failed to create memory content display: " << e.what();
318 errorBox.
setLabel(
"Memory Content");
319 errorBox.
addChild(
Label(
"Error displaying memory content - see logs"));
325 if (p.showClientStats)
327 tab.clientStatsGroup = createClientStatisticsGroupBox();
328 root.
addChild(tab.clientStatsGroup);
339 if (tab.rebuild.exchange(
false))
347 if (p.showClientStats)
350 if ((now - tab.lastStatsUpdate).toMilliSecondsDouble() > tab.statsUpdateIntervalMs)
353 bool needsRebuild =
false;
355 std::lock_guard lock(clientStatsMutex);
357 if (clientStats.size() != tab.clientLabels.size())
371 updateClientStatisticsLabels();
374 tab.lastStatsUpdate = now;
380 ExampleMemory::createClientStatisticsGroupBox()
384 std::lock_guard lock(clientStatsMutex);
387 statsBox.
setLabel(
"Client Statistics");
390 tab.summaryLabel =
Label(
"No clients connected yet");
391 statsBox.
addChild(tab.summaryLabel);
394 tab.clientLabels.clear();
395 tab.connectionLabels.clear();
398 for (
const auto& [clientId, stats] : clientStats)
402 tab.clientLabels[clientId] = clientLabel;
407 tab.connectionLabels[clientId] = connLabel;
414 updateClientStatisticsLabelsInternal();
420 ExampleMemory::updateClientStatisticsLabels()
422 std::lock_guard lock(clientStatsMutex);
423 updateClientStatisticsLabelsInternal();
427 ExampleMemory::updateClientStatisticsLabelsInternal()
429 using namespace armarx::RemoteGui::Client;
434 if (clientStats.empty())
436 tab.summaryLabel.setText(
"No clients connected yet");
440 std::stringstream summary;
441 summary <<
"Total clients: " << clientStats.size();
442 tab.summaryLabel.setText(summary.str());
447 for (
const auto& [clientId, stats] : clientStats)
450 if (tab.clientLabels.count(clientId) > 0)
452 std::stringstream clientInfo;
453 clientInfo <<
"Client " << clientIndex <<
": "
454 <<
"Commits=" << stats.commitCount <<
", "
455 <<
"Queries=" << stats.queryCount;
456 tab.clientLabels[clientId].setText(clientInfo.str());
460 if (tab.connectionLabels.count(clientId) > 0)
462 std::stringstream connInfo;
463 connInfo <<
" " << stats.remoteAddress;
464 if (stats.remotePort > 0)
466 connInfo <<
":" << stats.remotePort;
468 if (stats.clientPid > 0)
470 connInfo <<
" (PID: " << stats.clientPid <<
")";
473 std::string connStr = connInfo.str();
474 if (connStr.length() > 70)
476 connStr = connStr.substr(0, 67) +
"...";
478 tab.connectionLabels[clientId].setText(connStr);
488 ExampleMemory::extractClientIdentifier(
const Ice::Current& current)
const
493 return current.con->toString();
499 ExampleMemory::formatClientIdentifier(
const std::string& connectionString)
const
506 size_t remotePos = connectionString.find(
"remote address = ");
507 if (remotePos != std::string::npos)
510 std::string remote = connectionString.substr(remotePos + 17);
513 size_t end = remote.find_first_of(
"\r\n");
514 if (end != std::string::npos)
516 remote = remote.substr(0, end);
524 std::string cleaned = connectionString;
526 while ((pos = cleaned.find(
'\n', pos)) != std::string::npos)
528 cleaned.replace(pos, 1,
" | ");
536 ExampleMemory::parseConnectionInfo(
const std::string& connectionString, std::string& address,
int& port)
const
539 std::string fullAddress = formatClientIdentifier(connectionString);
543 size_t lastColon = fullAddress.rfind(
':');
545 if (lastColon != std::string::npos)
548 std::string portStr = fullAddress.substr(lastColon + 1);
551 port = std::stoi(portStr);
552 address = fullAddress.substr(0, lastColon);
554 catch (
const std::exception&)
557 address = fullAddress;
564 address = fullAddress;
570 ExampleMemory::extractClientPid(
const Ice::Current& current)
const
573 auto it = current.ctx.find(
"clientPid");
574 if (it != current.ctx.end())
578 return std::stoi(it->second);
580 catch (
const std::exception&)
589 ExampleMemory::getClientIndex(
const std::string& clientId)
594 auto it = clientIndices.find(clientId);
595 if (it != clientIndices.end())
601 int index = nextClientIndex++;
602 clientIndices[clientId] =
index;
607 ExampleMemory::recordCommit(
const Ice::Current& current)
609 std::lock_guard lock(clientStatsMutex);
610 std::string clientId = extractClientIdentifier(current);
612 auto& stats = clientStats[clientId];
613 bool isNewClient = stats.connectionString.empty();
617 stats.connectionString = clientId;
621 parseConnectionInfo(clientId, stats.remoteAddress, stats.remotePort);
624 stats.clientPid = extractClientPid(current);
626 ARMARX_INFO <<
"New client connected: " << clientId;
633 if (p.publishToDebugObserver && debugObserver)
635 int clientIndex = getClientIndex(clientId);
636 std::string channel =
getName() +
" | Client" + std::to_string(clientIndex);
638 values[
"commits"] =
new armarx::Variant(
static_cast<int>(stats.commitCount));
639 values[
"queries"] =
new armarx::Variant(
static_cast<int>(stats.queryCount));
640 values[
"address"] =
new armarx::Variant(stats.remoteAddress);
641 values[
"port"] =
new armarx::Variant(stats.remotePort);
642 if (stats.clientPid > 0)
644 values[
"pid"] =
new armarx::Variant(stats.clientPid);
646 debugObserver->setDebugChannel(channel, values);
651 ExampleMemory::recordQuery(
const Ice::Current& current)
653 std::lock_guard lock(clientStatsMutex);
654 std::string clientId = extractClientIdentifier(current);
656 auto& stats = clientStats[clientId];
657 bool isNewClient = stats.connectionString.empty();
661 stats.connectionString = clientId;
665 parseConnectionInfo(clientId, stats.remoteAddress, stats.remotePort);
668 stats.clientPid = extractClientPid(current);
670 ARMARX_INFO <<
"New client connected: " << clientId;
677 if (p.publishToDebugObserver && debugObserver)
679 int clientIndex = getClientIndex(clientId);
680 std::string channel =
getName() +
" | Client" + std::to_string(clientIndex);
682 values[
"commits"] =
new armarx::Variant(
static_cast<int>(stats.commitCount));
683 values[
"queries"] =
new armarx::Variant(
static_cast<int>(stats.queryCount));
684 values[
"address"] =
new armarx::Variant(stats.remoteAddress);
685 values[
"port"] =
new armarx::Variant(stats.remotePort);
686 if (stats.clientPid > 0)
688 values[
"pid"] =
new armarx::Variant(stats.clientPid);
690 debugObserver->setDebugChannel(channel, values);
695 ExampleMemory::logClientStatistics()
const
697 std::lock_guard lock(clientStatsMutex);
699 if (clientStats.empty())
708 for (
const auto& [clientId, stats] : clientStats)
711 << (stats.remotePort > 0 ?
":" + std::to_string(stats.remotePort) :
"");
712 if (stats.clientPid > 0)
714 ARMARX_IMPORTANT <<
" PID: " << stats.clientPid;
720 if (stats.commitCount > 0)
722 ARMARX_IMPORTANT <<
" Last commit: " << stats.lastCommit.toDateTimeString();
724 if (stats.queryCount > 0)
726 ARMARX_IMPORTANT <<
" Last query: " << stats.lastQuery.toDateTimeString();
730 ARMARX_INFO <<
" Raw connection: " << stats.connectionString;
int Label(int n[], int size, int *curLabel, MiscLib::Vector< std::pair< int, size_t > > *labels)
Default component property definition container.
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
void onInitComponent() override
Pure virtual hook for the subclass.
armem::query::data::Result query(const armem::query::data::Input &input, const Ice::Current &=Ice::emptyCurrent) override
void onDisconnectComponent() override
Hook for subclass.
void RemoteGui_update() override
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
armem::data::CommitResult commit(const armem::data::Commit &commit, const Ice::Current &=Ice::emptyCurrent) override
armem::actions::ExecuteActionOutputSeq executeActions(const armem::actions::ExecuteActionInputSeq &input) override
armem::actions::GetActionsOutputSeq getActions(const armem::actions::GetActionsInputSeq &input) override
void onConnectComponent() override
Pure virtual hook for the subclass.
void onExitComponent() override
Hook for subclass.
armem::data::AddSegmentsResult addSegments(const armem::data::AddSegmentsInput &input, const Ice::Current &) override
void createRemoteGuiTab()
std::string getDefaultName() const override
std::string getName() const
Retrieve name of object.
MemoryID withProviderSegmentName(const std::string &name) const
MemoryID getCoreSegmentID() const
std::string str(bool escapeDelimiters=true) const
Get a string representation of this memory ID.
MemoryID withEntityName(const std::string &name) const
std::string providerSegmentName
void latestEntitySnapshot(const MemoryID &entityID)
QueryInput buildQueryInput() const
Utility for memory Remote Guis.
GroupBox makeGroupBox(const armem::wm::Memory &memory) const
void setMemoryName(const std::string &memoryName)
server::wm::Memory & workingMemory()
CoreSegment & addCoreSegment(const std::string &name, Args... args)
void addPredictor(const PredictionEngine &engine, Predictor &&predictor)
#define ARMARX_INFO
The normal logging level.
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
query::Builder QueryBuilder
armarx::core::time::DateTime Time
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::map< std::string, VariantBasePtr > StringVariantBaseMap
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
void fromIce(const std::map< IceKeyT, IceValueT > &iceMap, boost::container::flat_map< CppKeyT, CppValueT > &cppMap)
void toIce(std::map< IceKeyT, IceValueT > &iceMap, const boost::container::flat_map< CppKeyT, CppValueT > &cppMap)
void RemoteGui_startRunningTask()
void RemoteGui_createTab(std::string const &name, RemoteGui::Client::Widget const &rootWidget, RemoteGui::Client::Tab *tab)
void setLabel(std::string const &text)
A bundle of updates to be sent to the memory.
An update of an entity for a specific point in time.
armem::MemoryID snapshotID
aron::data::DictPtr prediction
armem::MemoryID snapshotID
const auto * findLatestInstance(int instanceIndex=0) const
Find the latest entity instance.