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>
31 #include "dnsparser.hh"
34 #include "dnswriter.hh"
35 #include "dnsrecords.hh"
37 #include "threadname.hh"
38 #include <netinet/tcp.h>
39 #include <boost/array.hpp>
40 #include <boost/program_options.hpp>
44 namespace po
= boost::program_options
;
46 po::variables_map g_vm
;
50 unsigned int g_timeoutMsec
;
51 AtomicCounter g_networkErrors
, g_otherErrors
, g_OK
, g_truncates
, g_authAnswers
, g_timeOuts
;
54 unsigned int makeUsec(const struct timeval
& tv
)
56 return 1000000*tv
.tv_sec
+ tv
.tv_usec
;
59 /* On Linux, run echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle
60 to prevent running out of free TCP ports */
64 BenchQuery(const std::string
& qname_
, uint16_t qtype_
) : qname(qname_
), qtype(qtype_
), udpUsec(0), tcpUsec(0), answerSecond(0) {}
65 BenchQuery(): qtype(0), udpUsec(0), tcpUsec(0), answerSecond(0) {}
68 uint32_t udpUsec
, tcpUsec
;
72 void doQuery(BenchQuery
* q
)
75 vector
<uint8_t> packet
;
76 DNSPacketWriter
pw(packet
, q
->qname
, q
->qtype
);
80 struct timeval tv
, now
;
84 Socket
udpsock(g_dest
.sin4
.sin_family
, SOCK_DGRAM
);
86 udpsock
.sendTo(string(packet
.begin(), packet
.end()), g_dest
);
88 res
= waitForData(udpsock
.getHandle(), 0, 1000 * g_timeoutMsec
);
90 throw NetworkError("Error waiting for response");
96 udpsock
.recvFrom(reply
, origin
);
98 gettimeofday(&now
, 0);
99 q
->udpUsec
= makeUsec(now
- tv
);
102 MOADNSParser
mdp(false, reply
);
108 Socket
sock(g_dest
.sin4
.sin_family
, SOCK_STREAM
);
110 if(setsockopt(sock
.getHandle(),SOL_SOCKET
,SO_REUSEADDR
,(char*)&tmp
,sizeof tmp
)<0)
111 throw runtime_error("Unable to set socket reuse: "+stringerror());
113 if(g_tcpNoDelay
&& setsockopt(sock
.getHandle(), IPPROTO_TCP
, TCP_NODELAY
,(char*)&tmp
,sizeof tmp
)<0)
114 throw runtime_error("Unable to set socket no delay: "+stringerror());
116 sock
.connect(g_dest
);
117 uint16_t len
= htons(packet
.size());
118 string
tcppacket((char*)& len
, 2);
119 tcppacket
.append(packet
.begin(), packet
.end());
121 sock
.writen(tcppacket
);
123 res
= waitForData(sock
.getHandle(), 0, 1000 * g_timeoutMsec
);
125 throw NetworkError("Error waiting for response");
131 if(sock
.read((char *) &len
, 2) != 2)
132 throw PDNSException("tcp read failed");
135 std::unique_ptr
<char[]> creply(new char[len
]);
139 numread
=sock
.read(creply
.get()+n
, len
-n
);
141 throw PDNSException("tcp read failed");
145 reply
=string(creply
.get(), len
);
147 gettimeofday(&now
, 0);
148 q
->tcpUsec
= makeUsec(now
- tv
);
149 q
->answerSecond
= now
.tv_sec
;
151 MOADNSParser
mdp(false, reply
);
152 // cout<<"Had correct TCP/IP response, "<<mdp.d_answers.size()<<" answers, aabit="<<mdp.d_header.aa<<endl;
157 catch(NetworkError
& ne
)
159 cerr
<<"Network error: "<<ne
.what()<<endl
;
167 /* read queries from stdin, put in vector
168 launch n worker threads, each picks a query using AtomicCounter
169 If a worker reaches the end of its queue, it stops */
173 vector
<BenchQuery
> g_queries
;
175 static void* worker(void*)
177 setThreadName("dnstcpb/worker");
179 unsigned int pos
= g_pos
++;
180 if(pos
>= g_queries
.size())
183 doQuery(&g_queries
[pos
]); // this is safe as long as nobody *inserts* to g_queries
188 void usage(po::options_description
&desc
) {
189 cerr
<<"Syntax: dnstcpbench REMOTE [PORT] < QUERIES"<<endl
;
190 cerr
<<"Where QUERIES is one query per line, format: qname qtype, just 1 space"<<endl
;
194 int main(int argc
, char** argv
)
197 po::options_description
desc("Allowed options"), hidden
, alloptions
;
199 ("help,h", "produce help message")
200 ("version", "print version number")
201 ("verbose,v", "be verbose")
202 ("udp-first,u", "try UDP first")
203 ("file,f", po::value
<string
>(), "source file - if not specified, defaults to stdin")
204 ("tcp-no-delay", po::value
<bool>()->default_value(true), "use TCP_NODELAY socket option")
205 ("timeout-msec", po::value
<int>()->default_value(10), "wait for this amount of milliseconds for an answer")
206 ("workers", po::value
<int>()->default_value(100), "number of parallel workers");
209 ("remote-host", po::value
<string
>(), "remote-host")
210 ("remote-port", po::value
<int>()->default_value(53), "remote-port");
211 alloptions
.add(desc
).add(hidden
);
213 po::positional_options_description p
;
214 p
.add("remote-host", 1);
215 p
.add("remote-port", 1);
217 po::store(po::command_line_parser(argc
, argv
).options(alloptions
).positional(p
).run(), g_vm
);
220 if(g_vm
.count("version")) {
221 cerr
<<"dnstcpbench "<<VERSION
<<endl
;
225 if(g_vm
.count("help")) {
229 g_tcpNoDelay
= g_vm
["tcp-no-delay"].as
<bool>();
231 g_onlyTCP
= !g_vm
.count("udp-first");
232 g_verbose
= g_vm
.count("verbose");
233 g_timeoutMsec
= g_vm
["timeout-msec"].as
<int>();
237 if(g_vm
["remote-host"].empty()) {
242 g_dest
= ComboAddress(g_vm
["remote-host"].as
<string
>().c_str(), g_vm
["remote-port"].as
<int>());
244 unsigned int numworkers
=g_vm
["workers"].as
<int>();
247 cout
<<"Sending queries to: "<<g_dest
.toStringWithPort()<<endl
;
248 cout
<<"Attempting UDP first: " << (g_onlyTCP
? "no" : "yes") <<endl
;
249 cout
<<"Timeout: "<< g_timeoutMsec
<<"msec"<<endl
;
250 cout
<< "Using TCP_NODELAY: "<<g_tcpNoDelay
<<endl
;
254 std::vector
<pthread_t
> workers(numworkers
);
257 if(!g_vm
.count("file"))
260 fp
=fopen(g_vm
["file"].as
<string
>().c_str(), "r");
262 unixDie("Unable to open "+g_vm
["file"].as
<string
>()+" for input");
264 pair
<string
, string
> q
;
266 while(stringfgets(fp
, line
)) {
268 q
=splitField(line
, ' ');
269 g_queries
.push_back(BenchQuery(q
.first
, DNSRecordContent::TypeToNumber(q
.second
)));
273 for(unsigned int n
= 0; n
< numworkers
; ++n
) {
274 pthread_create(&workers
[n
], 0, worker
, 0);
276 for(unsigned int n
= 0; n
< numworkers
; ++n
) {
278 pthread_join(workers
[n
], &status
);
281 using namespace boost::accumulators
;
282 typedef accumulator_set
<
284 , stats
<boost::accumulators::tag::median(with_p_square_quantile
),
285 boost::accumulators::tag::mean(immediate
)
289 acc_t udpspeeds
, tcpspeeds
, qps
;
291 typedef map
<time_t, uint32_t> counts_t
;
294 for(const BenchQuery
& bq
: g_queries
) {
295 counts
[bq
.answerSecond
]++;
296 udpspeeds(bq
.udpUsec
);
297 tcpspeeds(bq
.tcpUsec
);
300 for(const counts_t::value_type
& val
: counts
) {
304 cout
<<"Average qps: "<<mean(qps
)<<", median qps: "<<median(qps
)<<endl
;
305 cout
<<"Average UDP latency: "<<mean(udpspeeds
)<<"usec, median: "<<median(udpspeeds
)<<"usec"<<endl
;
306 cout
<<"Average TCP latency: "<<mean(tcpspeeds
)<<"usec, median: "<<median(tcpspeeds
)<<"usec"<<endl
;
308 cout
<<"OK: "<<g_OK
<<", network errors: "<<g_networkErrors
<<", other errors: "<<g_otherErrors
<<endl
;
309 cout
<<"Timeouts: "<<g_timeOuts
<<endl
;
310 cout
<<"Truncateds: "<<g_truncates
<<", auth answers: "<<g_authAnswers
<<endl
;
312 catch(std::exception
&e
)
314 cerr
<<"Fatal: "<<e
.what()<<endl
;