2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #include <boost/accumulators/statistics/median.hpp>
26 #include <boost/accumulators/statistics/mean.hpp>
27 #include <boost/accumulators/accumulators.hpp>
29 #include <boost/accumulators/statistics.hpp>
33 #include "dnsparser.hh"
36 #include "dnswriter.hh"
37 #include "dnsrecords.hh"
39 #include "threadname.hh"
40 #include <netinet/tcp.h>
41 #include <boost/array.hpp>
42 #include <boost/program_options.hpp>
46 namespace po
= boost::program_options
;
48 po::variables_map g_vm
;
52 unsigned int g_timeoutMsec
;
53 AtomicCounter g_networkErrors
, g_otherErrors
, g_OK
, g_truncates
, g_authAnswers
, g_timeOuts
;
56 static unsigned int makeUsec(const struct timeval
& tv
)
58 return 1000000*tv
.tv_sec
+ tv
.tv_usec
;
61 /* On Linux, run echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle
62 to prevent running out of free TCP ports */
66 BenchQuery(const std::string
& qname_
, uint16_t qtype_
) : qname(qname_
), qtype(qtype_
), udpUsec(0), tcpUsec(0), answerSecond(0) {}
67 BenchQuery(): qtype(0), udpUsec(0), tcpUsec(0), answerSecond(0) {}
70 uint32_t udpUsec
, tcpUsec
;
74 static void doQuery(BenchQuery
* q
)
77 vector
<uint8_t> packet
;
78 DNSPacketWriter
pw(packet
, q
->qname
, q
->qtype
);
82 struct timeval tv
, now
;
86 Socket
udpsock(g_dest
.sin4
.sin_family
, SOCK_DGRAM
);
88 udpsock
.sendTo(string(packet
.begin(), packet
.end()), g_dest
);
90 res
= waitForData(udpsock
.getHandle(), 0, 1000 * g_timeoutMsec
);
92 throw NetworkError("Error waiting for response");
98 udpsock
.recvFrom(reply
, origin
);
100 gettimeofday(&now
, 0);
101 q
->udpUsec
= makeUsec(now
- tv
);
104 MOADNSParser
mdp(false, reply
);
110 Socket
sock(g_dest
.sin4
.sin_family
, SOCK_STREAM
);
112 if(setsockopt(sock
.getHandle(),SOL_SOCKET
,SO_REUSEADDR
,(char*)&tmp
,sizeof tmp
)<0)
113 throw runtime_error("Unable to set socket reuse: "+stringerror());
115 if(g_tcpNoDelay
&& setsockopt(sock
.getHandle(), IPPROTO_TCP
, TCP_NODELAY
,(char*)&tmp
,sizeof tmp
)<0)
116 throw runtime_error("Unable to set socket no delay: "+stringerror());
118 sock
.connect(g_dest
);
119 uint16_t len
= htons(packet
.size());
120 string
tcppacket((char*)& len
, 2);
121 tcppacket
.append(packet
.begin(), packet
.end());
123 sock
.writen(tcppacket
);
125 res
= waitForData(sock
.getHandle(), 0, 1000 * g_timeoutMsec
);
127 throw NetworkError("Error waiting for response");
133 if(sock
.read((char *) &len
, 2) != 2)
134 throw PDNSException("tcp read failed");
137 std::unique_ptr
<char[]> creply(new char[len
]);
141 numread
=sock
.read(creply
.get()+n
, len
-n
);
143 throw PDNSException("tcp read failed");
147 reply
=string(creply
.get(), len
);
149 gettimeofday(&now
, 0);
150 q
->tcpUsec
= makeUsec(now
- tv
);
151 q
->answerSecond
= now
.tv_sec
;
153 MOADNSParser
mdp(false, reply
);
154 // cout<<"Had correct TCP/IP response, "<<mdp.d_answers.size()<<" answers, aabit="<<mdp.d_header.aa<<endl;
159 catch(NetworkError
& ne
)
161 cerr
<<"Network error: "<<ne
.what()<<endl
;
169 /* read queries from stdin, put in vector
170 launch n worker threads, each picks a query using AtomicCounter
171 If a worker reaches the end of its queue, it stops */
175 vector
<BenchQuery
> g_queries
;
179 setThreadName("dnstcpb/worker");
181 unsigned int pos
= g_pos
++;
182 if(pos
>= g_queries
.size())
185 doQuery(&g_queries
[pos
]); // this is safe as long as nobody *inserts* to g_queries
189 static void usage(po::options_description
&desc
) {
190 cerr
<<"Syntax: dnstcpbench REMOTE [PORT] < QUERIES"<<endl
;
191 cerr
<<"Where QUERIES is one query per line, format: qname qtype, just 1 space"<<endl
;
195 int main(int argc
, char** argv
)
198 po::options_description
desc("Allowed options"), hidden
, alloptions
;
200 ("help,h", "produce help message")
201 ("version", "print version number")
202 ("verbose,v", "be verbose")
203 ("udp-first,u", "try UDP first")
204 ("file,f", po::value
<string
>(), "source file - if not specified, defaults to stdin")
205 ("tcp-no-delay", po::value
<bool>()->default_value(true), "use TCP_NODELAY socket option")
206 ("timeout-msec", po::value
<int>()->default_value(10), "wait for this amount of milliseconds for an answer")
207 ("workers", po::value
<int>()->default_value(100), "number of parallel workers");
210 ("remote-host", po::value
<string
>(), "remote-host")
211 ("remote-port", po::value
<int>()->default_value(53), "remote-port");
212 alloptions
.add(desc
).add(hidden
);
214 po::positional_options_description p
;
215 p
.add("remote-host", 1);
216 p
.add("remote-port", 1);
218 po::store(po::command_line_parser(argc
, argv
).options(alloptions
).positional(p
).run(), g_vm
);
221 if(g_vm
.count("version")) {
222 cerr
<<"dnstcpbench "<<VERSION
<<endl
;
226 if(g_vm
.count("help")) {
230 g_tcpNoDelay
= g_vm
["tcp-no-delay"].as
<bool>();
232 g_onlyTCP
= !g_vm
.count("udp-first");
233 g_verbose
= g_vm
.count("verbose");
234 g_timeoutMsec
= g_vm
["timeout-msec"].as
<int>();
238 if(g_vm
["remote-host"].empty()) {
243 g_dest
= ComboAddress(g_vm
["remote-host"].as
<string
>().c_str(), g_vm
["remote-port"].as
<int>());
245 unsigned int numworkers
=g_vm
["workers"].as
<int>();
248 cout
<<"Sending queries to: "<<g_dest
.toStringWithPort()<<endl
;
249 cout
<<"Attempting UDP first: " << (g_onlyTCP
? "no" : "yes") <<endl
;
250 cout
<<"Timeout: "<< g_timeoutMsec
<<"msec"<<endl
;
251 cout
<< "Using TCP_NODELAY: "<<g_tcpNoDelay
<<endl
;
255 std::vector
<std::thread
> workers
;
256 workers
.reserve(numworkers
);
259 if(!g_vm
.count("file"))
262 fp
=fopen(g_vm
["file"].as
<string
>().c_str(), "r");
264 unixDie("Unable to open "+g_vm
["file"].as
<string
>()+" for input");
266 pair
<string
, string
> q
;
268 while(stringfgets(fp
, line
)) {
270 q
=splitField(line
, ' ');
271 g_queries
.push_back(BenchQuery(q
.first
, DNSRecordContent::TypeToNumber(q
.second
)));
275 for (unsigned int n
= 0; n
< numworkers
; ++n
) {
276 workers
.push_back(std::thread(worker
));
278 for (auto& w
: workers
) {
282 using namespace boost::accumulators
;
283 typedef accumulator_set
<
285 , stats
<boost::accumulators::tag::median(with_p_square_quantile
),
286 boost::accumulators::tag::mean(immediate
)
290 acc_t udpspeeds
, tcpspeeds
, qps
;
292 typedef map
<time_t, uint32_t> counts_t
;
295 for(const BenchQuery
& bq
: g_queries
) {
296 counts
[bq
.answerSecond
]++;
297 udpspeeds(bq
.udpUsec
);
298 tcpspeeds(bq
.tcpUsec
);
301 for(const counts_t::value_type
& val
: counts
) {
305 cout
<<"Average qps: "<<mean(qps
)<<", median qps: "<<median(qps
)<<endl
;
306 cout
<<"Average UDP latency: "<<mean(udpspeeds
)<<"usec, median: "<<median(udpspeeds
)<<"usec"<<endl
;
307 cout
<<"Average TCP latency: "<<mean(tcpspeeds
)<<"usec, median: "<<median(tcpspeeds
)<<"usec"<<endl
;
309 cout
<<"OK: "<<g_OK
<<", network errors: "<<g_networkErrors
<<", other errors: "<<g_otherErrors
<<endl
;
310 cout
<<"Timeouts: "<<g_timeOuts
<<endl
;
311 cout
<<"Truncateds: "<<g_truncates
<<", auth answers: "<<g_authAnswers
<<endl
;
313 catch(std::exception
&e
)
315 cerr
<<"Fatal: "<<e
.what()<<endl
;