blob: a1827ecfea5f610bcc2df1ae82e8f7a3fa41880f [file] [log] [blame]
David Reiss709b69f2010-10-06 17:10:30 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19#ifndef _GNU_SOURCE
20#define _GNU_SOURCE // needed for getopt_long
21#endif
22
23#include <sys/time.h>
24#include <getopt.h>
25#include <boost/test/unit_test.hpp>
26
27#include <transport/TFileTransport.h>
28
29using namespace apache::thrift::transport;
30
31/**************************************************************************
32 * Global state
33 **************************************************************************/
34
35static const char* tmp_dir = "/tmp";
36
37class FsyncLog;
38FsyncLog* fsync_log;
39
40
41/**************************************************************************
42 * Helper code
43 **************************************************************************/
44
45// Provide BOOST_CHECK_LT() and BOOST_CHECK_GT(), in case we're compiled
46// with an older version of boost
47#ifndef BOOST_CHECK_LT
48#define BOOST_CHECK_CMP(a, b, op) \
49 BOOST_CHECK_MESSAGE((a) op (b), \
50 "check " BOOST_STRINGIZE(a) " " BOOST_STRINGIZE(op) " " \
51 BOOST_STRINGIZE(b) " failed: " BOOST_STRINGIZE(a) "=" <<\
52 (a) << " " BOOST_STRINGIZE(b) "=" << (b))
53
54#define BOOST_CHECK_LT(a, b) BOOST_CHECK_CMP(a, b, <)
55#define BOOST_CHECK_GT(a, b) BOOST_CHECK_CMP(a, b, >)
56#endif // BOOST_CHECK_LT
57
58/**
59 * Class to record calls to fsync
60 */
61class FsyncLog {
62 public:
63 struct FsyncCall {
64 struct timeval time;
65 int fd;
66 };
67 typedef std::list<FsyncCall> CallList;
68
69 FsyncLog() {}
70
71 void fsync(int fd) {
72 FsyncCall call;
73 gettimeofday(&call.time, NULL);
74 calls_.push_back(call);
75 }
76
77 const CallList* getCalls() const {
78 return &calls_;
79 }
80
81 private:
82 CallList calls_;
83};
84
85/**
86 * Helper class to clean up temporary files
87 */
88class TempFile {
89 public:
90 TempFile(const char* directory, const char* prefix) {
91 size_t path_len = strlen(directory) + strlen(prefix) + 8;
92 path_ = new char[path_len];
93 snprintf(path_, path_len, "%s/%sXXXXXX", directory, prefix);
94
95 fd_ = mkstemp(path_);
96 if (fd_ < 0) {
97 throw apache::thrift::TException("mkstemp() failed");
98 }
99 }
100
101 ~TempFile() {
102 unlink();
103 close();
104 }
105
106 const char* getPath() const {
107 return path_;
108 }
109
110 int getFD() const {
111 return fd_;
112 }
113
114 void unlink() {
115 if (path_) {
116 ::unlink(path_);
117 delete[] path_;
118 path_ = NULL;
119 }
120 }
121
122 void close() {
123 if (fd_ < 0) {
124 return;
125 }
126
127 ::close(fd_);
128 fd_ = -1;
129 }
130
131 private:
132 char* path_;
133 int fd_;
134};
135
136// Use our own version of fsync() for testing.
137// This returns immediately, so timing in test_destructor() isn't affected by
138// waiting on the actual filesystem.
139extern "C"
140int fsync(int fd) {
141 if (fsync_log) {
142 fsync_log->fsync(fd);
143 }
144 return 0;
145}
146
147
148int time_diff(const struct timeval* t1, const struct timeval* t2) {
149 return (t2->tv_usec - t1->tv_usec) + (t2->tv_sec - t1->tv_sec) * 1000000;
150}
151
152
153/**************************************************************************
154 * Test cases
155 **************************************************************************/
156
157/**
158 * Make sure the TFileTransport destructor exits "quickly".
159 *
160 * Previous versions had a bug causing the writer thread not to exit
161 * right away.
162 *
163 * It's kind of lame that we just check to see how long the destructor takes in
164 * wall-clock time. This could result in false failures on slower systems, or
165 * on heavily loaded machines.
166 */
167void test_destructor() {
168 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
169
170 unsigned int const NUM_ITERATIONS = 1000;
David Reiss709b69f2010-10-06 17:10:30 +0000171
David Reiss41993772010-10-06 17:10:31 +0000172 unsigned int num_over_500us = 0;
David Reiss709b69f2010-10-06 17:10:30 +0000173 for (unsigned int n = 0; n < NUM_ITERATIONS; ++n) {
174 ftruncate(f.getFD(), 0);
175
176 TFileTransport* transport = new TFileTransport(f.getPath());
177
178 // write something so that the writer thread gets started
179 transport->write(reinterpret_cast<const uint8_t*>("foo"), 3);
180
181 // Every other iteration, also call flush(), just in case that potentially
182 // has any effect on how the writer thread wakes up.
183 if (n & 0x1) {
184 transport->flush();
185 }
186
187 /*
188 * Time the call to the destructor
189 */
190 struct timeval start;
191 struct timeval end;
192
193 gettimeofday(&start, NULL);
194 delete transport;
195 gettimeofday(&end, NULL);
196
197 int delta = time_diff(&start, &end);
David Reiss41993772010-10-06 17:10:31 +0000198
199 // If any attempt takes more than 100ms, treat that as a failure.
200 // Treat this as a fatal failure, so we'll return now instead of
201 // looping over a very slow operation.
202 BOOST_REQUIRE_LT(delta, 100000);
203
204 // Normally, it takes less than 500us on my dev box.
205 // However, if the box is heavily loaded, some of the test runs
206 // take longer, since we're just waiting for our turn on the CPU.
207 if (delta > 500) {
208 ++num_over_500us;
209 }
David Reiss709b69f2010-10-06 17:10:30 +0000210 }
David Reiss41993772010-10-06 17:10:31 +0000211
212 // Make sure fewer than 10% of the runs took longer than 500us
213 BOOST_CHECK(num_over_500us < (NUM_ITERATIONS / 10));
David Reiss709b69f2010-10-06 17:10:30 +0000214}
215
216/**
217 * Make sure setFlushMaxUs() is honored.
218 */
219void test_flush_max_us_impl(uint32_t flush_us, uint32_t write_us,
220 uint32_t test_us) {
221 // TFileTransport only calls fsync() if data has been written,
222 // so make sure the write interval is smaller than the flush interval.
223 BOOST_REQUIRE(write_us < flush_us);
224
225 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
226
227 // Record calls to fsync()
228 FsyncLog log;
229 fsync_log = &log;
230
231 TFileTransport* transport = new TFileTransport(f.getPath());
232 // Don't flush because of # of bytes written
233 transport->setFlushMaxBytes(0xffffffff);
234 uint8_t buf[] = "a";
235 uint32_t buflen = sizeof(buf);
236
David Reiss41993772010-10-06 17:10:31 +0000237 // Set the flush interval
David Reiss709b69f2010-10-06 17:10:30 +0000238 transport->setFlushMaxUs(flush_us);
239
David Reiss41993772010-10-06 17:10:31 +0000240 // Make one call to write, to start the writer thread now.
241 // (If we just let the thread get created during our test loop,
242 // the thread creation sometimes takes long enough to make the first
243 // fsync interval fail the check.)
244 transport->write(buf, buflen);
245
David Reiss709b69f2010-10-06 17:10:30 +0000246 // Add one entry to the fsync log, just to mark the start time
247 log.fsync(-1);
248
249 // Loop doing write(), sleep(), ...
250 uint32_t total_time = 0;
251 while (true) {
252 transport->write(buf, buflen);
253 if (total_time > test_us) {
254 break;
255 }
256 usleep(write_us);
257 total_time += write_us;
258 }
259
260 delete transport;
261
262 // Stop logging new fsync() calls
263 fsync_log = NULL;
264
265 // Examine the fsync() log
266 //
267 // TFileTransport uses pthread_cond_timedwait(), which only has millisecond
268 // resolution. In my testing, it normally wakes up about 1 millisecond late.
269 // However, sometimes it takes a bit longer. Allow 5ms leeway.
270 int max_allowed_delta = flush_us + 5000;
271
272 const FsyncLog::CallList* calls = log.getCalls();
273 // We added 1 fsync call above.
274 // Make sure TFileTransport called fsync at least once
275 BOOST_CHECK_GT(calls->size(), 1);
276
277 const struct timeval* prev_time = NULL;
278 for (FsyncLog::CallList::const_iterator it = calls->begin();
279 it != calls->end();
280 ++it) {
281 if (prev_time) {
282 int delta = time_diff(prev_time, &it->time);
283 BOOST_CHECK_LT(delta, max_allowed_delta);
284 }
285 prev_time = &it->time;
286 }
287}
288
289void test_flush_max_us1() {
290 // fsync every 10ms, write every 5ms, for 500ms
291 test_flush_max_us_impl(10000, 5000, 500000);
292}
293
294void test_flush_max_us2() {
295 // fsync every 50ms, write every 20ms, for 500ms
296 test_flush_max_us_impl(50000, 20000, 500000);
297}
298
299void test_flush_max_us3() {
300 // fsync every 400ms, write every 300ms, for 1s
301 test_flush_max_us_impl(400000, 300000, 1000000);
302}
303
304/**************************************************************************
305 * General Initialization
306 **************************************************************************/
307
308void print_usage(FILE* f, const char* argv0) {
309 fprintf(f, "Usage: %s [boost_options] [options]\n", argv0);
310 fprintf(f, "Options:\n");
311 fprintf(f, " --tmp-dir=DIR, -t DIR\n");
312 fprintf(f, " --help\n");
313}
314
315void parse_args(int argc, char* argv[]) {
316 int seed;
317 int *seedptr = NULL;
318
319 struct option long_opts[] = {
320 { "help", false, NULL, 'h' },
321 { "tmp-dir", true, NULL, 't' },
322 { NULL, 0, NULL, 0 }
323 };
324
325 while (true) {
326 optopt = 1;
327 int optchar = getopt_long(argc, argv, "ht:", long_opts, NULL);
328 if (optchar == -1) {
329 break;
330 }
331
332 switch (optchar) {
333 case 't':
334 tmp_dir = optarg;
335 break;
336 case 'h':
337 print_usage(stdout, argv[0]);
338 exit(0);
339 case '?':
340 exit(1);
341 default:
342 // Only happens if someone adds another option to the optarg string,
343 // but doesn't update the switch statement to handle it.
344 fprintf(stderr, "unknown option \"-%c\"\n", optchar);
345 exit(1);
346 }
347 }
348}
349
350boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) {
351 // Parse arguments
352 parse_args(argc, argv);
353
354 boost::unit_test::test_suite* suite =
355 BOOST_TEST_SUITE("TFileTransportTests");
356
357 suite->add(BOOST_TEST_CASE(test_destructor));
358 suite->add(BOOST_TEST_CASE(test_flush_max_us1));
359 suite->add(BOOST_TEST_CASE(test_flush_max_us2));
360 suite->add(BOOST_TEST_CASE(test_flush_max_us3));
361
362 return suite;
363}