LogSpamTest.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @package ArmarXCore::ArmarXObjects::LogSpamTest
17 * @date 2026
18 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
19 * GNU General Public License
20 */
21
22#include "LogSpamTest.h"
23
24#include <algorithm>
25#include <chrono>
26#include <sstream>
27#include <thread>
28#include <unistd.h>
29
31
32namespace armarx
33{
34 namespace
35 {
36 // Word pool for the generated message bodies. Deliberately log-like, so the
37 // resulting lines have a realistic length distribution and a realistic amount of
38 // shared prefix between them.
39 const std::vector<std::string>&
40 wordPool()
41 {
42 static const std::vector<std::string> words{
43 "trajectory", "pose", "gripper", "velocity", "joint",
44 "torque", "waypoint", "frame", "transform", "solver",
45 "converged", "residual", "iteration", "timeout", "retry",
46 "planner", "collision", "segment", "cartesian", "orientation",
47 "calibration", "offset", "threshold", "controller", "setpoint",
48 "measurement", "estimate", "covariance", "landmark", "keyframe",
49 "descriptor", "match", "inlier", "outlier", "cloud",
50 "voxel", "octree", "occupancy", "path", "goal",
51 "obstacle", "footprint", "odometry", "drift", "latency",
52 "buffer", "queue", "dropped", "stale", "resynced"};
53 return words;
54 }
55
56 // A short random hex id, so that two messages that happen to draw the same words
57 // are still distinct.
58 std::string
59 randomId(std::mt19937& rng)
60 {
61 static const char* digits = "0123456789abcdef";
62 std::uniform_int_distribution<int> dist(0, 15);
63 std::string id(8, '0');
64 for (char& c : id)
65 {
66 c = digits[dist(rng)];
67 }
68 return id;
69 }
70 } // namespace
71
74 {
76 "MessagesPerSecond",
77 200,
78 "Target number of log messages per second, summed over all threads. <= 0 disables "
79 "the component (it then starts but stays silent).");
81 "ThreadCount",
82 1,
83 "Number of concurrent sender threads. They share MessagesPerSecond between them.");
85 "Seed",
86 0,
87 "Seed for the random message content. 0 picks a seed from the clock and the process "
88 "id, so instances differ; any other value makes the run reproducible.");
89
91 "WarningPercent",
92 5,
93 "Percentage of messages emitted at WARN. Note that WARN and above attach a full "
94 "backtrace, which is by far the largest per-message cost on the sender side.");
96 "ErrorPercent", 2, "Percentage of messages emitted at ERROR (also backtraced).");
97 defineOptionalProperty<int>("VerbosePercent",
98 0,
99 "Percentage of messages emitted at VERBOSE. Only visible if "
100 "ArmarX.Verbosity is set to Verbose or Debug.");
101
103 "MultilinePercent",
104 5,
105 "Percentage of messages spanning several lines. These are what give the viewer's "
106 "table rows individual heights.");
108 "MultilineMaxLines", 12, "Upper bound on the line count of a multi-line message.");
109
110 defineOptionalProperty<int>("MinWords", 6, "Lower bound on the word count of a message.");
111 defineOptionalProperty<int>("MaxWords", 20, "Upper bound on the word count of a message.");
112
114 "TagCount", 4, "Number of distinct tags to spread the messages over (at least 1).");
115
117 "BurstEveryMs",
118 0,
119 "If > 0, emit a burst of BurstSize messages every this many milliseconds, on top of "
120 "the steady rate. Use this to exercise the viewer's staging queue rather than only "
121 "its steady-state throughput.");
122 defineOptionalProperty<int>("BurstSize", 500, "Number of messages in one burst.");
123 }
124
125 std::string
127 {
128 return "LogSpamTest";
129 }
130
131 void
133 {
134 getProperty(messagesPerSecond, "MessagesPerSecond");
135 getProperty(threadCount, "ThreadCount");
136 getProperty(seed, "Seed");
137 getProperty(warningPercent, "WarningPercent");
138 getProperty(errorPercent, "ErrorPercent");
139 getProperty(verbosePercent, "VerbosePercent");
140 getProperty(multilinePercent, "MultilinePercent");
141 getProperty(multilineMaxLines, "MultilineMaxLines");
142 getProperty(minWords, "MinWords");
143 getProperty(maxWords, "MaxWords");
144 getProperty(tagCount, "TagCount");
145 getProperty(burstEveryMs, "BurstEveryMs");
146 getProperty(burstSize, "BurstSize");
147
148 threadCount = std::max(1, threadCount);
149 tagCount = std::max(1, tagCount);
150 minWords = std::max(1, minWords);
151 maxWords = std::max(minWords, maxWords);
152 multilineMaxLines = std::max(2, multilineMaxLines);
153 burstSize = std::max(0, burstSize);
154
155 if (seed == 0)
156 {
157 seed = static_cast<int>(
158 std::chrono::steady_clock::now().time_since_epoch().count() ^ ::getpid());
159 }
160
161 ARMARX_IMPORTANT << "LogSpamTest: " << messagesPerSecond << " msg/s over " << threadCount
162 << " thread(s), " << warningPercent << "% warnings, " << errorPercent
163 << "% errors, " << multilinePercent << "% multi-line, seed " << seed;
164 }
165
166 void
168 {
169 if (messagesPerSecond <= 0)
170 {
171 ARMARX_IMPORTANT << "LogSpamTest: MessagesPerSecond <= 0, staying silent.";
172 return;
173 }
174
175 running = true;
176 for (int i = 0; i < threadCount; ++i)
177 {
178 tasks.push_back(new RunningTask<LogSpamTest>(
179 this, &LogSpamTest::spamTaskRun, "LogSpamTask" + std::to_string(i)));
180 }
181 // Started only after the vector is complete, so no thread observes a partial list.
182 for (auto& task : tasks)
183 {
184 task->start();
185 }
186 }
187
188 void
190 {
191 running = false;
192 for (auto& task : tasks)
193 {
194 if (task)
195 {
196 task->stop(true);
197 }
198 }
199 tasks.clear();
200 }
201
202 void
206
207 std::string
208 LogSpamTest::buildMessage(std::mt19937& rng, long counter) const
209 {
210 const std::vector<std::string>& words = wordPool();
211 std::uniform_int_distribution<std::size_t> wordDist(0, words.size() - 1);
212 std::uniform_int_distribution<int> countDist(minWords, maxWords);
213 std::uniform_int_distribution<int> percentDist(0, 99);
214
215 std::ostringstream out;
216 // The counter and the id guarantee uniqueness even when the drawn words repeat.
217 out << "#" << counter << " [" << randomId(rng) << "]";
218
219 const int count = countDist(rng);
220 for (int i = 0; i < count; ++i)
221 {
222 out << " " << words[wordDist(rng)];
223 }
224
225 if (percentDist(rng) < multilinePercent)
226 {
227 std::uniform_int_distribution<int> lineDist(2, multilineMaxLines);
228 const int lines = lineDist(rng);
229 for (int line = 0; line < lines; ++line)
230 {
231 out << "\n " << line << ": ";
232 const int inner = countDist(rng);
233 for (int i = 0; i < inner; ++i)
234 {
235 out << words[wordDist(rng)] << " ";
236 }
237 }
238 }
239
240 return out.str();
241 }
242
243 void
244 LogSpamTest::emitOne(std::mt19937& rng, long& counter)
245 {
246 const std::string body = buildMessage(rng, counter++);
247
248 std::uniform_int_distribution<int> percentDist(0, 99);
249 const int roll = percentDist(rng);
250
251 const std::string tag = "spam" + std::to_string(counter % tagCount);
252
253// The logging macros expand to a conditional expression, so the stream chain has to be
254// written out per severity rather than kept in a variable.
255#define ARMARX_LOG_SPAM(MACRO) MACRO << LogTag(tag) << body
256
257 if (roll < errorPercent)
258 {
260 }
261 else if (roll < errorPercent + warningPercent)
262 {
264 }
265 else if (roll < errorPercent + warningPercent + verbosePercent)
266 {
268 }
269 else if (roll % 10 == 0)
270 {
272 }
273 else
274 {
276 }
277
278#undef ARMARX_LOG_SPAM
279 }
280
281 void
282 LogSpamTest::spamTaskRun()
283 {
284 const int index = nextThreadIndex++;
285 std::mt19937 rng(static_cast<std::mt19937::result_type>(seed + index * 7919));
286 long counter = 0;
287
288 // Each thread carries its share of the target rate.
289 const int perThreadRate = std::max(1, messagesPerSecond / threadCount);
290 const auto period = std::chrono::nanoseconds(std::chrono::seconds(1)) / perThreadRate;
291
292 auto nextMessage = std::chrono::steady_clock::now();
293 auto nextBurst = nextMessage + std::chrono::milliseconds(std::max(0, burstEveryMs));
294
295 while (running.load())
296 {
297 emitOne(rng, counter);
298
299 const auto now = std::chrono::steady_clock::now();
300
301 // Bursts are emitted by the first thread only, so BurstSize means the same
302 // thing regardless of ThreadCount.
303 if (burstEveryMs > 0 && index == 0 && now >= nextBurst)
304 {
305 for (int i = 0; i < burstSize && running.load(); ++i)
306 {
307 emitOne(rng, counter);
308 }
309 nextBurst = std::chrono::steady_clock::now() +
310 std::chrono::milliseconds(burstEveryMs);
311 }
312
313 nextMessage += period;
314 if (nextMessage < now)
315 {
316 // We cannot keep up with the requested rate (or a burst just ran); resync
317 // rather than spinning to catch up on a backlog that will never shrink.
318 nextMessage = now;
319 }
320 else
321 {
322 std::this_thread::sleep_until(nextMessage);
323 }
324 }
325 }
326
333} // namespace armarx
uint8_t index
#define ARMARX_LOG_SPAM(MACRO)
constexpr T c
ComponentPropertyDefinitions(std::string prefix, bool hasObjectNameParameter=true)
Definition Component.cpp:44
std::string getConfigIdentifier()
Retrieve config identifier for this component as set in constructor.
Definition Component.cpp:88
Property< PropertyType > getProperty(const std::string &name)
Properties of the LogSpamTest component.
Definition LogSpamTest.h:39
LogSpamTestPropertyDefinitions(std::string prefix)
void onInitComponent() override
void onDisconnectComponent() override
armarx::PropertyDefinitionsPtr createPropertyDefinitions() override
void onConnectComponent() override
void onExitComponent() override
std::string getDefaultName() const override
std::string prefix
Prefix of the properties such as namespace, domain, component name, etc.
PropertyDefinition< PropertyType > & defineOptionalProperty(const std::string &name, PropertyType defaultValue, const std::string &description="", PropertyDefinitionBase::PropertyConstness constness=PropertyDefinitionBase::eConstant)
#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_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:185
This file offers overloads of toIce() and fromIce() functions for STL container types.
IceUtil::Handle< class PropertyDefinitionContainer > PropertyDefinitionsPtr
PropertyDefinitions smart pointer type.