blob: 4abfcecfe7ac2a109424b8167ccd90c57c13ad14 [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
/**
* Copyright (C) 2014 Arizona Board of Regents
*
* This file is part of ndn-tlv-ping (Ping Application for Named Data Networking).
*
* ndn-tlv-ping is a free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* ndn-tlv-ping is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* ndn-tlv-ping, e.g., in LICENSE file. If not, see <http://www.gnu.org/licenses/>.
*
* @author: Jerald Paul Abraham <jeraldabraham@email.arizona.edu>
*/
#include <ndn-cxx/face.hpp>
#include <ndn-cxx/name.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/noncopyable.hpp>
namespace ndn {
namespace ping {
class NdnPing : boost::noncopyable
{
public:
explicit
NdnPing(char* programName)
: m_programName(programName)
, m_isAllowCachingSet(false)
, m_isPrintTimestampSet(false)
, m_hasError(false)
, m_totalPings(-1)
, m_startPingNumber(-1)
, m_pingsSent(0)
, m_pingsReceived(0)
, m_pingInterval(getPingMinimumInterval())
, m_clientIdentifier(0)
, m_pingTimeoutThreshold(getPingTimeoutThreshold())
, m_outstanding(0)
, m_face(m_ioService)
{
}
class PingStatistics : boost::noncopyable
{
public:
explicit
PingStatistics()
: m_sentPings(0)
, m_receivedPings(0)
, m_pingStartTime(time::steady_clock::now())
, m_minimumRoundTripTime(std::numeric_limits<double>::max())
, m_averageRoundTripTimeData(0)
, m_standardDeviationRoundTripTimeData(0)
{
}
void
addToPingStatistics(time::nanoseconds roundTripNanoseconds)
{
double roundTripTime = roundTripNanoseconds.count() / 1000000.0;
if (roundTripTime < m_minimumRoundTripTime)
m_minimumRoundTripTime = roundTripTime;
if (roundTripTime > m_maximumRoundTripTime)
m_maximumRoundTripTime = roundTripTime;
m_averageRoundTripTimeData += roundTripTime;
m_standardDeviationRoundTripTimeData += roundTripTime * roundTripTime;
}
public:
int m_sentPings;
int m_receivedPings;
time::steady_clock::TimePoint m_pingStartTime;
double m_minimumRoundTripTime;
double m_maximumRoundTripTime;
double m_averageRoundTripTimeData;
double m_standardDeviationRoundTripTimeData;
};
void
usage()
{
std::cout << "\n Usage:\n " << m_programName << " ndn:/name/prefix [options]\n"
" Ping a NDN name prefix using Interests with name"
" ndn:/name/prefix/ping/number.\n"
" The numbers in the Interests are randomly generated unless specified.\n"
" [-i interval] - set ping interval in seconds (minimum "
<< getPingMinimumInterval().count() << " milliseconds)\n"
" [-c count] - set total number of pings\n"
" [-n number] - set the starting number, the number is incremented by 1"
" after each Interest\n"
" [-p identifier] - add identifier to the Interest names before the"
" numbers to avoid conflict\n"
" [-a] - allow routers to return stale Data from cache\n"
" [-t] - print timestamp with messages\n"
" [-h] - print this message and exit\n\n";
exit(1);
}
time::milliseconds
getPingMinimumInterval()
{
return time::milliseconds(1000);
}
time::milliseconds
getPingTimeoutThreshold()
{
return time::milliseconds(4000);
}
void
setTotalPings(int totalPings)
{
if (totalPings <= 0)
usage();
m_totalPings = totalPings;
}
void
setPingInterval(int pingInterval)
{
if (pingInterval < getPingMinimumInterval().count())
usage();
m_pingInterval = time::milliseconds(pingInterval);
}
void
setStartPingNumber(int64_t startPingNumber)
{
if (startPingNumber < 0)
usage();
m_startPingNumber = startPingNumber;
}
void
setAllowCaching()
{
m_isAllowCachingSet = true;
}
void
setPrintTimestamp()
{
m_isPrintTimestampSet = true;
}
void
setClientIdentifier(char* clientIdentifier)
{
m_clientIdentifier = clientIdentifier;
if (strlen(clientIdentifier) == 0)
usage();
while (*clientIdentifier != '\0') {
if (isalnum(*clientIdentifier) == 0)
usage();
clientIdentifier++;
}
}
void
setPrefix(char* prefix)
{
m_prefix = prefix;
}
bool
hasError() const
{
return m_hasError;
}
void
onData(const ndn::Interest& interest,
Data& data,
time::steady_clock::TimePoint timePoint)
{
std::string pingReference = interest.getName().toUri();
m_pingsReceived++;
m_pingStatistics.m_receivedPings++;
time::nanoseconds roundTripTime = time::steady_clock::now() - timePoint;
if (m_isPrintTimestampSet)
std::cout << time::toIsoString(time::system_clock::now()) << " - ";
std::cout << "Content From " << m_prefix;
std::cout << " - Ping Reference = " <<
interest.getName().getSubName(interest.getName().size()-1).toUri().substr(1);
std::cout << " \t- Round Trip Time = " <<
roundTripTime.count() / 1000000.0 << " ms" << std::endl;
m_pingStatistics.addToPingStatistics(roundTripTime);
this->finish();
}
void
onTimeout(const ndn::Interest& interest)
{
if (m_isPrintTimestampSet)
std::cout << time::toIsoString(time::system_clock::now()) << " - ";
std::cout << "Timeout From " << m_prefix;
std::cout << " - Ping Reference = " <<
interest.getName().getSubName(interest.getName().size()-1).toUri().substr(1);
std::cout << std::endl;
this->finish();
}
void
printPingStatistics()
{
std::cout << "\n\n=== " << " Ping Statistics For "<< m_prefix <<" ===" << std::endl;
std::cout << "Sent=" << m_pingStatistics.m_sentPings;
std::cout << ", Received=" << m_pingStatistics.m_receivedPings;
double packetLossRate = m_pingStatistics.m_sentPings - m_pingStatistics.m_receivedPings;
packetLossRate /= m_pingStatistics.m_sentPings;
std::cout << ", Packet Loss=" << packetLossRate * 100.0 << "%";
if (m_pingStatistics.m_sentPings != m_pingStatistics.m_receivedPings)
m_hasError = true;
std::cout << ", Total Time=" << m_pingStatistics.m_averageRoundTripTimeData << " ms\n";
if (m_pingStatistics.m_receivedPings > 0) {
double averageRoundTripTime =
m_pingStatistics.m_averageRoundTripTimeData / m_pingStatistics.m_receivedPings;
double standardDeviationRoundTripTime =
m_pingStatistics.m_standardDeviationRoundTripTimeData / m_pingStatistics.m_receivedPings;
standardDeviationRoundTripTime -= averageRoundTripTime * averageRoundTripTime;
standardDeviationRoundTripTime = std::sqrt(standardDeviationRoundTripTime);
std::cout << "Round Trip Time (Min/Max/Avg/MDev) = (";
std::cout << m_pingStatistics.m_minimumRoundTripTime << "/";
std::cout << m_pingStatistics.m_maximumRoundTripTime << "/";
std::cout << averageRoundTripTime << "/";
std::cout << standardDeviationRoundTripTime << ") ms\n";
}
std::cout << std::endl;
}
void
performPing(boost::asio::deadline_timer* deadlineTimer)
{
if ((m_totalPings < 0) || (m_pingsSent < m_totalPings)) {
m_pingsSent++;
m_pingStatistics.m_sentPings++;
//Perform Ping
char pingNumberString[20];
Name pingPacketName(m_prefix);
pingPacketName.append("ping");
if (m_clientIdentifier != 0)
pingPacketName.append(m_clientIdentifier);
std::memset(pingNumberString, 0, 20);
if (m_startPingNumber < 0)
m_startPingNumber = std::rand();
sprintf(pingNumberString, "%lld", static_cast<long long int>(m_startPingNumber));
pingPacketName.append(pingNumberString);
ndn::Interest interest(pingPacketName);
if (m_isAllowCachingSet)
interest.setMustBeFresh(false);
else
interest.setMustBeFresh(true);
interest.setInterestLifetime(m_pingTimeoutThreshold);
interest.setNonce(m_startPingNumber);
m_startPingNumber++;
try {
m_face.expressInterest(interest,
std::bind(&NdnPing::onData, this, _1, _2,
time::steady_clock::now()),
std::bind(&NdnPing::onTimeout, this, _1));
deadlineTimer->expires_at(deadlineTimer->expires_at() +
boost::posix_time::millisec(m_pingInterval.count()));
deadlineTimer->async_wait(bind(&NdnPing::performPing,
this,
deadlineTimer));
}
catch (std::exception& e) {
std::cerr << "ERROR: " << e.what() << std::endl;
}
++m_outstanding;
}
else {
this->finish();
}
}
void
finish()
{
if (--m_outstanding >= 0) {
return;
}
m_face.shutdown();
printPingStatistics();
m_ioService.stop();
}
void
signalHandler()
{
m_face.shutdown();
printPingStatistics();
exit(1);
}
void
run()
{
std::cout << "\n=== Pinging " << m_prefix << " ===\n" <<std::endl;
boost::asio::signal_set signalSet(m_ioService, SIGINT, SIGTERM);
signalSet.async_wait(bind(&NdnPing::signalHandler, this));
boost::asio::deadline_timer deadlineTimer(m_ioService,
boost::posix_time::millisec(0));
deadlineTimer.async_wait(bind(&NdnPing::performPing,
this,
&deadlineTimer));
try {
m_face.processEvents();
}
catch (std::exception& e) {
std::cerr << "ERROR: " << e.what() << std::endl;
m_hasError = true;
m_ioService.stop();
}
}
private:
char* m_programName;
bool m_isAllowCachingSet;
bool m_isPrintTimestampSet;
bool m_hasError;
int m_totalPings;
int64_t m_startPingNumber;
int m_pingsSent;
int m_pingsReceived;
time::milliseconds m_pingInterval;
char* m_clientIdentifier;
time::milliseconds m_pingTimeoutThreshold;
char* m_prefix;
PingStatistics m_pingStatistics;
ssize_t m_outstanding;
boost::asio::io_service m_ioService;
Face m_face;
};
int
main(int argc, char* argv[])
{
std::srand(::time(0));
int res;
NdnPing program(argv[0]);
while ((res = getopt(argc, argv, "htai:c:n:p:")) != -1)
{
switch (res) {
case 'a':
program.setAllowCaching();
break;
case 'c':
program.setTotalPings(atoi(optarg));
break;
case 'h':
program.usage();
break;
case 'i':
program.setPingInterval(atoi(optarg));
break;
case 'n':
try {
program.setStartPingNumber(boost::lexical_cast<int64_t>(optarg));
}
catch (boost::bad_lexical_cast&) {
program.usage();
}
break;
case 'p':
program.setClientIdentifier(optarg);
break;
case 't':
program.setPrintTimestamp();
break;
default:
program.usage();
break;
}
}
argc -= optind;
argv += optind;
if (argv[0] == 0)
program.usage();
program.setPrefix(argv[0]);
program.run();
std::cout << std::endl;
if (program.hasError())
return 1;
else
return 0;
}
} // namespace ping
} // namespace ndn
int
main(int argc, char** argv)
{
return ndn::ping::main(argc, argv);
}