Commit 367579cc24954a856a89a48e819ba4ba788ef6e4
1 parent
e44119c5
Properly handle dollar topics
Also include a few stats.
Showing
12 changed files
with
196 additions
and
25 deletions
FlashMQTests/tst_maintests.cpp
| ... | ... | @@ -323,6 +323,9 @@ void MainTests::test_validSubscribePath() |
| 323 | 323 | QVERIFY(isValidSubscribePath("")); |
| 324 | 324 | QVERIFY(isValidSubscribePath("hello")); |
| 325 | 325 | |
| 326 | + QVERIFY(isValidSubscribePath("$SYS/hello")); | |
| 327 | + QVERIFY(isValidSubscribePath("hello/$SYS")); // Hmm, is this valid? | |
| 328 | + | |
| 326 | 329 | QVERIFY(!isValidSubscribePath("one/tw+o/three")); |
| 327 | 330 | QVERIFY(!isValidSubscribePath("one/+o/three")); |
| 328 | 331 | QVERIFY(!isValidSubscribePath("one/a+/three")); |
| ... | ... | @@ -707,6 +710,7 @@ void MainTests::test_validUtf8Sse() |
| 707 | 710 | QVERIFY(!data.isValidUtf8("+", true)); |
| 708 | 711 | QVERIFY(!data.isValidUtf8("🩰+asdfasdfasdf", true)); |
| 709 | 712 | QVERIFY(!data.isValidUtf8("+asdfasdfasdf", true)); |
| 713 | + QVERIFY(!data.isValidUtf8("$SYS/asdfasdfasdf", true)); | |
| 710 | 714 | |
| 711 | 715 | std::memset(m, 0, 16); |
| 712 | 716 | m[0] = 'a'; | ... | ... |
mainapp.cpp
| ... | ... | @@ -187,6 +187,10 @@ MainApp::MainApp(const std::string &configFilePath) : |
| 187 | 187 | |
| 188 | 188 | auto fPasswordFileReload = std::bind(&MainApp::queuePasswordFileReloadAllThreads, this); |
| 189 | 189 | timer.addCallback(fPasswordFileReload, 2000, "Password file reload."); |
| 190 | + | |
| 191 | + auto fPublishStats = std::bind(&MainApp::publishStatsOnDollarTopic, this); | |
| 192 | + timer.addCallback(fPublishStats, 10000, "Publish stats on $SYS"); | |
| 193 | + publishStatsOnDollarTopic(); | |
| 190 | 194 | } |
| 191 | 195 | |
| 192 | 196 | MainApp::~MainApp() |
| ... | ... | @@ -306,6 +310,43 @@ void MainApp::setFuzzFile(const std::string &fuzzFilePath) |
| 306 | 310 | this->fuzzFilePath = fuzzFilePath; |
| 307 | 311 | } |
| 308 | 312 | |
| 313 | +void MainApp::publishStatsOnDollarTopic() | |
| 314 | +{ | |
| 315 | + uint nrOfClients = 0; | |
| 316 | + uint64_t receivedMessageCountPerSecond = 0; | |
| 317 | + uint64_t receivedMessageCount = 0; | |
| 318 | + uint64_t sentMessageCountPerSecond = 0; | |
| 319 | + uint64_t sentMessageCount = 0; | |
| 320 | + | |
| 321 | + for (std::shared_ptr<ThreadData> &thread : threads) | |
| 322 | + { | |
| 323 | + nrOfClients += thread->getNrOfClients(); | |
| 324 | + | |
| 325 | + receivedMessageCountPerSecond += thread->getReceivedMessagePerSecond(); | |
| 326 | + receivedMessageCount += thread->getReceivedMessageCount(); | |
| 327 | + | |
| 328 | + sentMessageCountPerSecond += thread->getSentMessagePerSecond(); | |
| 329 | + sentMessageCount += thread->getSentMessageCount(); | |
| 330 | + } | |
| 331 | + | |
| 332 | + publishStat("$SYS/broker/clients/total", nrOfClients); | |
| 333 | + | |
| 334 | + publishStat("$SYS/broker/load/messages/received/total", receivedMessageCount); | |
| 335 | + publishStat("$SYS/broker/load/messages/received/persecond", receivedMessageCountPerSecond); | |
| 336 | + | |
| 337 | + publishStat("$SYS/broker/load/messages/sent/total", sentMessageCount); | |
| 338 | + publishStat("$SYS/broker/load/messages/sent/persecond", sentMessageCountPerSecond); | |
| 339 | +} | |
| 340 | + | |
| 341 | +void MainApp::publishStat(const std::string &topic, uint64_t n) | |
| 342 | +{ | |
| 343 | + std::vector<std::string> *subtopics = utils.splitTopic(topic); | |
| 344 | + const std::string payload = std::to_string(n); | |
| 345 | + Publish p(topic, payload, 0); | |
| 346 | + subscriptionStore->queuePacketAtSubscribers(*subtopics, p, true); | |
| 347 | + subscriptionStore->setRetainedMessage(topic, payload, 0); | |
| 348 | +} | |
| 349 | + | |
| 309 | 350 | void MainApp::initMainApp(int argc, char *argv[]) |
| 310 | 351 | { |
| 311 | 352 | if (instance != nullptr) | ... | ... |
mainapp.h
| ... | ... | @@ -41,6 +41,7 @@ License along with FlashMQ. If not, see <https://www.gnu.org/licenses/>. |
| 41 | 41 | #include "timer.h" |
| 42 | 42 | #include "scopedsocket.h" |
| 43 | 43 | #include "oneinstancelock.h" |
| 44 | +#include "threadlocalutils.h" | |
| 44 | 45 | |
| 45 | 46 | #define VERSION "0.7.0" |
| 46 | 47 | |
| ... | ... | @@ -64,6 +65,7 @@ class MainApp |
| 64 | 65 | std::mutex quitMutex; |
| 65 | 66 | std::string fuzzFilePath; |
| 66 | 67 | OneInstanceLock oneInstanceLock; |
| 68 | + Utils utils; | |
| 67 | 69 | |
| 68 | 70 | Logger *logger = Logger::getInstance(); |
| 69 | 71 | |
| ... | ... | @@ -77,6 +79,8 @@ class MainApp |
| 77 | 79 | void queueKeepAliveCheckAtAllThreads(); |
| 78 | 80 | void queuePasswordFileReloadAllThreads(); |
| 79 | 81 | void setFuzzFile(const std::string &fuzzFilePath); |
| 82 | + void publishStatsOnDollarTopic(); | |
| 83 | + void publishStat(const std::string &topic, uint64_t n); | |
| 80 | 84 | |
| 81 | 85 | MainApp(const std::string &configFilePath); |
| 82 | 86 | public: | ... | ... |
mqttpacket.cpp
| ... | ... | @@ -497,6 +497,8 @@ void MqttPacket::handlePublish() |
| 497 | 497 | logger->logf(LOG_DEBUG, "Publish received, topic '%s'. QoS=%d. Retain=%d, dup=%d", topic.c_str(), qos, retain, dup); |
| 498 | 498 | #endif |
| 499 | 499 | |
| 500 | + sender->getThreadData()->incrementReceivedMessageCount(); | |
| 501 | + | |
| 500 | 502 | if (qos) |
| 501 | 503 | { |
| 502 | 504 | packet_id_pos = pos; | ... | ... |
session.cpp
| ... | ... | @@ -48,7 +48,7 @@ void Session::assignActiveConnection(std::shared_ptr<Client> &client) |
| 48 | 48 | this->thread = client->getThreadData(); |
| 49 | 49 | } |
| 50 | 50 | |
| 51 | -void Session::writePacket(const MqttPacket &packet, char max_qos) | |
| 51 | +void Session::writePacket(const MqttPacket &packet, char max_qos, uint64_t &count) | |
| 52 | 52 | { |
| 53 | 53 | assert(max_qos <= 2); |
| 54 | 54 | |
| ... | ... | @@ -62,6 +62,7 @@ void Session::writePacket(const MqttPacket &packet, char max_qos) |
| 62 | 62 | { |
| 63 | 63 | std::shared_ptr<Client> c = makeSharedClient(); |
| 64 | 64 | c->writeMqttPacketAndBlameThisClient(packet, qos); |
| 65 | + count++; | |
| 65 | 66 | } |
| 66 | 67 | } |
| 67 | 68 | else if (qos > 0) |
| ... | ... | @@ -93,6 +94,7 @@ void Session::writePacket(const MqttPacket &packet, char max_qos) |
| 93 | 94 | std::shared_ptr<Client> c = makeSharedClient(); |
| 94 | 95 | c->writeMqttPacketAndBlameThisClient(*copyPacket.get(), qos); |
| 95 | 96 | copyPacket->setDuplicate(); // Any dealings with this packet from here will be a duplicate. |
| 97 | + count++; | |
| 96 | 98 | } |
| 97 | 99 | } |
| 98 | 100 | } |
| ... | ... | @@ -136,8 +138,10 @@ void Session::clearQosMessage(uint16_t packet_id) |
| 136 | 138 | // |
| 137 | 139 | // There is a bit of a hole there, I think. When we write out a packet to a receiver, it may decide to drop it, if its buffers |
| 138 | 140 | // are full, for instance. We are not required to (periodically) retry. TODO Perhaps I will implement that retry anyway. |
| 139 | -void Session::sendPendingQosMessages() | |
| 141 | +uint64_t Session::sendPendingQosMessages() | |
| 140 | 142 | { |
| 143 | + uint64_t count = 0; | |
| 144 | + | |
| 141 | 145 | if (!clientDisconnected()) |
| 142 | 146 | { |
| 143 | 147 | std::shared_ptr<Client> c = makeSharedClient(); |
| ... | ... | @@ -146,6 +150,7 @@ void Session::sendPendingQosMessages() |
| 146 | 150 | { |
| 147 | 151 | c->writeMqttPacketAndBlameThisClient(*qosMessage.packet.get(), qosMessage.packet->getQos()); |
| 148 | 152 | qosMessage.packet->setDuplicate(); // Any dealings with this packet from here will be a duplicate. |
| 153 | + count++; | |
| 149 | 154 | } |
| 150 | 155 | |
| 151 | 156 | for (const uint16_t packet_id : outgoingQoS2MessageIds) |
| ... | ... | @@ -155,6 +160,8 @@ void Session::sendPendingQosMessages() |
| 155 | 160 | c->writeMqttPacketAndBlameThisClient(packet, 2); |
| 156 | 161 | } |
| 157 | 162 | } |
| 163 | + | |
| 164 | + return count; | |
| 158 | 165 | } |
| 159 | 166 | |
| 160 | 167 | void Session::touch(time_t val) | ... | ... |
session.h
| ... | ... | @@ -64,9 +64,9 @@ public: |
| 64 | 64 | bool clientDisconnected() const; |
| 65 | 65 | std::shared_ptr<Client> makeSharedClient() const; |
| 66 | 66 | void assignActiveConnection(std::shared_ptr<Client> &client); |
| 67 | - void writePacket(const MqttPacket &packet, char max_qos); | |
| 67 | + void writePacket(const MqttPacket &packet, char max_qos, uint64_t &count); | |
| 68 | 68 | void clearQosMessage(uint16_t packet_id); |
| 69 | - void sendPendingQosMessages(); | |
| 69 | + uint64_t sendPendingQosMessages(); | |
| 70 | 70 | void touch(time_t val = 0); |
| 71 | 71 | bool hasExpired(); |
| 72 | 72 | ... | ... |
subscriptionstore.cpp
| ... | ... | @@ -78,6 +78,7 @@ SubscriptionNode *SubscriptionNode::getChildren(const std::string &subtopic) con |
| 78 | 78 | |
| 79 | 79 | SubscriptionStore::SubscriptionStore() : |
| 80 | 80 | root("root"), |
| 81 | + rootDollar("rootDollar"), | |
| 81 | 82 | sessionsByIdConst(sessionsById) |
| 82 | 83 | { |
| 83 | 84 | |
| ... | ... | @@ -87,10 +88,13 @@ void SubscriptionStore::addSubscription(std::shared_ptr<Client> &client, const s |
| 87 | 88 | { |
| 88 | 89 | const std::list<std::string> subtopics = split(topic, '/'); |
| 89 | 90 | |
| 91 | + SubscriptionNode *deepestNode = &root; | |
| 92 | + if (topic.length() > 0 && topic[0] == '$') | |
| 93 | + deepestNode = &rootDollar; | |
| 94 | + | |
| 90 | 95 | RWLockGuard lock_guard(&subscriptionsRwlock); |
| 91 | 96 | lock_guard.wrlock(); |
| 92 | 97 | |
| 93 | - SubscriptionNode *deepestNode = &root; | |
| 94 | 98 | for(const std::string &subtopic : subtopics) |
| 95 | 99 | { |
| 96 | 100 | std::unique_ptr<SubscriptionNode> *selectedChildren = nullptr; |
| ... | ... | @@ -120,25 +124,27 @@ void SubscriptionStore::addSubscription(std::shared_ptr<Client> &client, const s |
| 120 | 124 | { |
| 121 | 125 | const std::shared_ptr<Session> &ses = session_it->second; |
| 122 | 126 | deepestNode->addSubscriber(ses, qos); |
| 123 | - giveClientRetainedMessages(ses, topic, qos); | |
| 127 | + uint64_t count = giveClientRetainedMessages(ses, topic, qos); | |
| 128 | + client->getThreadData()->incrementSentMessageCount(count); | |
| 124 | 129 | } |
| 125 | 130 | } |
| 126 | 131 | |
| 127 | 132 | lock_guard.unlock(); |
| 128 | - | |
| 129 | - | |
| 130 | 133 | } |
| 131 | 134 | |
| 132 | 135 | void SubscriptionStore::removeSubscription(std::shared_ptr<Client> &client, const std::string &topic) |
| 133 | 136 | { |
| 134 | 137 | const std::list<std::string> subtopics = split(topic, '/'); |
| 135 | 138 | |
| 139 | + SubscriptionNode *deepestNode = &root; | |
| 140 | + if (topic.length() > 0 && topic[0] == '$') | |
| 141 | + deepestNode = &rootDollar; | |
| 142 | + | |
| 136 | 143 | RWLockGuard lock_guard(&subscriptionsRwlock); |
| 137 | 144 | lock_guard.wrlock(); |
| 138 | 145 | |
| 139 | 146 | // This code looks like that for addSubscription(), but it's specifically different in that we don't want to default-create non-existing |
| 140 | 147 | // nodes. We need to abort when that happens. |
| 141 | - SubscriptionNode *deepestNode = &root; | |
| 142 | 148 | for(const std::string &subtopic : subtopics) |
| 143 | 149 | { |
| 144 | 150 | SubscriptionNode *selectedChildren = nullptr; |
| ... | ... | @@ -208,7 +214,8 @@ void SubscriptionStore::registerClientAndKickExistingOne(std::shared_ptr<Client> |
| 208 | 214 | |
| 209 | 215 | session->assignActiveConnection(client); |
| 210 | 216 | client->assignSession(session); |
| 211 | - session->sendPendingQosMessages(); | |
| 217 | + uint64_t count = session->sendPendingQosMessages(); | |
| 218 | + client->getThreadData()->incrementSentMessageCount(count); | |
| 212 | 219 | } |
| 213 | 220 | |
| 214 | 221 | bool SubscriptionStore::sessionPresent(const std::string &clientid) |
| ... | ... | @@ -227,7 +234,7 @@ bool SubscriptionStore::sessionPresent(const std::string &clientid) |
| 227 | 234 | return result; |
| 228 | 235 | } |
| 229 | 236 | |
| 230 | -void SubscriptionStore::publishNonRecursively(const MqttPacket &packet, const std::vector<Subscription> &subscribers) const | |
| 237 | +void SubscriptionStore::publishNonRecursively(const MqttPacket &packet, const std::vector<Subscription> &subscribers, uint64_t &count) const | |
| 231 | 238 | { |
| 232 | 239 | for (const Subscription &sub : subscribers) |
| 233 | 240 | { |
| ... | ... | @@ -235,18 +242,29 @@ void SubscriptionStore::publishNonRecursively(const MqttPacket &packet, const st |
| 235 | 242 | if (!session_weak.expired()) // Shared pointer expires when session has been cleaned by 'clean session' connect. |
| 236 | 243 | { |
| 237 | 244 | const std::shared_ptr<Session> session = session_weak.lock(); |
| 238 | - session->writePacket(packet, sub.qos); | |
| 245 | + session->writePacket(packet, sub.qos, count); | |
| 239 | 246 | } |
| 240 | 247 | } |
| 241 | 248 | } |
| 242 | 249 | |
| 250 | +/** | |
| 251 | + * @brief SubscriptionStore::publishRecursively | |
| 252 | + * @param cur_subtopic_it | |
| 253 | + * @param end | |
| 254 | + * @param this_node | |
| 255 | + * @param packet | |
| 256 | + * @param count as a reference (vs return value) because a return value introduces an extra call i.e. limits tail recursion optimization. | |
| 257 | + * | |
| 258 | + * As noted in the params section, this method was written so that it could be (somewhat) optimized for tail recursion by the kernel. If you refactor this, | |
| 259 | + * look at objdump --disassemble --demangle to see how many calls (not jumps) to itself are made and compare. | |
| 260 | + */ | |
| 243 | 261 | void SubscriptionStore::publishRecursively(std::vector<std::string>::const_iterator cur_subtopic_it, std::vector<std::string>::const_iterator end, |
| 244 | - SubscriptionNode *this_node, const MqttPacket &packet) const | |
| 262 | + SubscriptionNode *this_node, const MqttPacket &packet, uint64_t &count) const | |
| 245 | 263 | { |
| 246 | 264 | if (cur_subtopic_it == end) // This is the end of the topic path, so look for subscribers here. |
| 247 | 265 | { |
| 248 | 266 | if (this_node) |
| 249 | - publishNonRecursively(packet, this_node->getSubscribers()); | |
| 267 | + publishNonRecursively(packet, this_node->getSubscribers(), count); | |
| 250 | 268 | return; |
| 251 | 269 | } |
| 252 | 270 | |
| ... | ... | @@ -263,33 +281,44 @@ void SubscriptionStore::publishRecursively(std::vector<std::string>::const_itera |
| 263 | 281 | |
| 264 | 282 | if (this_node->childrenPound) |
| 265 | 283 | { |
| 266 | - publishNonRecursively(packet, this_node->childrenPound->getSubscribers()); | |
| 284 | + publishNonRecursively(packet, this_node->childrenPound->getSubscribers(), count); | |
| 267 | 285 | } |
| 268 | 286 | |
| 269 | 287 | const auto &sub_node = this_node->children.find(cur_subtop); |
| 270 | 288 | if (sub_node != this_node->children.end()) |
| 271 | 289 | { |
| 272 | - publishRecursively(next_subtopic, end, sub_node->second.get(), packet); | |
| 290 | + publishRecursively(next_subtopic, end, sub_node->second.get(), packet, count); | |
| 273 | 291 | } |
| 274 | 292 | |
| 275 | 293 | if (this_node->childrenPlus) |
| 276 | 294 | { |
| 277 | - publishRecursively(next_subtopic, end, this_node->childrenPlus.get(), packet); | |
| 295 | + publishRecursively(next_subtopic, end, this_node->childrenPlus.get(), packet, count); | |
| 278 | 296 | } |
| 279 | 297 | } |
| 280 | 298 | |
| 281 | -void SubscriptionStore::queuePacketAtSubscribers(const std::vector<std::string> &subtopics, const MqttPacket &packet) | |
| 299 | +void SubscriptionStore::queuePacketAtSubscribers(const std::vector<std::string> &subtopics, const MqttPacket &packet, bool dollar) | |
| 282 | 300 | { |
| 283 | 301 | assert(subtopics.size() > 0); |
| 284 | 302 | |
| 303 | + SubscriptionNode *startNode = dollar ? &rootDollar : &root; | |
| 304 | + | |
| 285 | 305 | RWLockGuard lock_guard(&subscriptionsRwlock); |
| 286 | 306 | lock_guard.rdlock(); |
| 287 | 307 | |
| 288 | - publishRecursively(subtopics.begin(), subtopics.end(), &root, packet); | |
| 308 | + uint64_t count = 0; | |
| 309 | + publishRecursively(subtopics.begin(), subtopics.end(), startNode, packet, count); | |
| 310 | + | |
| 311 | + std::shared_ptr<Client> sender = packet.getSender(); | |
| 312 | + if (sender) | |
| 313 | + { | |
| 314 | + sender->getThreadData()->incrementSentMessageCount(count); | |
| 315 | + } | |
| 289 | 316 | } |
| 290 | 317 | |
| 291 | -void SubscriptionStore::giveClientRetainedMessages(const std::shared_ptr<Session> &ses, const std::string &subscribe_topic, char max_qos) | |
| 318 | +uint64_t SubscriptionStore::giveClientRetainedMessages(const std::shared_ptr<Session> &ses, const std::string &subscribe_topic, char max_qos) | |
| 292 | 319 | { |
| 320 | + uint64_t count = 0; | |
| 321 | + | |
| 293 | 322 | RWLockGuard locker(&retainedMessagesRwlock); |
| 294 | 323 | locker.rdlock(); |
| 295 | 324 | |
| ... | ... | @@ -300,8 +329,12 @@ void SubscriptionStore::giveClientRetainedMessages(const std::shared_ptr<Session |
| 300 | 329 | const MqttPacket packet(publish); |
| 301 | 330 | |
| 302 | 331 | if (topicsMatch(subscribe_topic, rm.topic)) |
| 303 | - ses->writePacket(packet, max_qos); | |
| 332 | + { | |
| 333 | + ses->writePacket(packet, max_qos, count); | |
| 334 | + } | |
| 304 | 335 | } |
| 336 | + | |
| 337 | + return count; | |
| 305 | 338 | } |
| 306 | 339 | |
| 307 | 340 | void SubscriptionStore::setRetainedMessage(const std::string &topic, const std::string &payload, char qos) | ... | ... |
subscriptionstore.h
| ... | ... | @@ -72,6 +72,7 @@ public: |
| 72 | 72 | class SubscriptionStore |
| 73 | 73 | { |
| 74 | 74 | SubscriptionNode root; |
| 75 | + SubscriptionNode rootDollar; | |
| 75 | 76 | pthread_rwlock_t subscriptionsRwlock = PTHREAD_RWLOCK_INITIALIZER; |
| 76 | 77 | std::unordered_map<std::string, std::shared_ptr<Session>> sessionsById; |
| 77 | 78 | const std::unordered_map<std::string, std::shared_ptr<Session>> &sessionsByIdConst; |
| ... | ... | @@ -81,9 +82,9 @@ class SubscriptionStore |
| 81 | 82 | |
| 82 | 83 | Logger *logger = Logger::getInstance(); |
| 83 | 84 | |
| 84 | - void publishNonRecursively(const MqttPacket &packet, const std::vector<Subscription> &subscribers) const; | |
| 85 | + void publishNonRecursively(const MqttPacket &packet, const std::vector<Subscription> &subscribers, uint64_t &count) const; | |
| 85 | 86 | void publishRecursively(std::vector<std::string>::const_iterator cur_subtopic_it, std::vector<std::string>::const_iterator end, |
| 86 | - SubscriptionNode *this_node, const MqttPacket &packet) const; | |
| 87 | + SubscriptionNode *this_node, const MqttPacket &packet, uint64_t &count) const; | |
| 87 | 88 | |
| 88 | 89 | public: |
| 89 | 90 | SubscriptionStore(); |
| ... | ... | @@ -93,8 +94,8 @@ public: |
| 93 | 94 | void registerClientAndKickExistingOne(std::shared_ptr<Client> &client); |
| 94 | 95 | bool sessionPresent(const std::string &clientid); |
| 95 | 96 | |
| 96 | - void queuePacketAtSubscribers(const std::vector<std::string> &subtopics, const MqttPacket &packet); | |
| 97 | - void giveClientRetainedMessages(const std::shared_ptr<Session> &ses, const std::string &subscribe_topic, char max_qos); | |
| 97 | + void queuePacketAtSubscribers(const std::vector<std::string> &subtopics, const MqttPacket &packet, bool dollar = false); | |
| 98 | + uint64_t giveClientRetainedMessages(const std::shared_ptr<Session> &ses, const std::string &subscribe_topic, char max_qos); | |
| 98 | 99 | |
| 99 | 100 | void setRetainedMessage(const std::string &topic, const std::string &payload, char qos); |
| 100 | 101 | ... | ... |
threaddata.cpp
| ... | ... | @@ -157,6 +157,59 @@ void ThreadData::queuePasswdFileReload() |
| 157 | 157 | wakeUpThread(); |
| 158 | 158 | } |
| 159 | 159 | |
| 160 | +int ThreadData::getNrOfClients() const | |
| 161 | +{ | |
| 162 | + return clients_by_fd.size(); | |
| 163 | +} | |
| 164 | + | |
| 165 | +void ThreadData::incrementReceivedMessageCount() | |
| 166 | +{ | |
| 167 | + receivedMessageCount++; | |
| 168 | +} | |
| 169 | + | |
| 170 | +uint64_t ThreadData::getReceivedMessageCount() const | |
| 171 | +{ | |
| 172 | + return receivedMessageCount; | |
| 173 | +} | |
| 174 | + | |
| 175 | +/** | |
| 176 | + * @brief ThreadData::getReceivedMessagePerSecond gets the amount of seconds received, averaged over the last time this was called. | |
| 177 | + * @return | |
| 178 | + * | |
| 179 | + * Locking is not required, because the counter is not written to from here. | |
| 180 | + */ | |
| 181 | +uint64_t ThreadData::getReceivedMessagePerSecond() | |
| 182 | +{ | |
| 183 | + std::chrono::time_point<std::chrono::steady_clock> now = std::chrono::steady_clock::now(); | |
| 184 | + std::chrono::milliseconds msSinceLastTime = std::chrono::duration_cast<std::chrono::milliseconds>(now - receivedMessagePreviousTime); | |
| 185 | + uint64_t messagesTimes1000 = (receivedMessageCount - receivedMessageCountPrevious) * 1000; | |
| 186 | + uint64_t result = messagesTimes1000 / (msSinceLastTime.count() + 1); // branchless avoidance of div by 0; | |
| 187 | + receivedMessagePreviousTime = now; | |
| 188 | + receivedMessageCountPrevious = receivedMessageCount; | |
| 189 | + return result; | |
| 190 | +} | |
| 191 | + | |
| 192 | +void ThreadData::incrementSentMessageCount(uint64_t n) | |
| 193 | +{ | |
| 194 | + sentMessageCount += n; | |
| 195 | +} | |
| 196 | + | |
| 197 | +uint64_t ThreadData::getSentMessageCount() const | |
| 198 | +{ | |
| 199 | + return sentMessageCount; | |
| 200 | +} | |
| 201 | + | |
| 202 | +uint64_t ThreadData::getSentMessagePerSecond() | |
| 203 | +{ | |
| 204 | + std::chrono::time_point<std::chrono::steady_clock> now = std::chrono::steady_clock::now(); | |
| 205 | + std::chrono::milliseconds msSinceLastTime = std::chrono::duration_cast<std::chrono::milliseconds>(now - sentMessagePreviousTime); | |
| 206 | + uint64_t messagesTimes1000 = (sentMessageCount - sentMessageCountPrevious) * 1000; | |
| 207 | + uint64_t result = messagesTimes1000 / (msSinceLastTime.count() + 1); // branchless avoidance of div by 0; | |
| 208 | + sentMessagePreviousTime = now; | |
| 209 | + sentMessageCountPrevious = sentMessageCount; | |
| 210 | + return result; | |
| 211 | +} | |
| 212 | + | |
| 160 | 213 | // TODO: profile how fast hash iteration is. Perhaps having a second list/vector is beneficial? |
| 161 | 214 | void ThreadData::doKeepAliveCheck() |
| 162 | 215 | { | ... | ... |
threaddata.h
| ... | ... | @@ -28,6 +28,7 @@ License along with FlashMQ. If not, see <https://www.gnu.org/licenses/>. |
| 28 | 28 | #include <mutex> |
| 29 | 29 | #include <shared_mutex> |
| 30 | 30 | #include <functional> |
| 31 | +#include <chrono> | |
| 31 | 32 | |
| 32 | 33 | #include "forward_declarations.h" |
| 33 | 34 | |
| ... | ... | @@ -47,6 +48,15 @@ class ThreadData |
| 47 | 48 | std::shared_ptr<SubscriptionStore> subscriptionStore; |
| 48 | 49 | Logger *logger; |
| 49 | 50 | |
| 51 | + uint64_t receivedMessageCount = 0; | |
| 52 | + uint64_t receivedMessageCountPrevious = 0; | |
| 53 | + std::chrono::time_point<std::chrono::steady_clock> receivedMessagePreviousTime = std::chrono::steady_clock::now(); | |
| 54 | + | |
| 55 | + uint64_t sentMessageCount = 0; | |
| 56 | + uint64_t sentMessageCountPrevious = 0; | |
| 57 | + std::chrono::time_point<std::chrono::steady_clock> sentMessagePreviousTime = std::chrono::steady_clock::now(); | |
| 58 | + | |
| 59 | + | |
| 50 | 60 | void reload(std::shared_ptr<Settings> settings); |
| 51 | 61 | void wakeUpThread(); |
| 52 | 62 | void doKeepAliveCheck(); |
| ... | ... | @@ -81,6 +91,16 @@ public: |
| 81 | 91 | void queueQuit(); |
| 82 | 92 | void waitForQuit(); |
| 83 | 93 | void queuePasswdFileReload(); |
| 94 | + | |
| 95 | + int getNrOfClients() const; | |
| 96 | + | |
| 97 | + void incrementReceivedMessageCount(); | |
| 98 | + uint64_t getReceivedMessageCount() const; | |
| 99 | + uint64_t getReceivedMessagePerSecond(); | |
| 100 | + | |
| 101 | + void incrementSentMessageCount(uint64_t n); | |
| 102 | + uint64_t getSentMessageCount() const; | |
| 103 | + uint64_t getSentMessagePerSecond(); | |
| 84 | 104 | }; |
| 85 | 105 | |
| 86 | 106 | #endif // THREADDATA_H | ... | ... |
threadlocalutils.cpp
| ... | ... | @@ -60,6 +60,9 @@ bool Utils::isValidUtf8(const std::string &s, bool alsoCheckInvalidPublishChars) |
| 60 | 60 | std::memcpy(topicCopy.data(), s.c_str(), len); |
| 61 | 61 | std::memset(&topicCopy.data()[len], 0x20, 16); // I fill out with spaces, as valid chars |
| 62 | 62 | |
| 63 | + if (alsoCheckInvalidPublishChars && len > 0 && s[0] == '$') | |
| 64 | + return false; | |
| 65 | + | |
| 63 | 66 | int n = 0; |
| 64 | 67 | const char *i = topicCopy.data(); |
| 65 | 68 | while (n < len) | ... | ... |
utils.cpp
| ... | ... | @@ -57,6 +57,9 @@ bool topicsMatch(const std::string &subscribeTopic, const std::string &publishTo |
| 57 | 57 | if (subscribeTopic.find("+") == std::string::npos && subscribeTopic.find("#") == std::string::npos) |
| 58 | 58 | return subscribeTopic == publishTopic; |
| 59 | 59 | |
| 60 | + if (!subscribeTopic.empty() && !publishTopic.empty() && publishTopic[0] == '$' && subscribeTopic[0] != '$') | |
| 61 | + return false; | |
| 62 | + | |
| 60 | 63 | const std::vector<std::string> subscribeParts = splitToVector(subscribeTopic, '/'); |
| 61 | 64 | const std::vector<std::string> publishParts = splitToVector(publishTopic, '/'); |
| 62 | 65 | ... | ... |