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