ProprioceptionStressTest.cpp
Go to the documentation of this file.
2
3#include <iomanip>
5#include <RobotAPI/libraries/armem_robot_state/aron/Proprioception.aron.generated.h>
6
8
10 "ProprioceptionStressTest");
11
13{
15 rng(std::random_device{}()),
16 jointAngleDist(-M_PI, M_PI)
17 {
18 }
19
21
22 std::string
24 {
25 return "ProprioceptionStressTest";
26 }
27
30 {
33
34 defs->optional(properties.robotName,
35 "p.robotName",
36 "The name of the robot to test with (e.g., 'Armar6', 'Armar7')");
37
38 defs->optional(properties.numJoints,
39 "p.numJoints",
40 "Number of mock joints to generate for testing");
41
42 defs->optional(properties.writeFrequencyHz,
43 "p.writeFrequencyHz",
44 "Frequency of write operations in Hz (0 = disabled)");
45
46 defs->optional(properties.readFrequencyHz,
47 "p.readFrequencyHz",
48 "Frequency of read operations in Hz (0 = disabled)");
49
50 defs->optional(properties.numParallelReads,
51 "p.numParallelReads",
52 "Number of parallel read threads (for testing shared mutexes)");
53
54 defs->optional(properties.numParallelWrites,
55 "p.numParallelWrites",
56 "Number of parallel write threads (for testing concurrent writes)");
57
58 defs->optional(properties.numExtraFloats,
59 "p.numExtraFloats",
60 "Number of extra float values to write per snapshot");
61
62 defs->optional(properties.numExtraLongs,
63 "p.numExtraLongs",
64 "Number of extra long values to write per snapshot");
65
66 defs->optional(properties.statisticsIntervalSec,
67 "p.statisticsIntervalSec",
68 "How often to print statistics (in seconds)");
69
70 defs->optional(properties.verboseLogging,
71 "p.verboseLogging",
72 "Enable verbose logging of each read/write operation");
73
74 return defs;
75 }
76
77 void
79 {
80 ARMARX_INFO << "Initializing ProprioceptionStressTest component";
81 ARMARX_INFO << " Robot: " << properties.robotName;
82 ARMARX_INFO << " Number of joints: " << properties.numJoints;
83 ARMARX_INFO << " Write frequency: " << properties.writeFrequencyHz << " Hz"
84 << " (x" << properties.numParallelWrites << " threads)";
85 ARMARX_INFO << " Read frequency: " << properties.readFrequencyHz << " Hz"
86 << " (x" << properties.numParallelReads << " threads)";
87 }
88
89 void
91 {
92 ARMARX_INFO << "Connecting to RobotStateMemory...";
93
94 // Connect reader and writer to the memory system
95 auto& mns = memoryNameSystem();
96 robotReader.connect(mns);
97 robotWriter.connect(mns);
98
99 // Initialize memory writer for direct proprioception commits
100 using namespace armem::robot_state;
101 memoryWriter = mns.useWriter(constants::memoryName);
102
103 ARMARX_INFO << "Connected to RobotStateMemory";
104
105 // Record start time for statistics
106 startTime = std::chrono::steady_clock::now();
107
108 // Start write tasks if enabled
109 if (properties.writeFrequencyHz > 0 && properties.numParallelWrites > 0)
110 {
111 ARMARX_INFO << "Starting " << properties.numParallelWrites << " write task(s) at "
112 << properties.writeFrequencyHz << " Hz each";
113
114 for (int i = 0; i < properties.numParallelWrites; ++i)
115 {
116 auto task = new SimpleRunningTask<>([this]() {
117 this->writeLoop();
118 });
119 task->start();
120 writeTasks.push_back(task);
121 }
122 }
123 else
124 {
125 ARMARX_INFO << "Write operations disabled (frequency = 0 or numParallelWrites = 0)";
126 }
127
128 // Start read tasks if enabled
129 if (properties.readFrequencyHz > 0 && properties.numParallelReads > 0)
130 {
131 ARMARX_INFO << "Starting " << properties.numParallelReads << " read task(s) at "
132 << properties.readFrequencyHz << " Hz each";
133
134 for (int i = 0; i < properties.numParallelReads; ++i)
135 {
136 auto task = new SimpleRunningTask<>([this]() {
137 this->readLoop();
138 });
139 task->start();
140 readTasks.push_back(task);
141 }
142 }
143 else
144 {
145 ARMARX_INFO << "Read operations disabled (frequency = 0 or numParallelReads = 0)";
146 }
147
148 // Start statistics task
149 if (properties.statisticsIntervalSec > 0)
150 {
151 ARMARX_INFO << "Starting statistics task (interval: "
152 << properties.statisticsIntervalSec << " seconds)";
153 statsTask = new SimpleRunningTask<>([this]() {
154 Metronome metronome(
155 Frequency::Hertz(1.0f / properties.statisticsIntervalSec));
156 while (statsTask && !statsTask->isStopped())
157 {
158 printStatistics();
159 metronome.waitForNextTick();
160 }
161 });
162 statsTask->start();
163 }
164
165 ARMARX_INFO << "All tasks started successfully";
166 }
167
168 void
170 {
171 ARMARX_INFO << "Disconnecting ProprioceptionStressTest...";
172
173 // Stop all write tasks
174 for (auto& task : writeTasks)
175 {
176 if (task)
177 {
178 task->stop();
179 }
180 }
181 writeTasks.clear();
182
183 // Stop all read tasks
184 for (auto& task : readTasks)
185 {
186 if (task)
187 {
188 task->stop();
189 }
190 }
191 readTasks.clear();
192
193 // Stop statistics task
194 if (statsTask)
195 {
196 statsTask->stop();
197 statsTask = nullptr;
198 }
199
200 // Print final statistics
201 printStatistics();
202
203 ARMARX_INFO << "ProprioceptionStressTest disconnected";
204 }
205
206 void
208 {
209 ARMARX_INFO << "Exiting ProprioceptionStressTest component";
210 }
211
212 void
213 ProprioceptionStressTest::writeLoop()
214 {
215 Metronome metronome(Frequency::Hertz(properties.writeFrequencyHz));
216
217 while (!writeTasks.empty())
218 {
219 try
220 {
221 // Generate complete mock proprioception data (joints + extras)
222 auto proprioception = generateMockProprioceptionData();
223
224 // Write to memory
226 bool success = storeProprioceptionWithExtras(
227 proprioception, properties.robotName, properties.robotName, timestamp);
228
229 if (success)
230 {
231 writeCount++;
232
233 if (properties.verboseLogging)
234 {
235 ARMARX_DEBUG << "Write #" << writeCount.load()
236 << ": Stored proprioception data with "
237 << proprioception.joints.position.size() << " joints, "
238 << proprioception.extraFloats.size() << " extraFloats, "
239 << proprioception.extraLongs.size() << " extraLongs at "
240 << timestamp.toMicroSecondsSinceEpoch() << " us";
241 }
242 }
243 else
244 {
245 writeErrorCount++;
246 ARMARX_WARNING << "Failed to write proprioception data (write #"
247 << (writeCount.load() + writeErrorCount.load()) << ")";
248 }
249 }
250 catch (const std::exception& e)
251 {
252 writeErrorCount++;
253 ARMARX_ERROR << "Exception during write operation: " << e.what();
254 }
255
256 metronome.waitForNextTick();
257 }
258 }
259
260 void
261 ProprioceptionStressTest::readLoop()
262 {
263 Metronome metronome(Frequency::Hertz(properties.readFrequencyHz));
264
265 while (!readTasks.empty())
266 {
267 try
268 {
269 // Query proprioception data
271 auto proprioception = robotReader.queryProprioception(properties.robotName, timestamp);
272
273 readCount++;
274
275 if (proprioception.has_value())
276 {
277 readSuccessCount++;
278
279 if (properties.verboseLogging)
280 {
281 const auto& joints = proprioception->joints.position;
282 ARMARX_DEBUG << "Read #" << readCount.load()
283 << ": Retrieved proprioception data with " << joints.size()
284 << " joints at " << timestamp.toMicroSecondsSinceEpoch() << " us";
285 }
286 }
287 else
288 {
289 readFailureCount++;
290
291 if (properties.verboseLogging)
292 {
293 ARMARX_DEBUG << "Read #" << readCount.load()
294 << ": No proprioception data available at "
295 << timestamp.toMicroSecondsSinceEpoch() << " us";
296 }
297 }
298 }
299 catch (const std::exception& e)
300 {
301 readFailureCount++;
302 ARMARX_ERROR << "Exception during read operation: " << e.what();
303 }
304
305 metronome.waitForNextTick();
306 }
307 }
308
309 arondto::Proprioception
310 ProprioceptionStressTest::generateMockProprioceptionData()
311 {
312 arondto::Proprioception proprioception;
313
314 // Generate mock joint positions with random values
315 for (int i = 0; i < properties.numJoints; ++i)
316 {
317 std::string jointName = "Joint_" + std::to_string(i);
318 float angle = jointAngleDist(rng);
319 proprioception.joints.position[jointName] = angle;
320 }
321
322 // Generate extra float values
323 for (int i = 0; i < properties.numExtraFloats; ++i)
324 {
325 std::string key = "extraFloat_" + std::to_string(i);
326 float value = jointAngleDist(rng);
327 proprioception.extraFloats[key] = value;
328 }
329
330 // Generate extra long values
331 for (int i = 0; i < properties.numExtraLongs; ++i)
332 {
333 std::string key = "extraLong_" + std::to_string(i);
334 long value = static_cast<long>(jointAngleDist(rng) * 1000);
335 proprioception.extraLongs[key] = value;
336 }
337
338 return proprioception;
339 }
340
341 bool
342 ProprioceptionStressTest::storeProprioceptionWithExtras(
343 const arondto::Proprioception& proprioception,
344 const std::string& robotTypeName,
345 const std::string& robotName,
346 const armem::Time& timestamp)
347 {
348 using namespace armem::robot_state;
349
350 // Create memory ID for proprioception segment
351 const auto providerId = armem::MemoryID(
352 constants::memoryName, constants::proprioceptionCoreSegment, robotTypeName);
353 const auto entityID = providerId.withEntityName(robotName).withTimestamp(timestamp);
354
355 // Create entity update
356 armem::EntityUpdate update;
357 update.entityID = entityID;
358 update.instancesData = {proprioception.toAron()};
359 update.referencedTime = timestamp;
360
361 // Commit to memory
362 armem::EntityUpdateResult updateResult = memoryWriter.commit(update);
363
364 if (!updateResult.success)
365 {
366 ARMARX_ERROR << "Failed to commit proprioception data: " << updateResult.errorMessage;
367 return false;
368 }
369
370 return true;
371 }
372
373 void
374 ProprioceptionStressTest::printStatistics()
375 {
376 const auto now = std::chrono::steady_clock::now();
377 const auto elapsedSec =
378 std::chrono::duration_cast<std::chrono::duration<double>>(now - startTime).count();
379
380 const uint64_t writes = writeCount.load();
381 const uint64_t writeErrors = writeErrorCount.load();
382 const uint64_t reads = readCount.load();
383 const uint64_t readSuccess = readSuccessCount.load();
384 const uint64_t readFailure = readFailureCount.load();
385
386 const double writeRate = (elapsedSec > 0) ? (writes / elapsedSec) : 0.0;
387 const double readRate = (elapsedSec > 0) ? (reads / elapsedSec) : 0.0;
388 const double readSuccessRate = (reads > 0) ? (100.0 * readSuccess / reads) : 0.0;
389
390 ARMARX_INFO << "\n"
391 << "========================================\n"
392 << " ProprioceptionStressTest Statistics\n"
393 << "========================================\n"
394 << "Elapsed time: " << std::fixed << std::setprecision(2) << elapsedSec
395 << " s\n"
396 << "Robot: " << properties.robotName << "\n"
397 << "----------------------------------------\n"
398 << "WRITE Operations:\n"
399 << " Total writes: " << writes << "\n"
400 << " Write errors: " << writeErrors << "\n"
401 << " Write rate: " << std::fixed << std::setprecision(2) << writeRate
402 << " ops/s\n"
403 << "----------------------------------------\n"
404 << "READ Operations:\n"
405 << " Total reads: " << reads << "\n"
406 << " Successful reads: " << readSuccess << "\n"
407 << " Failed reads: " << readFailure << "\n"
408 << " Read rate: " << std::fixed << std::setprecision(2) << readRate
409 << " ops/s\n"
410 << " Success rate: " << std::fixed << std::setprecision(1)
411 << readSuccessRate << " %\n"
412 << "========================================";
413 }
414
415} // namespace armarx::armem::client
std::string timestamp()
#define ARMARX_REGISTER_COMPONENT_EXECUTABLE(ComponentT, applicationName)
Definition Decoupled.h:29
#define M_PI
Definition MathTools.h:17
Default component property definition container.
Definition Component.h:70
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:90
static Frequency Hertz(std::int64_t hertz)
Definition Frequency.cpp:20
Debug component for stress-testing the Proprioception segment of RobotStateMemory with high-frequency...
void onInitComponent() override
Pure virtual hook for the subclass.
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
Creates the property definition container.
void onConnectComponent() override
Pure virtual hook for the subclass.
std::string getDefaultName() const override
Retrieve default name of component.
static DateTime Now()
Definition DateTime.cpp:51
Simple rate limiter for use in loops to maintain a certain frequency given a clock.
Definition Metronome.h:57
Duration waitForNextTick() const
Wait and block until the target period is met.
Definition Metronome.cpp:27
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:196
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:184
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
This file is part of ArmarX.
bool update(mongocxx::collection &coll, const nlohmann::json &query, const nlohmann::json &update)
Definition mongodb.cpp:68
armarx::core::time::DateTime Time
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.
SimpleRunningTask(Ts...) -> SimpleRunningTask< std::function< void(void)> >
std::shared_ptr< Value > value()
Definition cxxopts.hpp:855
double angle(const Point &a, const Point &b, const Point &c)
Definition point.hpp:109