]>
Commit | Line | Data |
---|---|---|
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 | 50 | StatBag S; |
51 | namespace po = boost::program_options; | |
4106b16e | 52 | |
36658df5 | 53 | po::variables_map g_vm; |
54 | bool g_verbose; | |
a51b52c9 | 55 | bool g_onlyTCP; |
a8f90a6a | 56 | bool g_tcpNoDelay; |
36658df5 | 57 | unsigned int g_timeoutMsec; |
58 | AtomicCounter g_networkErrors, g_otherErrors, g_OK, g_truncates, g_authAnswers, g_timeOuts; | |
1d74012c | 59 | ComboAddress g_dest; |
f7896b52 | 60 | |
050e6877 | 61 | static 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 | ||
69 | struct 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 | 79 | static void doQuery(BenchQuery* q) |
f7896b52 | 80 | try |
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 | } | |
166 | catch(NetworkError& ne) | |
167 | { | |
168 | cerr<<"Network error: "<<ne.what()<<endl; | |
169 | g_networkErrors++; | |
170 | } | |
171 | catch(...) | |
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 | ||
180 | AtomicCounter g_pos; | |
682549f2 | 181 | |
22f4bd59 | 182 | vector<BenchQuery> g_queries; |
1d74012c | 183 | |
0ddde5fb | 184 | static 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 | 196 | static 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 | 202 | int main(int argc, char** argv) |
203 | try | |
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 | } |
322 | catch(std::exception &e) | |
323 | { | |
324 | cerr<<"Fatal: "<<e.what()<<endl; | |
325 | } |