MemoryToIceAdapter.cpp
Go to the documentation of this file.
2
3#include <iostream>
4#include <list>
5#include <sstream>
6#include <thread>
7
8#include <IceUtil/Time.h>
9
16
27
29#include "query_proc/wm/wm.h"
30
32{
33 namespace
34 {
35 /**
36 * @brief Extract metadata from WM structure for given snapshots
37 *
38 * This function is called OUTSIDE of any WM locks to avoid deadlocks.
39 * It extracts only the metadata needed for async conversion (aron types).
40 *
41 * @param structure The WM structure to extract from
42 * @param snapshots The snapshots to extract metadata for
43 * @return Vector of segment metadata
44 */
45 std::vector<ltm::detail::mixin::PendingConversion::SegmentMetadata>
46 extractSegmentMetadata(const wm::Memory& structure,
47 const std::vector<wm::EntitySnapshot>& snapshots)
48 {
49 std::vector<ltm::detail::mixin::PendingConversion::SegmentMetadata> metadata;
50
51 // Build unique set of core/provider segments from snapshots
52 std::map<std::string, std::set<std::string>> segmentMap; // core -> set of providers
53 for (const auto& snapshot : snapshots)
54 {
55 segmentMap[snapshot.id().coreSegmentName].insert(snapshot.id().providerSegmentName);
56 }
57
58 // Extract metadata for each segment
59 for (const auto& [coreSegmentName, providerNames] : segmentMap)
60 {
61 auto* coreStructure = structure.findCoreSegment(coreSegmentName);
62 if (!coreStructure)
63 {
64 ARMARX_WARNING << "Core segment not found in structure: " << coreSegmentName;
65 continue;
66 }
67
69 meta.coreSegmentName = coreSegmentName;
70 meta.coreSegmentAronType = coreStructure->aronType();
71
72 for (const auto& providerName : providerNames)
73 {
74 auto* providerStructure = coreStructure->findProviderSegment(providerName);
75 if (providerStructure)
76 {
77 meta.providerSegmentAronTypes[providerName] = providerStructure->aronType();
78 }
79 }
80
81 metadata.push_back(std::move(meta));
82 }
83
84 return metadata;
85 }
86 } // anonymous namespace
87
88
92 {
93 // Both are dereferenced unconditionally on the commit path (e.g. `longtermMemory->
94 // isRecording()` below, `*workingMemory` in extractSegmentMetadata). Fail here, where
95 // the caller is still on the stack, rather than in a servant thread later.
98 }
99
100 void
101 MemoryToIceAdapter::setMemoryListener(client::MemoryListenerInterfacePrx memoryListener)
102 {
103 this->memoryListenerTopic = memoryListener;
104 }
105
106 // WRITING
107 data::AddSegmentResult
108 MemoryToIceAdapter::addSegment(const data::AddSegmentInput& input, bool addCoreSegments)
109 {
112
113 ARMARX_DEBUG << "Adding segment using MemoryToIceAdapter";
114
115 data::AddSegmentResult output;
116
117 server::wm::CoreSegment* coreSegment = nullptr;
118 try
119 {
120 coreSegment = &workingMemory->getCoreSegment(input.coreSegmentName);
121 }
122 catch (const armem::error::MissingEntry& e)
123 {
124 if (addCoreSegments)
125 {
126 coreSegment = &workingMemory->addCoreSegment(input.coreSegmentName);
127 }
128 else
129 {
130 output.success = false;
131 output.errorMessage = e.what();
132 return output;
133 }
134 }
135 ARMARX_CHECK_NOT_NULL(coreSegment);
136
137 if (input.providerSegmentName.size() > 0)
138 {
139 coreSegment->doLockedExclusive(
140 [&coreSegment, &input]()
141 {
142 try
143 {
144 coreSegment->addProviderSegment(input.providerSegmentName);
145 }
147 {
148 // This is ok.
149 if (input.clearWhenExists)
150 {
152 coreSegment->getProviderSegment(input.providerSegmentName);
153 provider.clear();
154 }
155 }
156 });
157 }
158
159 armem::MemoryID segmentID;
160 segmentID.memoryName = workingMemory->name();
161 segmentID.coreSegmentName = input.coreSegmentName;
162 segmentID.providerSegmentName = input.providerSegmentName;
163
164 output.success = true;
165 output.segmentID = segmentID.str();
166 return output;
167 }
168
169 data::AddSegmentsResult
170 MemoryToIceAdapter::addSegments(const data::AddSegmentsInput& input, bool addCoreSegments)
171 {
174
175 data::AddSegmentsResult output;
176 for (const auto& i : input)
177 {
178 output.push_back(addSegment(i, addCoreSegments));
179 }
180 return output;
181 }
182
183 data::CommitResult
184 MemoryToIceAdapter::commit(const data::Commit& commitIce, Time timeArrived)
185 {
188 auto handleException = [](const std::string& what)
189 {
190 data::CommitResult result;
191 data::EntityUpdateResult& r = result.results.emplace_back();
192 r.success = false;
193 r.errorMessage = what;
194 return result;
195 };
196
198 try
199 {
200 ::armarx::armem::fromIce(commitIce, commit, timeArrived);
201 }
202 catch (const Ice::Exception& e)
203 {
204 return handleException(e.what());
205 }
206 catch (const std::exception& e)
207 {
208 // Deliberately the whole std::exception family. The clause this replaces named
209 // aron::error::AronNotValidException, which nothing in the tree ever throws and which
210 // is a sibling of ValueNotValidException rather than a base of it -- so a malformed
211 // payload escaped the servant and reached the client as an Ice::UnknownException with
212 // the diagnostic gone. What conversion really throws is ValueNotValidException, or a
213 // std::out_of_range from the descriptor lookup in Aron2Descriptor() when the payload
214 // is not one of the known dto types. Neither has anything in common but std::exception.
215 return handleException(e.what());
216 }
217
218 armem::CommitResult result = this->commit(commit);
219 data::CommitResult resultIce;
220 toIce(resultIce, result);
221
222 return resultIce;
223 }
224
225 data::CommitResult
226 MemoryToIceAdapter::commit(const data::Commit& commitIce)
227 {
229 return commit(commitIce, armem::Time::Now());
230 }
231
234 {
236 return this->_commit(commit, false);
237 }
238
239 data::CommitResult
240 MemoryToIceAdapter::commitLocking(const data::Commit& commitIce, Time timeArrived)
241 {
244 auto handleException = [](const std::string& what)
245 {
246 data::CommitResult result;
247 data::EntityUpdateResult& r = result.results.emplace_back();
248 r.success = false;
249 r.errorMessage = what;
250 return result;
251 };
252
254 try
255 {
256 ::armarx::armem::fromIce(commitIce, commit, timeArrived);
257 }
258 catch (const Ice::Exception& e)
259 {
260 return handleException(e.what());
261 }
262 catch (const std::exception& e)
263 {
264 // Deliberately the whole std::exception family. The clause this replaces named
265 // aron::error::AronNotValidException, which nothing in the tree ever throws and which
266 // is a sibling of ValueNotValidException rather than a base of it -- so a malformed
267 // payload escaped the servant and reached the client as an Ice::UnknownException with
268 // the diagnostic gone. What conversion really throws is ValueNotValidException, or a
269 // std::out_of_range from the descriptor lookup in Aron2Descriptor() when the payload
270 // is not one of the known dto types. Neither has anything in common but std::exception.
271 return handleException(e.what());
272 }
273
274 armem::CommitResult result = this->commitLocking(commit);
275 data::CommitResult resultIce;
276 toIce(resultIce, result);
277
278 return resultIce;
279 }
280
281 data::CommitResult
282 MemoryToIceAdapter::commitLocking(const data::Commit& commitIce)
283 {
285 return commitLocking(commitIce, armem::Time::Now());
286 }
287
290 {
292 return this->_commit(commit, true);
293 }
294
296 MemoryToIceAdapter::_commit(const armem::Commit& commit, bool locking)
297 {
299 TIMING_START(MemoryToIceAdapter_commit);
300
301 // Start timing for this commit
302 auto commitStartTime = std::chrono::steady_clock::now();
303
304 IceUtil::Time startTime;
305 auto debugObserver = longtermMemory->getDebugObserver();
306 if (debugObserver)
307 {
308 startTime = IceUtil::Time::now();
309 }
310
311 // Update statistics - increment commit count and entity update count
312 statistics.totalCommitCount.fetch_add(1, std::memory_order_relaxed);
313 statistics.totalEntityUpdates.fetch_add(commit.updates.size(), std::memory_order_relaxed);
314
315 std::vector<data::MemoryID> updatedIDs;
316 const bool publishUpdates = bool(memoryListenerTopic);
317
318 CommitResult commitResult;
319 for (const EntityUpdate& update : commit.updates)
320 {
321 EntityUpdateResult& result = commitResult.results.emplace_back();
322 try
323 {
324 IceUtil::Time updateStartTime;
325 if (debugObserver)
326 {
327 updateStartTime = IceUtil::Time::now();
328 }
329
330 auto updateResult =
331 locking ? workingMemory->updateLocking(update) : workingMemory->update(update);
332
333 result.success = true;
334 result.snapshotID = updateResult.id;
335 result.arrivedTime = update.arrivedTime;
336
337 // Track successful update
338 statistics.successfulUpdates.fetch_add(1, std::memory_order_relaxed);
339
340 if (debugObserver)
341 {
342 IceUtil::Time updateEndTime = IceUtil::Time::now();
343 IceUtil::Time updateElapsed = updateEndTime - updateStartTime;
344 float updateElapsedMs = updateElapsed.toMilliSecondsDouble();
345
346 std::string channelName = workingMemory->name() + "Memory";
347 debugObserver->setDebugChannel(
348 channelName,
349 {
350 {"Memory | Commit | updateResult step [ms]", new armarx::Variant(updateElapsedMs)},
351 });
352 }
353
354 for (const auto& snapshot : updateResult.removedSnapshots)
355 {
356 ARMARX_DEBUG << "The id " << snapshot.id() << " was removed from wm";
357 }
358
359 // Consolidate to ltm(s) if recording mode is CLONE_WM
360 if (longtermMemory->isRecording() &&
361 longtermMemory->getRecordingMode() ==
363 {
364 // PERFORMANCE OPTIMIZATION: Defer conversion to async thread
365 // Previously, toMemory() was called synchronously here, blocking the commit path
366 // Now we pass snapshots directly and conversion happens in async storage thread
367 //
368 // DEADLOCK FIX: Extract metadata BEFORE calling storeSnapshotsAsync to ensure
369 // we don't access WM structure while any WM locks might be held
370 IceUtil::Time storeStartTime;
371 if (debugObserver)
372 {
373 storeStartTime = IceUtil::Time::now();
374 }
375
376 // Extract metadata from WM structure (done outside any locks)
377 auto segmentMetadata = extractSegmentMetadata(*workingMemory, updateResult.updatedSnapshots);
378
379 // Store snapshots with deferred conversion (happens in async thread)
380 // This no longer accesses WM structure - all metadata is pre-extracted
381 longtermMemory->storeSnapshotsAsync(
382 longtermMemory->name(),
383 updateResult.updatedSnapshots,
384 segmentMetadata);
385
386 if (debugObserver)
387 {
388 IceUtil::Time storeEndTime = IceUtil::Time::now();
389 IceUtil::Time storeElapsed = storeEndTime - storeStartTime;
390 float storeElapsedMs = storeElapsed.toMilliSecondsDouble();
391
392 std::string channelName = workingMemory->name() + "Memory";
393 debugObserver->setDebugChannel(
394 channelName,
395 {
396 {"Memory | Commit | LTM enqueue (CONSOLIDATE_ALL) [ms]", new armarx::Variant(storeElapsedMs)},
397 });
398 }
399 }
400
401
402 // Consolidate to ltm(s) if recording mode is CONSOLIDATE_REMOVED
403 if (longtermMemory->isRecording() &&
404 longtermMemory->getRecordingMode() ==
406 {
407 // PERFORMANCE OPTIMIZATION: Defer conversion to async thread
408 // Previously, toMemory() was called synchronously here, blocking the commit path
409 // Now we pass snapshots directly and conversion happens in async storage thread
410 //
411 // DEADLOCK FIX: Extract metadata BEFORE calling storeSnapshotsAsync to ensure
412 // we don't access WM structure while any WM locks might be held
413 IceUtil::Time storeStartTime;
414 if (debugObserver)
415 {
416 storeStartTime = IceUtil::Time::now();
417 }
418
419 // Extract metadata from WM structure (done outside any locks)
420 auto segmentMetadata = extractSegmentMetadata(*workingMemory, updateResult.removedSnapshots);
421
422 // Store snapshots with deferred conversion (happens in async thread)
423 // This no longer accesses WM structure - all metadata is pre-extracted
424 longtermMemory->storeSnapshotsAsync(
425 longtermMemory->name(),
426 updateResult.removedSnapshots,
427 segmentMetadata);
428
429 if (debugObserver)
430 {
431 IceUtil::Time storeEndTime = IceUtil::Time::now();
432 IceUtil::Time storeElapsed = storeEndTime - storeStartTime;
433 float storeElapsedMs = storeElapsed.toMilliSecondsDouble();
434
435 std::string channelName = workingMemory->name() + "Memory";
436 debugObserver->setDebugChannel(
437 channelName,
438 {
439 {"Memory | Commit | LTM enqueue (CONSOLIDATE_REMOVED) [ms]", new armarx::Variant(storeElapsedMs)},
440 });
441 }
442 }
443
444 if (longtermMemory->isRecording() &&
445 longtermMemory->getRecordingMode() ==
447 {
448 ARMARX_WARNING << deactivateSpam() << "THIS IS NOT IMPLEMENTED YET!!!";
449 }
450
451 if (publishUpdates)
452 {
453 data::MemoryID& id = updatedIDs.emplace_back();
454 toIce(id, result.snapshotID);
455 }
456 }
457 catch (const error::ArMemError& e)
458 {
459 result.success = false;
460 result.errorMessage = e.what();
461 statistics.failedUpdates.fetch_add(1, std::memory_order_relaxed);
462 }
463 catch (const aron::error::AronException& e)
464 {
465 result.success = false;
466 result.errorMessage = e.what();
467 statistics.failedUpdates.fetch_add(1, std::memory_order_relaxed);
468 }
469 catch (const Ice::Exception& e)
470 {
471 result.success = false;
472 result.errorMessage = e.what();
473 statistics.failedUpdates.fetch_add(1, std::memory_order_relaxed);
474 }
475 catch (...)
476 {
477 // The three handlers above all report the failure; this one used to only log, so
478 // an unrecognised exception left result.success at the true set after the working
479 // memory update and the client was told the commit had gone through. Nothing here
480 // is specific to LTM consolidation either -- the try covers the WM update too.
481 result.success = false;
483 ARMARX_ERROR << "Unhandled exception during commit: " << result.errorMessage;
484 statistics.failedUpdates.fetch_add(1, std::memory_order_relaxed);
485 }
486 }
487
488 if (publishUpdates)
489 {
490 memoryListenerTopic->memoryUpdated(updatedIDs);
491 }
492
493 // Calculate commit blocking time
494 auto commitEndTime = std::chrono::steady_clock::now();
495 double commitBlockingTimeMs = std::chrono::duration<double, std::milli>(commitEndTime - commitStartTime).count();
496
497 // Update blocking time statistics (using compare-exchange for max)
498 {
499 double currentTotal = statistics.totalCommitBlockingTimeMs.load(std::memory_order_relaxed);
500 statistics.totalCommitBlockingTimeMs.store(currentTotal + commitBlockingTimeMs, std::memory_order_relaxed);
501
502 double currentMax = statistics.maxCommitBlockingTimeMs.load(std::memory_order_relaxed);
503 while (commitBlockingTimeMs > currentMax)
504 {
505 if (statistics.maxCommitBlockingTimeMs.compare_exchange_weak(currentMax, commitBlockingTimeMs, std::memory_order_relaxed))
506 {
507 break;
508 }
509 }
510 }
511
512 if (debugObserver)
513 {
514 IceUtil::Time endTime = IceUtil::Time::now();
515 IceUtil::Time elapsed = endTime - startTime;
516 float elapsedMs = elapsed.toMilliSecondsDouble();
517
518 // Calculate current rates
519 auto [commitsPerSec, queriesPerSec] = statistics.updateRates();
520
521 const size_t queueSize = longtermMemory->getAsyncQueueSize();
522
523 std::string channelName = workingMemory->name() + "Memory";
524 debugObserver->setDebugChannel(
525 channelName,
526 {
527 // Timing metrics
528 {"Memory | Commit | t blocked [ms]", new armarx::Variant(elapsedMs)},
529 {"Memory | Commit | max blocked [ms]", new armarx::Variant(static_cast<float>(statistics.maxCommitBlockingTimeMs.load()))},
530 // Rate metrics
531 {"Memory | writes/sec", new armarx::Variant(static_cast<float>(commitsPerSec))},
532 {"Memory | reads/sec", new armarx::Variant(static_cast<float>(queriesPerSec))},
533 // Count metrics
534 {"Memory | total commits", new armarx::Variant(static_cast<int>(statistics.totalCommitCount.load()))},
535 {"Memory | total entity updates", new armarx::Variant(static_cast<int>(statistics.totalEntityUpdates.load()))},
536 {"Memory | successful updates", new armarx::Variant(static_cast<int>(statistics.successfulUpdates.load()))},
537 {"Memory | failed updates", new armarx::Variant(static_cast<int>(statistics.failedUpdates.load()))},
538 // Queue metrics
539 {"Memory | LTM async queue size", new armarx::Variant(static_cast<int>(queueSize))},
540 });
541 }
542
543 TIMING_END_STREAM(MemoryToIceAdapter_commit, ARMARX_DEBUG);
544 return commitResult;
545 }
546
547 // READING
548 armem::query::data::Result
549 MemoryToIceAdapter::query(const armem::query::data::Input& input)
550 {
554
555 // Start timing for this query
556 auto queryStartTime = std::chrono::steady_clock::now();
557
558 // Update statistics - increment query count
559 statistics.totalQueryCount.fetch_add(1, std::memory_order_relaxed);
560
561 // Core segment processors will aquire the core segment locks.
563 armem::query::boolToDataMode(input.withData));
564 armem::wm::Memory wmResult = wmServerProcessor.process(input, *workingMemory);
565
566 armem::query::data::Result result;
567
568
569 result.memory = armarx::toIce<data::MemoryPtr>(wmResult);
570
571 result.success = true;
572 if (result.memory->coreSegments.size() == 0)
573 {
574 ARMARX_DEBUG << "No data in memory found after query.";
575 }
576
577 // Calculate query blocking time
578 auto queryEndTime = std::chrono::steady_clock::now();
579 double queryBlockingTimeMs = std::chrono::duration<double, std::milli>(queryEndTime - queryStartTime).count();
580
581 // Update blocking time statistics (using compare-exchange for max)
582 {
583 double currentTotal = statistics.totalQueryBlockingTimeMs.load(std::memory_order_relaxed);
584 statistics.totalQueryBlockingTimeMs.store(currentTotal + queryBlockingTimeMs, std::memory_order_relaxed);
585
586 double currentMax = statistics.maxQueryBlockingTimeMs.load(std::memory_order_relaxed);
587 while (queryBlockingTimeMs > currentMax)
588 {
589 if (statistics.maxQueryBlockingTimeMs.compare_exchange_weak(currentMax, queryBlockingTimeMs, std::memory_order_relaxed))
590 {
591 break;
592 }
593 }
594 }
595
596 // Report to debug observer if available
597 auto debugObserver = longtermMemory->getDebugObserver();
598 if (debugObserver)
599 {
600 std::string channelName = workingMemory->name() + "Memory";
601 debugObserver->setDebugChannel(
602 channelName,
603 {
604 {"Memory | Query | t blocked [ms]", new armarx::Variant(static_cast<float>(queryBlockingTimeMs))},
605 {"Memory | Query | max blocked [ms]", new armarx::Variant(static_cast<float>(statistics.maxQueryBlockingTimeMs.load()))},
606 {"Memory | total queries", new armarx::Variant(static_cast<int>(statistics.totalQueryCount.load()))},
607 });
608 }
609
610 return result;
611 }
612
613 armem::query::data::Result
614 MemoryToIceAdapter::queryLTM(const armem::query::data::Input& input, bool storeIntoWM)
615 {
617
619 armem::wm::Memory ltmResult = ltmProcessor.process(input, *longtermMemory);
620
621 // convert memory ==> meaning resolving references
622 // upon query, the LTM only returns a structure of the data (memory without data)
623 if (input.withData)
624 {
625 longtermMemory->resolve(ltmResult);
626 }
627
628 if (longtermMemory->isRecording() || storeIntoWM)
629 {
630 this->commit(toCommit(ltmResult));
631
632 // mark removed entries of wm in viewer
633 // TODO
634 }
635
636 armem::query::data::Result result;
637
638 result.memory = armarx::toIce<data::MemoryPtr>(ltmResult);
639
640 result.success = true;
641 if (result.memory->coreSegments.size() == 0)
642 {
643 ARMARX_DEBUG << "No data in ltm found after query.";
644 }
645
646 return result;
647 }
648
651 {
653
654 return client::QueryResult::fromIce(query(input.toIce()));
655 }
656
657 armem::structure::data::GetServerStructureResult
659 {
663
664 armem::structure::data::GetServerStructureResult ret;
665 ret.success = true;
666
667 wm::Memory structure;
668 structure.id() = workingMemory->id();
669
670 // Get all info from the WM
672 builder.all();
673
674 auto query_result = this->query(builder.buildQueryInput());
675 if (query_result.success)
676 {
677 structure.append(query_result.memory);
678 }
679
680 // Get all info from the LTM
681 structure.append(longtermMemory->loadAllReferences());
682
683 ret.serverStructure = armarx::toIce<data::MemoryPtr>(structure);
684
685 return ret;
686 }
687
690 {
692
693 ARMARX_INFO << "Reloading of all core segments from LTM into WM triggered";
694
695 int maxAmountOfSnapshots = this->longtermMemory->p.maxAmountOfSnapshotsLoaded;
696 //create WM and load latest references
698 this->longtermMemory->loadLatestNReferences(maxAmountOfSnapshots, m);
699
700 //construct a commit of the loaded data and commit it to the working memory
701 auto com = armem::toCommit(m);
702 auto res = this->commit(com);
703
704 //the CommitResult contains some information which might be helpful:
705 return res;
706 }
707
709 MemoryToIceAdapter::reloadCoreSegmentsFromLTM(std::list<std::string>& coreSegmentNames)
710 {
712
713 ARMARX_INFO << "Reloading of specific core segments from LTM into WM triggered";
714
715 std::ostringstream namesStr;
716 for (auto it = coreSegmentNames.begin(); it != coreSegmentNames.end(); ++it)
717 {
718 if (it != coreSegmentNames.begin())
719 namesStr << ", "; // Add comma before every element except the first
720 namesStr << *it;
721 }
722
723 ARMARX_INFO << "Loading core segments=" << namesStr.str();
724
725
726 int maxAmountOfSnapshots = this->longtermMemory->p.maxAmountOfSnapshotsLoaded;
727 //create WM and load latest references
729 this->longtermMemory->loadLatestNReferences(maxAmountOfSnapshots, m, coreSegmentNames);
730
731 //construct a commit of the loaded data and commit it to the working memory
732 auto com = armem::toCommit(m);
733 auto res = this->commit(com);
734
735 //the CommitResult contains some information which might be helpful:
736 return res;
737 }
738
741 {
742 ARMARX_INFO << "Reloading of coresegment defined in 'loadedCoreSegments' from LTM into WM "
743 "on startup triggered";
744
745 auto coreNames = this->longtermMemory->p.coreSegmentsToLoad;
746
747 ARMARX_INFO << "Loading core segments=" << coreNames
748 << " defined in property 'loadedCoreSegments'";
749
750 //convert string to list of names:
751 std::list<std::string> names;
752 std::stringstream ss(coreNames);
753 std::string item;
754
755 while (std::getline(ss, item, ','))
756 {
757 names.push_back(item);
758 }
759
760 return reloadCoreSegmentsFromLTM(names);
761 }
762
763 // WM LOADING FROM LTM
766 {
768
769 ARMARX_INFO << "Reloading of data from LTM into WM on startup triggered";
770
771 if (this->longtermMemory->p.importOnStartUp)
772 {
774 }
775 else
776 {
777 ARMARX_INFO << "Not loading initial data from LTM due to importOnStartup being "
778 << this->longtermMemory->p.importOnStartUp;
780 return r;
781 }
782 }
783
784 // LTM STORING AND RECORDING
785 dto::DirectlyStoreResult
786 MemoryToIceAdapter::directlyStore(const dto::DirectlyStoreInput& directlStoreInput)
787 {
790
791 dto::DirectlyStoreResult output;
792 output.success = true;
793
794 armem::wm::Memory m = armarx::fromIce<armem::wm::Memory>(directlStoreInput.memory);
795 longtermMemory->directlyStore(m);
796
797 return output;
798 }
799
800 dto::StartRecordResult
801 MemoryToIceAdapter::startRecord(const dto::StartRecordInput& startRecordInput)
802 {
805 ARMARX_IMPORTANT << "Enabling the recording of memory " << longtermMemory->id().str();
806 longtermMemory->startRecording();
807
808 dto::StartRecordResult ret;
809 ret.success = true;
810
811 return ret;
812 }
813
814 dto::StopRecordResult
816 {
820 ARMARX_IMPORTANT << "Disabling the recording of memory " << longtermMemory->id().str();
821
822 if (longtermMemory->p.storeOnStop)
823 { //if true this means when stopping LTM recording leftover snapshots are transferred to WM using the simulated consolidation
824 ARMARX_INFO << "Starting to save left-over WM data into LTM";
825 longtermMemory->directlyStore(*workingMemory, true);
826 ARMARX_INFO << "Stored leftover WM data into LTM";
827 }
828 else
829 {
830 ARMARX_INFO << "Not storing WM data into LTM on stop, because storeOnStop is "
831 << longtermMemory->p.storeOnStop;
832 }
833
834 // Stop the recording (stops the periodic buffer task)
835 longtermMemory->stopRecording();
836
837 // Use the new async flush mechanism to ensure all queued data is written
838 // This runs in a separate thread to avoid blocking the caller
839 auto ltm = longtermMemory;
840 std::thread flushThread(
841 [ltm]()
842 {
843 ARMARX_INFO << "Flushing async storage queue...";
844 bool success = ltm->flushAsyncStorage(0); // 0 = wait indefinitely
845 if (success)
846 {
847 ARMARX_INFO << "All pending data stored successfully";
848 }
849 else
850 {
851 ARMARX_WARNING << "Flush completed with timeout or errors";
852 }
853 ltm->bufferFinished();
854 });
855 flushThread.detach();
856
858 << "Stopped all LTM recordings, flushing async queue in background. "
859 << "Please wait with stopping the component until all files are written";
860
861 dto::StopRecordResult ret;
862 ret.success = true;
863
864 return ret;
865 }
866
867 dto::RecordStatusResult
869 {
870 dto::RecordStatusResult ret;
871 ret.success = true;
872
873 long savedSnapshots;
874 long totalSnapshots;
875
876 ARMARX_DEBUG << "Get record status";
877
878 longtermMemory->forEachCoreSegment(
879 [&savedSnapshots, &totalSnapshots](const auto& c)
880 {
881 c.forEachProviderSegment(
882 [&savedSnapshots, &totalSnapshots](const auto& p)
883 {
884 p.forEachEntity(
885 [&savedSnapshots, &totalSnapshots](const auto& e)
886 {
887 savedSnapshots += e.getStatistics().recordedSnapshots;
888
889 e.forEachSnapshot([&totalSnapshots](const auto&)
890 { totalSnapshots++; });
891 });
892 });
893 });
894
895 ret.status.savedSnapshots = savedSnapshots;
896 ret.status.totalSnapshots = totalSnapshots;
897
898 return ret;
899 }
900
901 // PREDICTION
902 prediction::data::PredictionResultSeq
903 MemoryToIceAdapter::predict(prediction::data::PredictionRequestSeq requests)
904 {
905 auto res = workingMemory->dispatchPredictions(
906 armarx::fromIce<std::vector<PredictionRequest>>(requests));
908 }
909
910 prediction::data::EngineSupportMap
912 {
913 prediction::data::EngineSupportMap result;
914 armarx::toIce(result, workingMemory->getAllPredictionEngines());
915
916 // Uncomment once LTM also supports prediction engines.
917
918 /*prediction::data::EngineSupportMap ltmMap;
919 armarx::toIce(ltmMap, longtermMemory->getAllPredictionEngines());
920 for (const auto& [memoryID, engines] : ltmMap)
921 {
922 auto entryIter = result.find(memoryID);
923 if (entryIter == result.end())
924 {
925 result.emplace(memoryID, engines);
926 }
927 else
928 {
929 // Merge LTM-supported engines with WM-supported engines, removing duplicates
930 std::set<prediction::data::PredictionEngine> engineSet;
931 engineSet.insert(entryIter->second.begin(), entryIter->second.end());
932 engineSet.insert(engines.begin(), engines.end());
933 entryIter->second.assign(engineSet.begin(), engineSet.end());
934 }
935 }*/
936
937 return result;
938 }
939
940 void
942 {
943 auto debugObserver = longtermMemory->getDebugObserver();
944 if (!debugObserver)
945 {
946 return;
947 }
948
949 // Calculate current rates
950 auto [commitsPerSec, queriesPerSec] = statistics.updateRates();
951
952 // Queue size and async stats from the LTM.
953 const size_t queueSize = longtermMemory->getAsyncQueueSize();
954 const size_t numThreadsProcessing = longtermMemory->getNumThreadsProcessing();
955 const auto& asyncStats = longtermMemory->getAsyncStorageStatistics();
956 const uint64_t asyncItemsEnqueued =
957 asyncStats.totalItemsEnqueued.load(std::memory_order_relaxed);
958 const uint64_t asyncItemsProcessed =
959 asyncStats.totalItemsProcessed.load(std::memory_order_relaxed);
960 const uint64_t asyncSnapshotsStored =
961 asyncStats.totalSnapshotsStored.load(std::memory_order_relaxed);
962 // Not snapshotsDropped on its own: that counter covers only the error path, so while
963 // the queue was shedding load under backpressure this channel read zero throughout.
964 const uint64_t asyncSnapshotsDropped = asyncStats.totalSnapshotsDropped();
965 const uint64_t asyncSnapshotsDroppedBackpressure =
966 asyncStats.snapshotsDroppedBackpressure.load(std::memory_order_relaxed);
967 const uint64_t asyncBackpressureEvents =
968 asyncStats.backpressureEvents.load(std::memory_order_relaxed);
969 const double asyncAvgStorageTimeMs = asyncStats.getAvgStorageTimeMs();
970 const double asyncMaxStorageTimeMs = asyncStats.getMaxStorageTimeMs();
971
972 // Calculate average blocking times
973 uint64_t commitCount = statistics.totalCommitCount.load(std::memory_order_relaxed);
974 uint64_t queryCount = statistics.totalQueryCount.load(std::memory_order_relaxed);
975 double avgCommitBlockingMs = commitCount > 0
976 ? statistics.totalCommitBlockingTimeMs.load(std::memory_order_relaxed) / commitCount
977 : 0.0;
978 double avgQueryBlockingMs = queryCount > 0
979 ? statistics.totalQueryBlockingTimeMs.load(std::memory_order_relaxed) / queryCount
980 : 0.0;
981
982 std::string channelName = workingMemory->name() + "Memory";
983 debugObserver->setDebugChannel(
984 channelName,
985 {
986 // Rate metrics
987 {"Memory | writes/sec", new armarx::Variant(static_cast<float>(commitsPerSec))},
988 {"Memory | reads/sec", new armarx::Variant(static_cast<float>(queriesPerSec))},
989 // Count metrics
990 {"Memory | total commits", new armarx::Variant(static_cast<int>(statistics.totalCommitCount.load()))},
991 {"Memory | total queries", new armarx::Variant(static_cast<int>(statistics.totalQueryCount.load()))},
992 {"Memory | total entity updates", new armarx::Variant(static_cast<int>(statistics.totalEntityUpdates.load()))},
993 {"Memory | successful updates", new armarx::Variant(static_cast<int>(statistics.successfulUpdates.load()))},
994 {"Memory | failed updates", new armarx::Variant(static_cast<int>(statistics.failedUpdates.load()))},
995 // Timing metrics
996 {"Memory | Commit | avg blocked [ms]", new armarx::Variant(static_cast<float>(avgCommitBlockingMs))},
997 {"Memory | Commit | max blocked [ms]", new armarx::Variant(static_cast<float>(statistics.maxCommitBlockingTimeMs.load()))},
998 {"Memory | Query | avg blocked [ms]", new armarx::Variant(static_cast<float>(avgQueryBlockingMs))},
999 {"Memory | Query | max blocked [ms]", new armarx::Variant(static_cast<float>(statistics.maxQueryBlockingTimeMs.load()))},
1000 // Async storage queue metrics
1001 {"Memory | LTM async queue size", new armarx::Variant(static_cast<int>(queueSize))},
1002 {"Memory | LTM threads processing", new armarx::Variant(static_cast<int>(numThreadsProcessing))},
1003 {"Memory | LTM items enqueued", new armarx::Variant(static_cast<int>(asyncItemsEnqueued))},
1004 {"Memory | LTM items processed", new armarx::Variant(static_cast<int>(asyncItemsProcessed))},
1005 {"Memory | LTM snapshots stored", new armarx::Variant(static_cast<int>(asyncSnapshotsStored))},
1006 {"Memory | LTM snapshots dropped", new armarx::Variant(static_cast<int>(asyncSnapshotsDropped))},
1007 {"Memory | LTM snapshots dropped (backpressure)", new armarx::Variant(static_cast<int>(asyncSnapshotsDroppedBackpressure))},
1008 {"Memory | LTM backpressure events", new armarx::Variant(static_cast<int>(asyncBackpressureEvents))},
1009 {"Memory | LTM avg storage [ms]", new armarx::Variant(static_cast<float>(asyncAvgStorageTimeMs))},
1010 {"Memory | LTM max storage [ms]", new armarx::Variant(static_cast<float>(asyncMaxStorageTimeMs))},
1011 });
1012 }
1013
1014} // namespace armarx::armem::server
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition Logging.cpp:75
constexpr T c
The Variant class is described here: Variants.
Definition Variant.h:224
std::string coreSegmentName
Definition MemoryID.h:51
std::string str(bool escapeDelimiters=true) const
Get a string representation of this memory ID.
Definition MemoryID.cpp:102
std::string memoryName
Definition MemoryID.h:50
std::string providerSegmentName
Definition MemoryID.h:52
ProviderSegmentT & getProviderSegment(const std::string &name)
CoreSegmentT * findCoreSegment(const std::string &name)
Definition MemoryBase.h:121
void append(const OtherDerivedT &other)
Merge another memory into this one.
Definition MemoryBase.h:363
std::vector< UpdateResult > update(const Commit &commit, const bool addMissingCoreSegmentDuringUpdate=false, const bool checkMemoryName=true)
Store all updates in commit.
Definition MemoryBase.h:310
void all()
Get all snapshots from all entities in all segments.
Definition Builder.cpp:54
Indicates that a name in a given ID does not match a container's own name.
Definition ArMemError.h:58
Indicates that a container did not have an entry under a given name.
Definition ArMemError.h:75
armem::structure::data::GetServerStructureResult getServerStructure()
void setMemoryListener(client::MemoryListenerInterfacePrx memoryListenerTopic)
dto::StartRecordResult startRecord(const dto::StartRecordInput &startRecordInput)
armem::CommitResult reloadFromLTMOnStartup()
Triggers a reload (.
armem::CommitResult reloadAllFromLTM()
Loads all core segments and their data from the LTM.
query::data::Result queryLTM(const armem::query::data::Input &input, bool storeIntoWM)
Query the LTMs of the memory server.
client::MemoryListenerInterfacePrx memoryListenerTopic
query::data::Result query(const armem::query::data::Input &input)
armem::CommitResult reloadCoreSegmentsFromLTM(std::list< std::string > &coreSegmentname)
Only load specific core segments and their data from the LTM.
prediction::data::PredictionResultSeq predict(prediction::data::PredictionRequestSeq requests)
dto::DirectlyStoreResult directlyStore(const dto::DirectlyStoreInput &directlStoreInput)
void reportDebugMetrics()
Report all debug metrics to the debug observer.
armem::CommitResult reloadPropertyDefinedCoreSegmentsFromLTM()
data::AddSegmentResult addSegment(const data::AddSegmentInput &input, bool addCoreSegments=false)
data::CommitResult commitLocking(const data::Commit &commitIce, Time timeArrived)
prediction::data::EngineSupportMap getAvailableEngines()
data::AddSegmentsResult addSegments(const data::AddSegmentsInput &input, bool addCoreSegments=false)
data::CommitResult commit(const data::Commit &commitIce, Time timeArrived)
MemoryToIceAdapter(server::wm::Memory *workingMemory, server::ltm::Memory *longtermMemory)
Construct a MemoryToIceAdapter from an existing Memory.
A memory storing data on the hard drive and in mongodb (needs 'armarx memory start' to start the mong...
Definition Memory.h:24
DebugObserverInterfacePrx getDebugObserver() const
Get the current debug observer (may be nullptr)
Definition MemoryBase.h:538
ResultMemoryT process(const armem::query::data::Input &input, const MemoryT &memory) const
ResultMemoryT process(const armem::query::data::Input &input, const MemoryT &memory) const
ProviderSegment & addProviderSegment(const std::string &name, Args... args)
auto doLockedExclusive(FunctionT &&function)
Execute function under exclusive (write) lock.
std::vector< Base::UpdateResult > updateLocking(const Commit &commit)
Perform the commit, locking the core segments.
Client-side working memory.
static DateTime Now()
Definition DateTime.cpp:51
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:188
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:194
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:182
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
#define TIMING_START(name)
Helper macro to do timing tests.
Definition TimeUtil.h:289
#define TIMING_END_STREAM(name, os)
Prints duration.
Definition TimeUtil.h:310
@ NoData
Just get the structure, but no ARON data.
Definition DataMode.h:8
DataMode boolToDataMode(bool withData)
Definition DataMode.cpp:6
void fromIce(const data::MemoryID &ice, MemoryID &id)
armarx::core::time::DateTime Time
Commit toCommit(const ContainerT &container)
Definition operations.h:23
void toIce(data::MemoryID &ice, const MemoryID &id)
std::string GetHandledExceptionString()
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)
Result of a Commit.
Definition Commit.h:111
std::vector< EntityUpdateResult > results
Definition Commit.h:112
A bundle of updates to be sent to the memory.
Definition Commit.h:90
Result of an EntityUpdate.
Definition Commit.h:75
An update of an entity for a specific point in time.
Definition Commit.h:26
A query for parts of a memory.
Definition Query.h:24
Result of a QueryInput.
Definition Query.h:51
static QueryResult fromIce(const armem::query::data::Result &ice)
Definition Query.cpp:26
#define ARMARX_TRACE
Definition trace.h:75