The Multicast Replay sample turns recorded handler log data back into multicast traffic with MulticastSender. It can replay explicit log files or automatically select matching sample data for a channel, which makes it useful for lab testing receivers, validating connectivity files, and simulating primary or arbitrated feed delivery.
The example uses files included with the samples. Omitting --log makes the sample gather matching channel 310 log files from ../Data.
Replay sample data for channel 310 using generated testing connectivity and automatically selected log files:
Replay to receivers on the local network by selecting the sending interface and setting an explicit TTL. Replace eth0 with the required interface name on Linux or interface IPv4 address on Windows:
namespace {
const std::string SampleDataLocation = "../Data";
class MulticastReplayConfiguration : public virtual ConfigurationBase
{
public:
bool filesToReplay(FileList& files) const
{
const Messaging::Char* const option = "log";
const auto args = options().args(option);
if (args)
{
const auto argQty = args->size();
if (0 == argQty)
throwBadOptionArgQty(option, argQty, 1, static_cast<size_t>(-1));
std::copy(args->begin(), args->end(), std::back_inserter(files));
return true;
}
return false;
}
LogPlayerSettings& apply(LogPlayerSettings& settings) const
{
applySendDelay(settings);
settings.
delayPolicy(CombinedReplayDelayer::service());
return settings;
}
FeedSettings& apply(FeedSettings& settings) const
{
applyFeedLayout(settings);
return settings;
}
Messaging::TimeSpan arbitrageDelay() const
{
const Messaging::Char* const option = "arbitrage-delay";
const auto args = argsFor(option, 1, 1);
const auto delay = args ? convertUInt64(option, (*args)[0]) : 0;
return makeMicrosecondSpan(option, delay);
}
Test::MulticastTargetOptions multicastTargetOptions() const
{
const Messaging::Char* const option = "ttl";
const auto args = argsFor(option, 1, 1);
const auto ttl = args ? convertUInt64(option, (*args)[0]) : 0;
Test::MulticastTargetOptions result;
return result;
}
protected:
MulticastReplayConfiguration(size_t qty, char** args)
: ConfigurationBase(qty, args)
{
}
void showOptions(std::ostream& out) const override
{
out << " --log <file1 [file2 [..]]>" << std::endl
<< "\tTells to replay the given list of log files. " << std::endl
<< "\tIf omitted, sample data is replayed. " << std::endl
<< std::endl
<< " --send-delay <natural|microseconds>" << std::endl
<< "\tDefines delay before sending another packet to the same source. " << std::endl
<< "\tUse natural to preserve delays from the log file. Default is natural. " << std::endl
<< std::endl
<< " --source-switch-delay <microseconds>" << std::endl
<< "\tDefines delay when target NetFeedType changes. Default is 1000. " << std::endl
<< std::endl
<< " --update-sending-time <true|false>" << std::endl
<< "\tUpdates SendingTime in the Binary Packet Header before sending each packet. "
<< "Default is false. " << std::endl
<< std::endl
<< " --primary-feed <A|B>" << std::endl
<< "\tDefines feed used as the primary feed when sending data. "
<< "Without arbitrage, only this feed is used. Default is A. " << std::endl
<< std::endl
<< " --simulate-arbitrage" << std::endl
<< "\tSends data through both feeds to simulate arbitrage. " << std::endl
<< std::endl
<< " --arbitrage-delay <microseconds>" << std::endl
<< "\tDefines delay before sending the same packet to the secondary feed. "
<< "Default is 0. " << std::endl
<< std::endl
<< " --iface-A <interface>" << std::endl
<< "\tSpecifies network interface to use for feed A. Default is "
<< Test::loopbackNetworkInterface() << ". " << std::endl
<< std::endl
<< " --iface-B <interface>" << std::endl
<< "\tSpecifies network interface to use for feed B. Default is "
<< Test::loopbackNetworkInterface() << ". " << std::endl
<< std::endl
<< " --ttl <0..32>" << std::endl
<< "\tDefines the IPv4 multicast time-to-live. Default is 0. " << std::endl
<< "\tA positive value requires " << Test::MulticastEgressEnvironmentVariable << ' '
<< "to be enabled. " << std::endl
<< std::endl
<< " --packets-count <count>" << std::endl
<< "\tStops replay after the specified number of packets sent to incremental "
<< "feed targets. Historical packets remapped to incremental are included. " << std::endl
<< std::endl;
}
private:
std::string networkInterface(const Messaging::Char* option) const
{
const auto args = argsFor(option, 1, 1);
return args ? (*args)[0] : Test::loopbackNetworkInterface();
}
const OptionArgs* argsFor(const Messaging::Char* option, size_t minQty, size_t maxQty) const
{
const auto args = options().args(option);
if (args)
{
const auto argQty = args->size();
if (argQty < minQty || maxQty < argQty)
throwBadOptionArgQty(option, argQty, minQty, maxQty);
}
return args;
}
static Messaging::TimeSpan makeMicrosecondSpan(const Messaging::Char* option, Messaging::UInt64 microseconds)
{
typedef Messaging::TimeSpan::Microseconds Microseconds;
if (microseconds > static_cast<Messaging::UInt64>((std::numeric_limits<Microseconds>::max)()))
throwOptionArgConversionFailed(option);
return Messaging::MicrosecondSpan(static_cast<Microseconds>(microseconds));
}
static Messaging::UInt64 convertUInt64(const Messaging::Char* option, const std::string& value)
{
return OptionArgConverter<Messaging::UInt64>(option)(
value);
}
void applySendDelay(LogPlayerSettings& settings) const
{
const Messaging::Char* const option = "send-delay";
const auto args = argsFor(option, 1, 1);
if (!args || (*args)[0] == "natural")
{
return;
}
settings.
sendDelay(makeMicrosecondSpan(option, convertUInt64(option, (*args)[0])));
}
Messaging::TimeSpan sourceSwitchDelay() const
{
const Messaging::Char* const option = "source-switch-delay";
const auto args = argsFor(option, 1, 1);
const auto delay = args ? convertUInt64(option, (*args)[0]) : 1000;
return makeMicrosecondSpan(option, delay);
}
bool updateSendingTime() const
{
const Messaging::Char* const option = "update-sending-time";
const auto args = argsFor(option, 1, 1);
return args ? OptionArgConverter<bool>(option)((*args)[0]) : false;
}
NetFeedRole::Enum primaryFeed() const
{
const Messaging::Char* const option = "primary-feed";
const auto args = argsFor(option, 1, 1);
return args ? OptionArgConverter<NetFeedRole::Enum>(option)((*args)[0]) : NetFeedRole::A;
}
bool simulateArbitrage() const
{
const Messaging::Char* const option = "simulate-arbitrage";
return nullptr != argsFor(option, 0, 0);
}
static RecoveryFeedLayout::Enum recoveryFeedLayout(NetFeedRole::Enum primaryFeed, bool arbitrage)
{
switch (primaryFeed)
{
case NetFeedRole::A:
return arbitrage ? RecoveryFeedLayout::FeedAWithFailoverToFeedB : RecoveryFeedLayout::FeedAOnly;
case NetFeedRole::B:
return arbitrage ? RecoveryFeedLayout::FeedBWithFailoverToFeedA : RecoveryFeedLayout::FeedBOnly;
default:
throwOptionArgConversionFailed("primary-feed");
return RecoveryFeedLayout::FeedAOnly;
}
}
static RealtimeFeedLayout::Enum realtimeFeedLayout(NetFeedRole::Enum primaryFeed, bool arbitrage)
{
switch (primaryFeed)
{
case NetFeedRole::A:
return arbitrage ? RealtimeFeedLayout::FeedAWithFailoverToFeedB : RealtimeFeedLayout::FeedAOnly;
case NetFeedRole::B:
return arbitrage ? RealtimeFeedLayout::FeedBWithFailoverToFeedA : RealtimeFeedLayout::FeedBOnly;
default:
throwOptionArgConversionFailed("primary-feed");
return RealtimeFeedLayout::FeedAOnly;
}
}
void applyFeedLayout(FeedSettings& settings) const
{
const auto primary = primaryFeed();
const auto arbitrage = simulateArbitrage();
const auto recoveryLayout = recoveryFeedLayout(primary, arbitrage);
}
size_t packetsCount() const
{
const Messaging::Char* const option = "packets-count";
const auto args = argsFor(option, 1, 1);
if (!args)
return (std::numeric_limits<size_t>::max)();
const auto count = convertUInt64(option, (*args)[0]);
if (count > static_cast<Messaging::UInt64>((std::numeric_limits<size_t>::max)()))
throwOptionArgConversionFailed(option);
return static_cast<size_t>(count);
}
};
class Configuration
: public ChannelConfiguration
, public ConnectivityConfiguration
, public MulticastReplayConfiguration
{
public:
Configuration(size_t qty, char** args)
: ConfigurationBase(qty, args)
, ChannelConfiguration(qty, args, 310)
, ConnectivityConfiguration(qty, args)
, MulticastReplayConfiguration(qty, args)
{
}
bool connectivityFileSpecified() const
{
return exist(options(), "connectivity");
}
private:
void showOptions(std::ostream& out) const override
{
ChannelConfiguration::showOptions(out);
out << " --connectivity <file>" << std::endl
<< "\tTells to use the given connectivity configuration file. " << std::endl
<< "\tIf omitted, generated testing connectivity is used. " << std::endl
<< "\tOutgoing destinations outside 239.0.0.0/8 require "
<< Test::AllowAnyMulticastDestinationEnvironmentVariable << " to be enabled. " << std::endl
<< std::endl;
MulticastReplayConfiguration::showOptions(out);
}
};
void identify()
{
std::cout <<
"Multicast Replay Sample for CME MDP Premium Market Data Handler, v" <<
toStr(
Version::current()) <<
". "
<< std::endl
<< std::endl;
}
FileList resolveReplayLogs(
const Configuration& configuration)
{
configuration.filesToReplay(logs);
if (logs.empty())
return logs;
}
}
int main(int argc, char** argv)
{
identify();
try
{
const Configuration configuration(argc, argv);
if (configuration.show())
{
configuration.show(std::cout);
return 0;
}
LogPlayerSettings logPlayerSettings(configuration.channel(), resolveReplayLogs(configuration));
configuration.apply(logPlayerSettings);
if (configuration.connectivityFileSpecified())
else
configuration.apply(feedSettings);
Test::MulticastSender sender{feedSettings, configuration.arbitrageDelay(), logPlayerSettings.delayPolicy(), configuration.multicastTargetOptions()};
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
return 1;
}
return 0;
}