1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_TAG "connect_benchmark"
18
19 /*
20 * See README.md for general notes.
21 *
22 * This set of benchmarks measures the throughput of connect() calls on a single thread for IPv4 and
23 * IPv6.
24 *
25 * Realtime timed tests
26 * ====================
27 *
28 * The tests named *_high_load record the following useful information:
29 *
30 * - real_time: the mean roundtrip time for one connect() call under load
31 *
32 * - iterations: the number of times the test was run within the timelimit --- approximately
33 * MinTime / real_time
34 *
35 * Manually timed tests
36 * ====================
37 *
38 * All other sets of tests apart from *_high_load run with manual timing. The purpose of these is to
39 * measure 90th-percentile latency for connect() calls compared to mean latency.
40 *
41 * (TODO: ideally this should be against median latency, but google-benchmark only supports one
42 * custom 'label' output for graphing. Stddev isn't appropriate because the latency
43 * distribution is usually spiky, not in a nice neat normal-like distribution.)
44 *
45 * The manually timed tests record the following useful information:
46 *
47 * - real_time: the average time taken to complete a test run. Unlike the real_time used in high
48 * load tests, this is calculated from before-and-after values of the realtime clock
49 * over many iterations so may be less accurate than the under-load times.
50 *
51 * - iterations: the number of times the test was run within the timelimit --- approximately
52 * MinTime / real_time, although as explained, may not be as meaningful because of
53 * overhead from timing.
54 *
55 * - label: a manually-recorded time giving the 90th-percentile value of real_time over all
56 * individual runs. Should be compared to real_time.
57 *
58 */
59
60 #include <arpa/inet.h>
61 #include <cutils/sockets.h>
62 #include <errno.h>
63 #include <netinet/in.h>
64 #include <time.h>
65
66 #include <map>
67 #include <functional>
68 #include <thread>
69
70 #include <android-base/stringprintf.h>
71 #include <benchmark/benchmark.h>
72 #include <log/log.h>
73 #include <netdutils/Stopwatch.h>
74 #include <utils/StrongPointer.h>
75
76 #include "FwmarkClient.h"
77 #include "SockDiag.h"
78
79 using android::base::StringPrintf;
80 using android::netdutils::Stopwatch;
81
bindAndListen(int s)82 static int bindAndListen(int s) {
83 sockaddr_in6 sin6 = { .sin6_family = AF_INET6 };
84 if (bind(s, (sockaddr*) &sin6, sizeof(sin6)) == 0) {
85 if (listen(s, 1)) {
86 return -1;
87 }
88 sockaddr_in sin = {};
89 socklen_t len = sizeof(sin);
90 if (getsockname(s, (sockaddr*) &sin, &len)) {
91 return -1;
92 }
93 return ntohs(sin.sin_port);
94 } else {
95 return -1;
96 }
97 }
98
ipv4_loopback(benchmark::State & state,const bool waitBetweenRuns)99 static void ipv4_loopback(benchmark::State& state, const bool waitBetweenRuns) {
100 const int listensocket = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
101 const int port = bindAndListen(listensocket);
102 if (port == -1) {
103 state.SkipWithError("Unable to bind server socket");
104 return;
105 }
106
107 // ALOGW("Listening on port = %d", port);
108 std::vector<uint64_t> latencies(state.max_iterations);
109 uint64_t iterations = 0;
110
111 while (state.KeepRunning()) {
112 int sock = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
113 if (sock < 0) {
114 state.SkipWithError(StringPrintf("socket() failed with errno=%d", errno).c_str());
115 break;
116 }
117
118 const Stopwatch stopwatch;
119
120 sockaddr_in server = { .sin_family = AF_INET, .sin_port = htons(port) };
121 if (connect(sock, (sockaddr*) &server, sizeof(server))) {
122 state.SkipWithError(StringPrintf("connect() failed with errno=%d", errno).c_str());
123 close(sock);
124 break;
125 }
126
127 if (waitBetweenRuns) {
128 latencies[iterations] = stopwatch.timeTakenUs();
129 state.SetIterationTime(static_cast<double>(latencies[iterations]) / 1.0e6L);
130 std::this_thread::sleep_for(std::chrono::milliseconds(10));
131 ++iterations;
132 }
133
134 sockaddr_in6 client;
135 socklen_t clientlen = sizeof(client);
136 int accepted = accept4(listensocket, (sockaddr*) &client, &clientlen, SOCK_CLOEXEC);
137 if (accepted < 0) {
138 state.SkipWithError(StringPrintf("accept() failed with errno=%d", errno).c_str());
139 close(sock);
140 break;
141 }
142
143 close(accepted);
144 close(sock);
145 }
146 close(listensocket);
147 // ALOGI("Finished test on port = %d", port);
148
149 if (iterations > 0) {
150 latencies.resize(iterations);
151 sort(latencies.begin(), latencies.end());
152 state.SetLabel(StringPrintf("%lld", (long long) latencies[iterations * 9 / 10]));
153 }
154 }
155
ipv6_loopback(benchmark::State & state,const bool waitBetweenRuns)156 static void ipv6_loopback(benchmark::State& state, const bool waitBetweenRuns) {
157 const int listensocket = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
158 const int port = bindAndListen(listensocket);
159 if (port == -1) {
160 state.SkipWithError("Unable to bind server socket");
161 return;
162 }
163
164 // ALOGW("Listening on port = %d", port);
165 std::vector<uint64_t> latencies(state.max_iterations);
166 uint64_t iterations = 0;
167
168 while (state.KeepRunning()) {
169 int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
170 if (sock < 0) {
171 state.SkipWithError(StringPrintf("socket() failed with errno=%d", errno).c_str());
172 break;
173 }
174
175 const Stopwatch stopwatch;
176
177 sockaddr_in6 server = { .sin6_family = AF_INET6, .sin6_port = htons(port) };
178 if (connect(sock, (sockaddr*) &server, sizeof(server))) {
179 state.SkipWithError(StringPrintf("connect() failed with errno=%d", errno).c_str());
180 close(sock);
181 break;
182 }
183
184 if (waitBetweenRuns) {
185 latencies[iterations] = stopwatch.timeTakenUs();
186 state.SetIterationTime(static_cast<double>(latencies[iterations]) / 1.0e6L);
187 std::this_thread::sleep_for(std::chrono::milliseconds(10));
188 ++iterations;
189 }
190
191 sockaddr_in6 client;
192 socklen_t clientlen = sizeof(client);
193 int accepted = accept4(listensocket, (sockaddr*) &client, &clientlen, SOCK_CLOEXEC);
194 if (accepted < 0) {
195 state.SkipWithError(StringPrintf("accept() failed with errno=%d", errno).c_str());
196 close(sock);
197 break;
198 }
199
200 close(accepted);
201 close(sock);
202 }
203 close(listensocket);
204 // ALOGI("Finished test on port = %d", port);
205
206 if (iterations > 0) {
207 latencies.resize(iterations);
208 sort(latencies.begin(), latencies.end());
209 state.SetLabel(StringPrintf("%lld", (long long) latencies[iterations * 9 / 10]));
210 }
211 }
212
run(decltype(ipv4_loopback) benchmarkFunction,::benchmark::State & state,const bool waitBetweenRuns)213 static void run(decltype(ipv4_loopback) benchmarkFunction, ::benchmark::State& state,
214 const bool waitBetweenRuns) {
215 benchmarkFunction(state, waitBetweenRuns);
216 }
217
218 constexpr int MIN_THREADS = 1;
219 constexpr int MAX_THREADS = 1;
220 constexpr double MIN_TIME = 0.5 /* seconds */;
221
222 // IPv4 benchmarks under no load
ipv4_no_load(::benchmark::State & state)223 static void ipv4_no_load(::benchmark::State& state) {
224 run(ipv4_loopback, state, true);
225 }
226 BENCHMARK(ipv4_no_load)->MinTime(MIN_TIME)->UseManualTime();
227
228 // IPv4 benchmarks under high load
ipv4_high_load(::benchmark::State & state)229 static void ipv4_high_load(::benchmark::State& state) {
230 run(ipv4_loopback, state, false);
231 }
232 BENCHMARK(ipv4_high_load)->ThreadRange(MIN_THREADS, MAX_THREADS)->MinTime(MIN_TIME)->UseRealTime();
233
234 // IPv6 raw connect() without using fwmark
ipv6_no_load(::benchmark::State & state)235 static void ipv6_no_load(::benchmark::State& state) {
236 run(ipv6_loopback, state, true);
237 }
238 BENCHMARK(ipv6_no_load)->MinTime(MIN_TIME)->UseManualTime();
239
240 // IPv6 benchmarks under high load
ipv6_high_load(::benchmark::State & state)241 static void ipv6_high_load(::benchmark::State& state) {
242 run(ipv6_loopback, state, false);
243 }
244 BENCHMARK(ipv6_high_load)->ThreadRange(MIN_THREADS, MAX_THREADS)->MinTime(MIN_TIME)->UseRealTime();
245