diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
commit | 19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch) | |
tree | 42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/jaegertracing/thrift/lib/cpp/test/TFileTransportTest.cpp | |
parent | Initial commit. (diff) | |
download | ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.tar.xz ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.zip |
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/jaegertracing/thrift/lib/cpp/test/TFileTransportTest.cpp')
-rw-r--r-- | src/jaegertracing/thrift/lib/cpp/test/TFileTransportTest.cpp | 404 |
1 files changed, 404 insertions, 0 deletions
diff --git a/src/jaegertracing/thrift/lib/cpp/test/TFileTransportTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/TFileTransportTest.cpp new file mode 100644 index 000000000..21c1f3b33 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/TFileTransportTest.cpp @@ -0,0 +1,404 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE // needed for getopt_long +#endif + +#include <thrift/thrift-config.h> + +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#include <getopt.h> +#include <boost/test/unit_test.hpp> + +#include <thrift/transport/TFileTransport.h> + +#ifdef __MINGW32__ + #include <io.h> + #include <unistd.h> + #include <sys/types.h> + #include <fcntl.h> + #include <sys\stat.h> +#endif + +using namespace apache::thrift::transport; + +/************************************************************************** + * Global state + **************************************************************************/ + +static const char* tmp_dir = "/tmp"; + +class FsyncLog; +FsyncLog* fsync_log; + +/************************************************************************** + * Helper code + **************************************************************************/ + +/** + * Class to record calls to fsync + */ +class FsyncLog { +public: + struct FsyncCall { + struct timeval time; + int fd; + }; + typedef std::list<FsyncCall> CallList; + + FsyncLog() = default; + + void fsync(int fd) { + (void)fd; + FsyncCall call; + THRIFT_GETTIMEOFDAY(&call.time, nullptr); + calls_.push_back(call); + } + + const CallList* getCalls() const { return &calls_; } + +private: + CallList calls_; +}; + +/** + * Helper class to clean up temporary files + */ +class TempFile { +public: + TempFile(const char* directory, const char* prefix) { + #ifdef __MINGW32__ + ((void)directory); + size_t path_len = strlen(prefix) + 8; + path_ = new char[path_len]; + snprintf(path_, path_len, "%sXXXXXX", prefix); + if (_mktemp_s(path_,path_len) == 0) { + fd_ = open(path_,O_CREAT | O_RDWR | O_BINARY,S_IREAD | S_IWRITE); + if (fd_ < 0) { + throw apache::thrift::TException("_mktemp_s() failed"); + } + } else { + throw apache::thrift::TException("_mktemp_s() failed"); + } + #else + size_t path_len = strlen(directory) + strlen(prefix) + 8; + path_ = new char[path_len]; + snprintf(path_, path_len, "%s/%sXXXXXX", directory, prefix); + + fd_ = mkstemp(path_); + if (fd_ < 0) { + throw apache::thrift::TException("mkstemp() failed"); + } + #endif + } + + ~TempFile() { + unlink(); + close(); + } + + const char* getPath() const { return path_; } + + int getFD() const { return fd_; } + + void unlink() { + if (path_) { + ::unlink(path_); + delete[] path_; + path_ = nullptr; + } + } + + void close() { + if (fd_ < 0) { + return; + } + + ::close(fd_); + fd_ = -1; + } + +private: + char* path_; + int fd_; +}; + +// Use our own version of fsync() for testing. +// This returns immediately, so timing in test_destructor() isn't affected by +// waiting on the actual filesystem. +extern "C" int fsync(int fd) { + if (fsync_log) { + fsync_log->fsync(fd); + } + return 0; +} + +int time_diff(const struct timeval* t1, const struct timeval* t2) { + return (t2->tv_usec - t1->tv_usec) + (t2->tv_sec - t1->tv_sec) * 1000000; +} + +/************************************************************************** + * Test cases + **************************************************************************/ + +/** + * Make sure the TFileTransport destructor exits "quickly". + * + * Previous versions had a bug causing the writer thread not to exit + * right away. + * + * It's kind of lame that we just check to see how long the destructor takes in + * wall-clock time. This could result in false failures on slower systems, or + * on heavily loaded machines. + */ +BOOST_AUTO_TEST_CASE(test_destructor) { + TempFile f(tmp_dir, "thrift.TFileTransportTest."); + + unsigned int const NUM_ITERATIONS = 1000; + + unsigned int num_over = 0; + for (unsigned int n = 0; n < NUM_ITERATIONS; ++n) { + BOOST_CHECK_EQUAL(0, ftruncate(f.getFD(), 0)); + + TFileTransport* transport = new TFileTransport(f.getPath()); + + // write something so that the writer thread gets started + transport->write(reinterpret_cast<const uint8_t*>("foo"), 3); + + // Every other iteration, also call flush(), just in case that potentially + // has any effect on how the writer thread wakes up. + if (n & 0x1) { + transport->flush(); + } + + /* + * Time the call to the destructor + */ + struct timeval start; + struct timeval end; + + THRIFT_GETTIMEOFDAY(&start, nullptr); + delete transport; + THRIFT_GETTIMEOFDAY(&end, nullptr); + + int delta = time_diff(&start, &end); + + // If any attempt takes more than 500ms, treat that as a failure. + // Treat this as a fatal failure, so we'll return now instead of + // looping over a very slow operation. + BOOST_WARN( delta < 500000 ); + + // Normally, it takes less than 100ms on my dev box. + // However, if the box is heavily loaded, some of the test runs + // take longer, since we're just waiting for our turn on the CPU. + if (delta > 100000) { + ++num_over; + } + } + + // Make sure fewer than 10% of the runs took longer than 1000us + BOOST_WARN(num_over < (NUM_ITERATIONS / 10)); +} + +/** + * Make sure setFlushMaxUs() is honored. + */ +void test_flush_max_us_impl(uint32_t flush_us, uint32_t write_us, uint32_t test_us) { + // TFileTransport only calls fsync() if data has been written, + // so make sure the write interval is smaller than the flush interval. + BOOST_WARN(write_us < flush_us); + + TempFile f(tmp_dir, "thrift.TFileTransportTest."); + + // Record calls to fsync() + FsyncLog log; + fsync_log = &log; + + TFileTransport* transport = new TFileTransport(f.getPath()); + // Don't flush because of # of bytes written + transport->setFlushMaxBytes(0xffffffff); + uint8_t buf[] = "a"; + uint32_t buflen = sizeof(buf); + + // Set the flush interval + transport->setFlushMaxUs(flush_us); + + // Make one call to write, to start the writer thread now. + // (If we just let the thread get created during our test loop, + // the thread creation sometimes takes long enough to make the first + // fsync interval fail the check.) + transport->write(buf, buflen); + + // Add one entry to the fsync log, just to mark the start time + log.fsync(-1); + + // Loop doing write(), sleep(), ... + uint32_t total_time = 0; + while (true) { + transport->write(buf, buflen); + if (total_time > test_us) { + break; + } + usleep(write_us); + total_time += write_us; + } + + delete transport; + + // Stop logging new fsync() calls + fsync_log = nullptr; + + // Examine the fsync() log + // + // TFileTransport uses pthread_cond_timedwait(), which only has millisecond + // resolution. In my testing, it normally wakes up about 1 millisecond late. + // However, sometimes it takes a bit longer. Allow 5ms leeway. + int max_allowed_delta = flush_us + 5000; + + const FsyncLog::CallList* calls = log.getCalls(); + // We added 1 fsync call above. + // Make sure TFileTransport called fsync at least once + BOOST_WARN_GE(calls->size(), static_cast<FsyncLog::CallList::size_type>(1)); + + const struct timeval* prev_time = nullptr; + for (const auto & call : *calls) { + if (prev_time) { + int delta = time_diff(prev_time, &call.time); + BOOST_WARN( delta < max_allowed_delta ); + } + prev_time = &call.time; + } +} + +BOOST_AUTO_TEST_CASE(test_flush_max_us1) { + // fsync every 10ms, write every 5ms, for 500ms + test_flush_max_us_impl(10000, 5000, 500000); +} + +BOOST_AUTO_TEST_CASE(test_flush_max_us2) { + // fsync every 50ms, write every 20ms, for 500ms + test_flush_max_us_impl(50000, 20000, 500000); +} + +BOOST_AUTO_TEST_CASE(test_flush_max_us3) { + // fsync every 400ms, write every 300ms, for 1s + test_flush_max_us_impl(400000, 300000, 1000000); +} + +/** + * Make sure flush() is fast when there is nothing to do. + * + * TFileTransport used to have a bug where flush() would wait for the fsync + * timeout to expire. + */ +BOOST_AUTO_TEST_CASE(test_noop_flush) { + TempFile f(tmp_dir, "thrift.TFileTransportTest."); + TFileTransport transport(f.getPath()); + + // Write something to start the writer thread. + uint8_t buf[] = "a"; + transport.write(buf, 1); + + struct timeval start; + THRIFT_GETTIMEOFDAY(&start, nullptr); + + for (unsigned int n = 0; n < 10; ++n) { + transport.flush(); + + struct timeval now; + THRIFT_GETTIMEOFDAY(&now, nullptr); + + // Fail if at any point we've been running for longer than half a second. + // (With the buggy code, TFileTransport used to take 3 seconds per flush()) + // + // Use a fatal fail so we break out early, rather than continuing to make + // many more slow flush() calls. + int delta = time_diff(&start, &now); + BOOST_WARN( delta < 2000000 ); + } +} + +/************************************************************************** + * General Initialization + **************************************************************************/ + +void print_usage(FILE* f, const char* argv0) { + fprintf(f, "Usage: %s [boost_options] [options]\n", argv0); + fprintf(f, "Options:\n"); + fprintf(f, " --tmp-dir=DIR, -t DIR\n"); + fprintf(f, " --help\n"); +} + +void parse_args(int argc, char* argv[]) { + struct option long_opts[] + = {{"help", false, nullptr, 'h'}, {"tmp-dir", true, nullptr, 't'}, {nullptr, 0, nullptr, 0}}; + + while (true) { + optopt = 1; + int optchar = getopt_long(argc, argv, "ht:", long_opts, nullptr); + if (optchar == -1) { + break; + } + + switch (optchar) { + case 't': + tmp_dir = optarg; + break; + case 'h': + print_usage(stdout, argv[0]); + exit(0); + case '?': + exit(1); + default: + // Only happens if someone adds another option to the optarg string, + // but doesn't update the switch statement to handle it. + fprintf(stderr, "unknown option \"-%c\"\n", optchar); + exit(1); + } + } +} + +#ifdef BOOST_TEST_DYN_LINK +static int myArgc = 0; +static char **myArgv = nullptr; + +bool init_unit_test_suite() { + boost::unit_test::framework::master_test_suite().p_name.value = "TFileTransportTest"; + + // Parse arguments + parse_args(myArgc,myArgv); + return true; +} + +int main( int argc, char* argv[] ) { + myArgc = argc; + myArgv = argv; + return ::boost::unit_test::unit_test_main(&init_unit_test_suite,argc,argv); +} +#else +boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) { + boost::unit_test::framework::master_test_suite().p_name.value = "TFileTransportTest"; + + // Parse arguments + parse_args(argc, argv); + return NULL; +} +#endif |