/* * UDPWorkload.actor.cpp * * This source file is part of the FoundationDB open source project * * Copyright 2013-2022 Apple Inc. and the FoundationDB project authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "fdbclient/FDBTypes.h" #include "fdbclient/ReadYourWrites.h" #include "fdbserver/workloads/workloads.actor.h" #include "flow/ActorCollection.h" #include "flow/Arena.h" #include "flow/Error.h" #include "flow/IRandom.h" #include "flow/flow.h" #include "flow/network.h" #include "flow/serialize.h" #include #include #include #include #include #include #include "flow/actorcompiler.h" // has to be last include namespace { struct UDPWorkload : TestWorkload { constexpr static const char* name = "UDPWorkload"; // config Key keyPrefix; double runFor; int minPort, maxPort; // members NetworkAddress serverAddress; Reference serverSocket; std::unordered_map sent, received, acked, successes; PromiseStream toAck; UDPWorkload(WorkloadContext const& wcx) : TestWorkload(wcx) { keyPrefix = getOption(options, "keyPrefix"_sr, "/udp/"_sr); runFor = getOption(options, "runFor"_sr, 60.0); minPort = getOption(options, "minPort"_sr, 5000); maxPort = getOption(options, "minPort"_sr, 6000); for (auto p : { minPort, maxPort }) { ASSERT(p > 0 && p < std::numeric_limits::max()); } } std::string description() const override { return name; } ACTOR static Future _setup(UDPWorkload* self, Database cx) { state NetworkAddress localAddress(g_network->getLocalAddress().ip, deterministicRandom()->randomInt(self->minPort, self->maxPort + 1), true, false); state Key key = self->keyPrefix.withSuffix(BinaryWriter::toValue(self->clientId, Unversioned())); state Value serializedLocalAddress = BinaryWriter::toValue(localAddress, IncludeVersion()); state ReadYourWritesTransaction tr(cx); Reference s = wait(INetworkConnections::net()->createUDPSocket(localAddress.isV6())); self->serverSocket = std::move(s); self->serverSocket->bind(localAddress); self->serverAddress = localAddress; loop { try { Optional v = wait(tr.get(key)); if (v.present()) { return Void(); } tr.set(key, serializedLocalAddress); wait(tr.commit()); return Void(); } catch (Error& e) { wait(tr.onError(e)); } } } Future setup(Database const& cx) override { return _setup(this, cx); } class Message { int _type = 0; public: enum class Type : uint8_t { PING, PONG }; Message() {} explicit Message(Type t) { switch (t) { case Type::PING: _type = 0; break; case Type::PONG: _type = 1; break; default: UNSTOPPABLE_ASSERT(false); } } Type type() const { switch (_type) { case 0: return Type::PING; case 1: return Type::PONG; default: UNSTOPPABLE_ASSERT(false); } } template void serialize(Ar& ar) { serializer(ar, _type); } }; static Message ping() { return Message{ Message::Type::PING }; } static Message pong() { return Message{ Message::Type::PONG }; } ACTOR static Future _receiver(UDPWorkload* self) { state Standalone packetString = makeString(IUDPSocket::MAX_PACKET_SIZE); state uint8_t* packet = mutateString(packetString); state NetworkAddress peerAddress; loop { int sz = wait(self->serverSocket->receiveFrom(packet, packet + IUDPSocket::MAX_PACKET_SIZE, &peerAddress)); auto msg = BinaryReader::fromStringRef(packetString.substr(0, sz), IncludeVersion()); if (msg.type() == Message::Type::PONG) { self->successes[peerAddress] += 1; } else if (msg.type() == Message::Type::PING) { self->received[peerAddress] += 1; self->toAck.send(peerAddress); } else { UNSTOPPABLE_ASSERT(false); } } } ACTOR static Future serverSender(UDPWorkload* self, std::vector* remotes) { state Standalone packetString; state NetworkAddress peer; loop { choose { when(wait(delay(0.1))) { peer = deterministicRandom()->randomChoice(*remotes); packetString = BinaryWriter::toValue(Message{ Message::Type::PING }, IncludeVersion()); self->sent[peer] += 1; } when(NetworkAddress p = waitNext(self->toAck.getFuture())) { peer = p; packetString = BinaryWriter::toValue(pong(), IncludeVersion()); self->acked[peer] += 1; } } int res = wait(self->serverSocket->sendTo(packetString.begin(), packetString.end(), peer)); ASSERT(res == packetString.size()); } } ACTOR static Future clientReceiver(UDPWorkload* self, Reference socket, Future done) { state Standalone packetString = makeString(IUDPSocket::MAX_PACKET_SIZE); state uint8_t* packet = mutateString(packetString); state NetworkAddress peer; state Future finished = Never(); loop { choose { when(int sz = wait(socket->receiveFrom(packet, packet + IUDPSocket::MAX_PACKET_SIZE, &peer))) { auto res = BinaryReader::fromStringRef(packetString.substr(0, sz), IncludeVersion()); ASSERT(res.type() == Message::Type::PONG); self->successes[peer] += 1; } when(wait(done)) { finished = delay(1.0); done = Never(); } when(wait(finished)) { return Void(); } } } } ACTOR static Future clientSender(UDPWorkload* self, std::vector* remotes) { state AsyncVar> socket; state Standalone sendString; state ActorCollection actors(false); state NetworkAddress peer; loop { choose { when(wait(delay(0.1))) {} when(wait(actors.getResult())) { UNSTOPPABLE_ASSERT(false); } } if (!socket.get().isValid() || deterministicRandom()->random01() < 0.05) { peer = deterministicRandom()->randomChoice(*remotes); Reference s = wait(INetworkConnections::net()->createUDPSocket(peer)); socket.set(s); socket = s; actors.add(clientReceiver(self, socket.get(), socket.onChange())); } sendString = BinaryWriter::toValue(ping(), IncludeVersion()); int res = wait(socket.get()->send(sendString.begin(), sendString.end())); ASSERT(res == sendString.size()); self->sent[peer] += 1; } } ACTOR static Future _start(UDPWorkload* self, Database cx) { state ReadYourWritesTransaction tr(cx); state std::vector remotes; loop { try { RangeResult range = wait(tr.getRange(prefixRange(self->keyPrefix), CLIENT_KNOBS->TOO_MANY)); ASSERT(!range.more); for (auto const& p : range) { auto cID = BinaryReader::fromStringRefclientId)>( p.key.removePrefix(self->keyPrefix), Unversioned()); if (cID != self->clientId) { remotes.emplace_back(BinaryReader::fromStringRef(p.value, IncludeVersion())); } } break; } catch (Error& e) { wait(tr.onError(e)); } } wait(clientSender(self, &remotes) && serverSender(self, &remotes) && _receiver(self)); UNSTOPPABLE_ASSERT(false); return Void(); } Future start(Database const& cx) override { return delay(runFor) || _start(this, cx); } Future check(Database const& cx) override { return true; } void getMetrics(std::vector& m) override { unsigned totalReceived = 0, totalSent = 0, totalAcked = 0, totalSuccess = 0; for (const auto& p : sent) { totalSent += p.second; } for (const auto& p : received) { totalReceived += p.second; } for (const auto& p : acked) { totalAcked += p.second; } for (const auto& p : successes) { totalSuccess += p.second; } m.emplace_back("Sent", totalSent, Averaged::False); m.emplace_back("Received", totalReceived, Averaged::False); m.emplace_back("Acknknowledged", totalAcked, Averaged::False); m.emplace_back("Successes", totalSuccess, Averaged::False); } }; } // namespace WorkloadFactory UDPWorkloadFactory(UDPWorkload::name);