]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/dnstcpbench.cc
Meson: Separate test files from common files
[thirdparty/pdns.git] / pdns / dnstcpbench.cc
CommitLineData
22f4bd59 1/*
12471842
PL
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
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.
8 *
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.
12 *
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.
17 *
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.
21 */
870a0fe4
AT
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
dc125378
OM
25#pragma GCC diagnostic push
26#pragma GCC diagnostic ignored "-Wunused-parameter"
d65e98fd 27#if __clang_major__ >= 15
dc125378 28#pragma GCC diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy"
d65e98fd 29#endif
4106b16e 30#include <boost/accumulators/statistics/median.hpp>
31#include <boost/accumulators/statistics/mean.hpp>
32#include <boost/accumulators/accumulators.hpp>
4106b16e 33#include <boost/accumulators/statistics.hpp>
dc125378 34#pragma GCC diagnostic pop
4106b16e 35
0ddde5fb
RG
36#include <thread>
37
a51b52c9 38#include "dnsparser.hh"
39#include "sstuff.hh"
40#include "misc.hh"
41#include "dnswriter.hh"
42#include "dnsrecords.hh"
43#include "statbag.hh"
519f5484 44#include "threadname.hh"
a8f90a6a 45#include <netinet/tcp.h>
a51b52c9 46#include <boost/array.hpp>
36658df5 47#include <boost/program_options.hpp>
fa8fd4d2 48
a51b52c9 49
36658df5 50StatBag S;
51namespace po = boost::program_options;
4106b16e 52
36658df5 53po::variables_map g_vm;
54bool g_verbose;
a51b52c9 55bool g_onlyTCP;
a8f90a6a 56bool g_tcpNoDelay;
36658df5 57unsigned int g_timeoutMsec;
58AtomicCounter g_networkErrors, g_otherErrors, g_OK, g_truncates, g_authAnswers, g_timeOuts;
1d74012c 59ComboAddress g_dest;
f7896b52 60
050e6877 61static unsigned int makeUsec(const struct timeval& tv)
4106b16e 62{
63 return 1000000*tv.tv_sec + tv.tv_usec;
64}
65
d65e98fd 66/* On Linux, run echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle
22f4bd59 67 to prevent running out of free TCP ports */
68
69struct BenchQuery
70{
4106b16e 71 BenchQuery(const std::string& qname_, uint16_t qtype_) : qname(qname_), qtype(qtype_), udpUsec(0), tcpUsec(0), answerSecond(0) {}
67252448 72 BenchQuery(): qtype(0), udpUsec(0), tcpUsec(0), answerSecond(0) {}
eaedd091 73 DNSName qname;
22f4bd59 74 uint16_t qtype;
4106b16e 75 uint32_t udpUsec, tcpUsec;
76 time_t answerSecond;
22f4bd59 77};
f7896b52 78
050e6877 79static void doQuery(BenchQuery* q)
f7896b52 80try
a51b52c9 81{
82 vector<uint8_t> packet;
1d74012c 83 DNSPacketWriter pw(packet, q->qname, q->qtype);
36658df5 84 int res;
a51b52c9 85 string reply;
86
4106b16e 87 struct timeval tv, now;
88 gettimeofday(&tv, 0);
89
a51b52c9 90 if(!g_onlyTCP) {
528b753d 91 Socket udpsock(g_dest.sin4.sin_family, SOCK_DGRAM);
d65e98fd 92
16657041 93 udpsock.sendTo(string(packet.begin(), packet.end()), g_dest);
a51b52c9 94 ComboAddress origin;
36658df5 95 res = waitForData(udpsock.getHandle(), 0, 1000 * g_timeoutMsec);
96 if(res < 0)
97 throw NetworkError("Error waiting for response");
98 if(!res) {
99 g_timeOuts++;
100 return;
101 }
102
a51b52c9 103 udpsock.recvFrom(reply, origin);
4106b16e 104
105 gettimeofday(&now, 0);
106 q->udpUsec = makeUsec(now - tv);
107 tv=now;
108
27c0050c 109 MOADNSParser mdp(false, reply);
a51b52c9 110 if(!mdp.d_header.tc)
111 return;
f7896b52 112 g_truncates++;
a51b52c9 113 }
114
528b753d 115 Socket sock(g_dest.sin4.sin_family, SOCK_STREAM);
f7896b52 116 int tmp=1;
ba2b8a7a
FM
117 if (setsockopt(sock.getHandle(), SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof tmp) < 0) {
118 throw runtime_error("Unable to set socket reuse: " + stringerror());
119 }
d65e98fd 120
ba2b8a7a
FM
121 if (g_tcpNoDelay && setsockopt(sock.getHandle(), IPPROTO_TCP, TCP_NODELAY, &tmp, sizeof tmp) < 0) {
122 throw runtime_error("Unable to set socket no delay: " + stringerror());
123 }
a51b52c9 124
1d74012c 125 sock.connect(g_dest);
36658df5 126 uint16_t len = htons(packet.size());
127 string tcppacket((char*)& len, 2);
16657041 128 tcppacket.append(packet.begin(), packet.end());
36658df5 129
130 sock.writen(tcppacket);
131
132 res = waitForData(sock.getHandle(), 0, 1000 * g_timeoutMsec);
133 if(res < 0)
134 throw NetworkError("Error waiting for response");
135 if(!res) {
136 g_timeOuts++;
137 return;
138 }
d65e98fd 139
a51b52c9 140 if(sock.read((char *) &len, 2) != 2)
3f81d239 141 throw PDNSException("tcp read failed");
d65e98fd 142
a51b52c9 143 len=ntohs(len);
2bbc9eb0 144 auto creply = std::make_unique<char[]>(len);
a51b52c9 145 int n=0;
146 int numread;
147 while(n<len) {
c2826d2e 148 numread=sock.read(creply.get()+n, len-n);
a51b52c9 149 if(numread<0)
3f81d239 150 throw PDNSException("tcp read failed");
a51b52c9 151 n+=numread;
152 }
d65e98fd 153
c2826d2e 154 reply=string(creply.get(), len);
d65e98fd 155
4106b16e 156 gettimeofday(&now, 0);
157 q->tcpUsec = makeUsec(now - tv);
158 q->answerSecond = now.tv_sec;
159
27c0050c 160 MOADNSParser mdp(false, reply);
f7896b52 161 // cout<<"Had correct TCP/IP response, "<<mdp.d_answers.size()<<" answers, aabit="<<mdp.d_header.aa<<endl;
36658df5 162 if(mdp.d_header.aa)
163 g_authAnswers++;
f7896b52 164 g_OK++;
165}
166catch(NetworkError& ne)
167{
168 cerr<<"Network error: "<<ne.what()<<endl;
169 g_networkErrors++;
170}
171catch(...)
172{
173 g_otherErrors++;
a51b52c9 174}
175
176/* read queries from stdin, put in vector
177 launch n worker threads, each picks a query using AtomicCounter
178 If a worker reaches the end of its queue, it stops */
179
180AtomicCounter g_pos;
682549f2 181
22f4bd59 182vector<BenchQuery> g_queries;
1d74012c 183
0ddde5fb 184static void worker()
a51b52c9 185{
519f5484 186 setThreadName("dnstcpb/worker");
a51b52c9 187 for(;;) {
d65e98fd 188 unsigned int pos = g_pos++;
f7896b52 189 if(pos >= g_queries.size())
a51b52c9 190 break;
1d74012c 191
192 doQuery(&g_queries[pos]); // this is safe as long as nobody *inserts* to g_queries
a51b52c9 193 }
a51b52c9 194}
195
050e6877 196static void usage(po::options_description &desc) {
c4f20ff9
PL
197 cerr<<"Syntax: dnstcpbench REMOTE [PORT] < QUERIES"<<endl;
198 cerr<<"Where QUERIES is one query per line, format: qname qtype, just 1 space"<<endl;
199 cerr<<desc<<endl;
200}
201
a51b52c9 202int main(int argc, char** argv)
203try
204{
36658df5 205 po::options_description desc("Allowed options"), hidden, alloptions;
206 desc.add_options()
207 ("help,h", "produce help message")
c4f20ff9 208 ("version", "print version number")
36658df5 209 ("verbose,v", "be verbose")
210 ("udp-first,u", "try UDP first")
74f463af 211 ("file,f", po::value<string>(), "source file - if not specified, defaults to stdin")
a8f90a6a 212 ("tcp-no-delay", po::value<bool>()->default_value(true), "use TCP_NODELAY socket option")
36658df5 213 ("timeout-msec", po::value<int>()->default_value(10), "wait for this amount of milliseconds for an answer")
214 ("workers", po::value<int>()->default_value(100), "number of parallel workers");
215
216 hidden.add_options()
217 ("remote-host", po::value<string>(), "remote-host")
218 ("remote-port", po::value<int>()->default_value(53), "remote-port");
d65e98fd 219 alloptions.add(desc).add(hidden);
36658df5 220
221 po::positional_options_description p;
222 p.add("remote-host", 1);
223 p.add("remote-port", 1);
224
225 po::store(po::command_line_parser(argc, argv).options(alloptions).positional(p).run(), g_vm);
226 po::notify(g_vm);
c4f20ff9
PL
227
228 if(g_vm.count("version")) {
229 cerr<<"dnstcpbench "<<VERSION<<endl;
230 exit(EXIT_SUCCESS);
231 }
232
36658df5 233 if(g_vm.count("help")) {
c4f20ff9 234 usage(desc);
36658df5 235 exit(EXIT_SUCCESS);
236 }
a8f90a6a 237 g_tcpNoDelay = g_vm["tcp-no-delay"].as<bool>();
238
36658df5 239 g_onlyTCP = !g_vm.count("udp-first");
240 g_verbose = g_vm.count("verbose");
241 g_timeoutMsec = g_vm["timeout-msec"].as<int>();
242
a51b52c9 243 reportAllTypes();
f7896b52 244
36658df5 245 if(g_vm["remote-host"].empty()) {
c4f20ff9 246 usage(desc);
f7896b52 247 exit(EXIT_FAILURE);
248 }
f7896b52 249
36658df5 250 g_dest = ComboAddress(g_vm["remote-host"].as<string>().c_str(), g_vm["remote-port"].as<int>());
251
252 unsigned int numworkers=g_vm["workers"].as<int>();
d65e98fd 253
36658df5 254 if(g_verbose) {
255 cout<<"Sending queries to: "<<g_dest.toStringWithPort()<<endl;
256 cout<<"Attempting UDP first: " << (g_onlyTCP ? "no" : "yes") <<endl;
9273c94d 257 cout<<"Timeout: "<< g_timeoutMsec<<" ms"<<endl;
a8f90a6a 258 cout << "Using TCP_NODELAY: "<<g_tcpNoDelay<<endl;
36658df5 259 }
260
261
0ddde5fb
RG
262 std::vector<std::thread> workers;
263 workers.reserve(numworkers);
a51b52c9 264
114b8796 265 pdns::UniqueFilePtr filePtr{nullptr};
e30bc0cf 266 if (!g_vm.count("file")) {
114b8796 267 filePtr = pdns::UniqueFilePtr(fdopen(0, "r"));
e30bc0cf 268 }
74f463af 269 else {
114b8796
RG
270 filePtr = pdns::UniqueFilePtr(fopen(g_vm["file"].as<string>().c_str(), "r"));
271 if (!filePtr) {
74f463af 272 unixDie("Unable to open "+g_vm["file"].as<string>()+" for input");
e30bc0cf 273 }
74f463af 274 }
f7896b52 275 pair<string, string> q;
276 string line;
114b8796 277 while(stringfgets(filePtr.get(), line)) {
dc593046 278 boost::trim_right(line);
f7896b52 279 q=splitField(line, ' ');
22f4bd59 280 g_queries.push_back(BenchQuery(q.first, DNSRecordContent::TypeToNumber(q.second)));
a51b52c9 281 }
114b8796 282 filePtr.reset();
e30bc0cf 283
0ddde5fb
RG
284 for (unsigned int n = 0; n < numworkers; ++n) {
285 workers.push_back(std::thread(worker));
a51b52c9 286 }
0ddde5fb
RG
287 for (auto& w : workers) {
288 w.join();
a51b52c9 289 }
d65e98fd 290
4106b16e 291 using namespace boost::accumulators;
292 typedef accumulator_set<
40cd3e09 293 double
4106b16e 294 , stats<boost::accumulators::tag::median(with_p_square_quantile),
295 boost::accumulators::tag::mean(immediate)
296 >
297 > acc_t;
298
299 acc_t udpspeeds, tcpspeeds, qps;
d65e98fd 300
4106b16e 301 typedef map<time_t, uint32_t> counts_t;
302 counts_t counts;
303
ef7cd021 304 for(const BenchQuery& bq : g_queries) {
4106b16e 305 counts[bq.answerSecond]++;
306 udpspeeds(bq.udpUsec);
307 tcpspeeds(bq.tcpUsec);
308 }
309
ef7cd021 310 for(const counts_t::value_type& val : counts) {
4106b16e 311 qps(val.second);
312 }
313
314 cout<<"Average qps: "<<mean(qps)<<", median qps: "<<median(qps)<<endl;
35f4f574
JS
315 cout<<"Average UDP latency: "<<mean(udpspeeds)<<" us, median: "<<median(udpspeeds)<<" us"<<endl;
316 cout<<"Average TCP latency: "<<mean(tcpspeeds)<<" us, median: "<<median(tcpspeeds)<<" us"<<endl;
4106b16e 317
f7896b52 318 cout<<"OK: "<<g_OK<<", network errors: "<<g_networkErrors<<", other errors: "<<g_otherErrors<<endl;
36658df5 319 cout<<"Timeouts: "<<g_timeOuts<<endl;
320 cout<<"Truncateds: "<<g_truncates<<", auth answers: "<<g_authAnswers<<endl;
a51b52c9 321}
322catch(std::exception &e)
323{
324 cerr<<"Fatal: "<<e.what()<<endl;
325}