]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnstcpbench.cc
Merge pull request #7870 from omoerbeek/stubquery-fix-arg
[thirdparty/pdns.git] / pdns / dnstcpbench.cc
1 /*
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 */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include <boost/accumulators/statistics/median.hpp>
26 #include <boost/accumulators/statistics/mean.hpp>
27 #include <boost/accumulators/accumulators.hpp>
28
29 #include <boost/accumulators/statistics.hpp>
30
31 #include "dnsparser.hh"
32 #include "sstuff.hh"
33 #include "misc.hh"
34 #include "dnswriter.hh"
35 #include "dnsrecords.hh"
36 #include "statbag.hh"
37 #include "threadname.hh"
38 #include <netinet/tcp.h>
39 #include <boost/array.hpp>
40 #include <boost/program_options.hpp>
41
42
43 StatBag S;
44 namespace po = boost::program_options;
45
46 po::variables_map g_vm;
47 bool g_verbose;
48 bool g_onlyTCP;
49 bool g_tcpNoDelay;
50 unsigned int g_timeoutMsec;
51 AtomicCounter g_networkErrors, g_otherErrors, g_OK, g_truncates, g_authAnswers, g_timeOuts;
52 ComboAddress g_dest;
53
54 unsigned int makeUsec(const struct timeval& tv)
55 {
56 return 1000000*tv.tv_sec + tv.tv_usec;
57 }
58
59 /* On Linux, run echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle
60 to prevent running out of free TCP ports */
61
62 struct BenchQuery
63 {
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) {}
66 DNSName qname;
67 uint16_t qtype;
68 uint32_t udpUsec, tcpUsec;
69 time_t answerSecond;
70 };
71
72 void doQuery(BenchQuery* q)
73 try
74 {
75 vector<uint8_t> packet;
76 DNSPacketWriter pw(packet, q->qname, q->qtype);
77 int res;
78 string reply;
79
80 struct timeval tv, now;
81 gettimeofday(&tv, 0);
82
83 if(!g_onlyTCP) {
84 Socket udpsock(g_dest.sin4.sin_family, SOCK_DGRAM);
85
86 udpsock.sendTo(string(packet.begin(), packet.end()), g_dest);
87 ComboAddress origin;
88 res = waitForData(udpsock.getHandle(), 0, 1000 * g_timeoutMsec);
89 if(res < 0)
90 throw NetworkError("Error waiting for response");
91 if(!res) {
92 g_timeOuts++;
93 return;
94 }
95
96 udpsock.recvFrom(reply, origin);
97
98 gettimeofday(&now, 0);
99 q->udpUsec = makeUsec(now - tv);
100 tv=now;
101
102 MOADNSParser mdp(false, reply);
103 if(!mdp.d_header.tc)
104 return;
105 g_truncates++;
106 }
107
108 Socket sock(g_dest.sin4.sin_family, SOCK_STREAM);
109 int tmp=1;
110 if(setsockopt(sock.getHandle(),SOL_SOCKET,SO_REUSEADDR,(char*)&tmp,sizeof tmp)<0)
111 throw runtime_error("Unable to set socket reuse: "+string(strerror(errno)));
112
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: "+string(strerror(errno)));
115
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());
120
121 sock.writen(tcppacket);
122
123 res = waitForData(sock.getHandle(), 0, 1000 * g_timeoutMsec);
124 if(res < 0)
125 throw NetworkError("Error waiting for response");
126 if(!res) {
127 g_timeOuts++;
128 return;
129 }
130
131 if(sock.read((char *) &len, 2) != 2)
132 throw PDNSException("tcp read failed");
133
134 len=ntohs(len);
135 char *creply = new char[len];
136 int n=0;
137 int numread;
138 while(n<len) {
139 numread=sock.read(creply+n, len-n);
140 if(numread<0)
141 throw PDNSException("tcp read failed");
142 n+=numread;
143 }
144
145 reply=string(creply, len);
146 delete[] creply;
147
148 gettimeofday(&now, 0);
149 q->tcpUsec = makeUsec(now - tv);
150 q->answerSecond = now.tv_sec;
151
152 MOADNSParser mdp(false, reply);
153 // cout<<"Had correct TCP/IP response, "<<mdp.d_answers.size()<<" answers, aabit="<<mdp.d_header.aa<<endl;
154 if(mdp.d_header.aa)
155 g_authAnswers++;
156 g_OK++;
157 }
158 catch(NetworkError& ne)
159 {
160 cerr<<"Network error: "<<ne.what()<<endl;
161 g_networkErrors++;
162 }
163 catch(...)
164 {
165 g_otherErrors++;
166 }
167
168 /* read queries from stdin, put in vector
169 launch n worker threads, each picks a query using AtomicCounter
170 If a worker reaches the end of its queue, it stops */
171
172 AtomicCounter g_pos;
173
174 vector<BenchQuery> g_queries;
175
176 static void* worker(void*)
177 {
178 setThreadName("dnstcpb/worker");
179 for(;;) {
180 unsigned int pos = g_pos++;
181 if(pos >= g_queries.size())
182 break;
183
184 doQuery(&g_queries[pos]); // this is safe as long as nobody *inserts* to g_queries
185 }
186 return 0;
187 }
188
189 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;
192 cerr<<desc<<endl;
193 }
194
195 int main(int argc, char** argv)
196 try
197 {
198 po::options_description desc("Allowed options"), hidden, alloptions;
199 desc.add_options()
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");
208
209 hidden.add_options()
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);
213
214 po::positional_options_description p;
215 p.add("remote-host", 1);
216 p.add("remote-port", 1);
217
218 po::store(po::command_line_parser(argc, argv).options(alloptions).positional(p).run(), g_vm);
219 po::notify(g_vm);
220
221 if(g_vm.count("version")) {
222 cerr<<"dnstcpbench "<<VERSION<<endl;
223 exit(EXIT_SUCCESS);
224 }
225
226 if(g_vm.count("help")) {
227 usage(desc);
228 exit(EXIT_SUCCESS);
229 }
230 g_tcpNoDelay = g_vm["tcp-no-delay"].as<bool>();
231
232 g_onlyTCP = !g_vm.count("udp-first");
233 g_verbose = g_vm.count("verbose");
234 g_timeoutMsec = g_vm["timeout-msec"].as<int>();
235
236 reportAllTypes();
237
238 if(g_vm["remote-host"].empty()) {
239 usage(desc);
240 exit(EXIT_FAILURE);
241 }
242
243 g_dest = ComboAddress(g_vm["remote-host"].as<string>().c_str(), g_vm["remote-port"].as<int>());
244
245 unsigned int numworkers=g_vm["workers"].as<int>();
246
247 if(g_verbose) {
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;
252 }
253
254
255 std::vector<pthread_t> workers(numworkers);
256
257 FILE* fp;
258 if(!g_vm.count("file"))
259 fp=fdopen(0, "r");
260 else {
261 fp=fopen(g_vm["file"].as<string>().c_str(), "r");
262 if(!fp)
263 unixDie("Unable to open "+g_vm["file"].as<string>()+" for input");
264 }
265 pair<string, string> q;
266 string line;
267 while(stringfgets(fp, line)) {
268 trim_right(line);
269 q=splitField(line, ' ');
270 g_queries.push_back(BenchQuery(q.first, DNSRecordContent::TypeToNumber(q.second)));
271 }
272 fclose(fp);
273
274 for(unsigned int n = 0; n < numworkers; ++n) {
275 pthread_create(&workers[n], 0, worker, 0);
276 }
277 for(unsigned int n = 0; n < numworkers; ++n) {
278 void* status;
279 pthread_join(workers[n], &status);
280 }
281
282 using namespace boost::accumulators;
283 typedef accumulator_set<
284 double
285 , stats<boost::accumulators::tag::median(with_p_square_quantile),
286 boost::accumulators::tag::mean(immediate)
287 >
288 > acc_t;
289
290 acc_t udpspeeds, tcpspeeds, qps;
291
292 typedef map<time_t, uint32_t> counts_t;
293 counts_t counts;
294
295 for(const BenchQuery& bq : g_queries) {
296 counts[bq.answerSecond]++;
297 udpspeeds(bq.udpUsec);
298 tcpspeeds(bq.tcpUsec);
299 }
300
301 for(const counts_t::value_type& val : counts) {
302 qps(val.second);
303 }
304
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;
308
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;
312 }
313 catch(std::exception &e)
314 {
315 cerr<<"Fatal: "<<e.what()<<endl;
316 }