diff options
Diffstat (limited to 'src/log/test.cc')
-rw-r--r-- | src/log/test.cc | 504 |
1 files changed, 504 insertions, 0 deletions
diff --git a/src/log/test.cc b/src/log/test.cc new file mode 100644 index 000000000..08ba5bff6 --- /dev/null +++ b/src/log/test.cc @@ -0,0 +1,504 @@ +#include <gtest/gtest.h> + +#include "log/Log.h" +#include "common/Clock.h" +#include "include/coredumpctl.h" +#include "SubsystemMap.h" + +#include "global/global_init.h" +#include "common/ceph_argparse.h" +#include "global/global_context.h" +#include "common/dout.h" + +#include <unistd.h> + +#include <limits.h> + +using namespace std; +using namespace ceph::logging; + +TEST(Log, Simple) +{ + SubsystemMap subs; + subs.set_log_level(0, 10); + subs.set_gather_level(0, 10); + + subs.set_log_level(1, 20); + subs.set_gather_level(1, 1); + + subs.set_log_level(2, 20); + subs.set_gather_level(2, 2); + + subs.set_log_level(3, 10); + subs.set_gather_level(3, 3); + + Log log(&subs); + log.start(); + + log.set_log_file("foo"); + log.reopen_log_file(); + + log.set_stderr_level(5, -1); + + + for (int i=0; i<100; i++) { + int sys = i % 4; + int l = 5 + (i%4); + if (subs.should_gather(sys, l)) { + MutableEntry e(l, sys); + log.submit_entry(std::move(e)); + } + } + + log.flush(); + + log.dump_recent(); + + log.stop(); +} + +TEST(Log, ReuseBad) +{ + SubsystemMap subs; + subs.set_log_level(1, 1); + subs.set_gather_level(1, 1); + Log log(&subs); + log.start(); + log.set_log_file("foo"); + log.reopen_log_file(); + + const int l = 0; + { + MutableEntry e(l, 1); + auto& out = e.get_ostream(); + out << (std::streambuf*)nullptr; + EXPECT_TRUE(out.bad()); // writing nullptr to a stream sets its badbit + log.submit_entry(std::move(e)); + } + { + MutableEntry e(l, 1); + auto& out = e.get_ostream(); + EXPECT_FALSE(out.bad()); // should not see failures from previous log entry + out << "hello world"; + log.submit_entry(std::move(e)); + } + + log.flush(); + log.stop(); +} + +int many = 10000; + +TEST(Log, ManyNoGather) +{ + SubsystemMap subs; + subs.set_log_level(1, 1); + subs.set_gather_level(1, 1); + Log log(&subs); + log.start(); + log.set_log_file("big"); + log.reopen_log_file(); + for (int i=0; i<many; i++) { + int l = 10; + if (subs.should_gather(1, l)) + log.submit_entry(MutableEntry(1, 0)); + } + log.flush(); + log.stop(); +} + + +TEST(Log, ManyGatherLog) +{ + SubsystemMap subs; + subs.set_log_level(1, 20); + subs.set_gather_level(1, 10); + Log log(&subs); + log.start(); + log.set_log_file("big"); + log.reopen_log_file(); + for (int i=0; i<many; i++) { + int l = 10; + if (subs.should_gather(1, l)) { + MutableEntry e(l, 1); + e.get_ostream() << "this is a long string asdf asdf asdf asdf asdf asdf asd fasd fasdf "; + log.submit_entry(std::move(e)); + } + } + log.flush(); + log.stop(); +} + +TEST(Log, ManyGatherLogStackSpillover) +{ + SubsystemMap subs; + subs.set_log_level(1, 20); + subs.set_gather_level(1, 10); + Log log(&subs); + log.start(); + log.set_log_file("big"); + log.reopen_log_file(); + for (int i=0; i<many; i++) { + int l = 10; + if (subs.should_gather(1, l)) { + MutableEntry e(l, 1); + auto& s = e.get_ostream(); + s << "foo"; + s << std::string(sizeof(e) * 2, '-'); + log.submit_entry(std::move(e)); + } + } + log.flush(); + log.stop(); +} + +TEST(Log, ManyGather) +{ + SubsystemMap subs; + subs.set_log_level(1, 20); + subs.set_gather_level(1, 1); + Log log(&subs); + log.start(); + log.set_log_file("big"); + log.reopen_log_file(); + for (int i=0; i<many; i++) { + int l = 10; + if (subs.should_gather(1, l)) + log.submit_entry(MutableEntry(l, 1)); + } + log.flush(); + log.stop(); +} + +static void readpipe(int fd, int verify) +{ + while (1) { + /* Use larger buffer on receiver as Linux will allow pipes buffers to + * exceed PIPE_BUF. We can't avoid tearing due to small read buffers from + * the Ceph side. + */ + + char buf[65536] = ""; + int rc = read(fd, buf, (sizeof buf) - 1); + if (rc == 0) { + _exit(0); + } else if (rc == -1) { + _exit(1); + } else if (rc > 0) { + if (verify) { + char* p = strrchr(buf, '\n'); + /* verify no torn writes */ + if (p == NULL) { + _exit(2); + } else if (p[1] != '\0') { + write(2, buf, strlen(buf)); + _exit(3); + } + } + } else _exit(100); + usleep(500); + } +} + +TEST(Log, StderrPipeAtomic) +{ + int pfd[2] = {-1, -1}; + int rc = pipe(pfd); + ASSERT_EQ(rc, 0); + pid_t pid = fork(); + if (pid == 0) { + close(pfd[1]); + readpipe(pfd[0], 1); + } else if (pid == (pid_t)-1) { + ASSERT_EQ(0, 1); + } + close(pfd[0]); + + SubsystemMap subs; + subs.set_log_level(1, 20); + subs.set_gather_level(1, 10); + Log log(&subs); + log.start(); + log.set_log_file(""); + log.reopen_log_file(); + log.set_stderr_fd(pfd[1]); + log.set_stderr_level(1, 20); + /* -128 for prefix space */ + for (int i = 0; i < PIPE_BUF-128; i++) { + MutableEntry e(1, 1); + auto& s = e.get_ostream(); + for (int j = 0; j < i; j++) { + char c = 'a'; + c += (j % 26); + s << c; + } + log.submit_entry(std::move(e)); + } + log.flush(); + log.stop(); + close(pfd[1]); + int status; + pid_t waited = waitpid(pid, &status, 0); + ASSERT_EQ(pid, waited); + ASSERT_NE(WIFEXITED(status), 0); + ASSERT_EQ(WEXITSTATUS(status), 0); +} + +TEST(Log, StderrPipeBig) +{ + int pfd[2] = {-1, -1}; + int rc = pipe(pfd); + ASSERT_EQ(rc, 0); + pid_t pid = fork(); + if (pid == 0) { + /* no verification as some reads will be torn due to size > PIPE_BUF */ + close(pfd[1]); + readpipe(pfd[0], 0); + } else if (pid == (pid_t)-1) { + ASSERT_EQ(0, 1); + } + close(pfd[0]); + + SubsystemMap subs; + subs.set_log_level(1, 20); + subs.set_gather_level(1, 10); + Log log(&subs); + log.start(); + log.set_log_file(""); + log.reopen_log_file(); + log.set_stderr_fd(pfd[1]); + log.set_stderr_level(1, 20); + /* -128 for prefix space */ + for (int i = 0; i < PIPE_BUF*2; i++) { + MutableEntry e(1, 1); + auto& s = e.get_ostream(); + for (int j = 0; j < i; j++) { + char c = 'a'; + c += (j % 26); + s << c; + } + log.submit_entry(std::move(e)); + } + log.flush(); + log.stop(); + close(pfd[1]); + int status; + pid_t waited = waitpid(pid, &status, 0); + ASSERT_EQ(pid, waited); + ASSERT_NE(WIFEXITED(status), 0); + ASSERT_EQ(WEXITSTATUS(status), 0); +} + +void do_segv() +{ + SubsystemMap subs; + subs.set_log_level(1, 20); + subs.set_gather_level(1, 1); + Log log(&subs); + log.start(); + log.set_log_file("big"); + log.reopen_log_file(); + + log.inject_segv(); + MutableEntry e(10, 1); + { + PrCtl unset_dumpable; + log.submit_entry(std::move(e)); // this should segv + } + + log.flush(); + log.stop(); +} + +TEST(Log, InternalSegv) +{ + ASSERT_DEATH(do_segv(), ".*"); +} + +TEST(Log, LargeLog) +{ + SubsystemMap subs; + subs.set_log_level(1, 20); + subs.set_gather_level(1, 10); + Log log(&subs); + log.start(); + log.set_log_file("big"); + log.reopen_log_file(); + int l = 10; + { + MutableEntry e(l, 1); + std::string msg(10000000, 'a'); + e.get_ostream() << msg; + log.submit_entry(std::move(e)); + } + log.flush(); + log.stop(); +} + +TEST(Log, LargeFromSmallLog) +{ + SubsystemMap subs; + subs.set_log_level(1, 20); + subs.set_gather_level(1, 10); + Log log(&subs); + log.start(); + log.set_log_file("big"); + log.reopen_log_file(); + int l = 10; + { + MutableEntry e(l, 1); + for (int i = 0; i < 1000000; i++) { + std::string msg(10, 'a'); + e.get_ostream() << msg; + } + log.submit_entry(std::move(e)); + } + log.flush(); + log.stop(); +} + +// Make sure nothing bad happens when we switch + +TEST(Log, TimeSwitch) +{ + SubsystemMap subs; + subs.set_log_level(1, 20); + subs.set_gather_level(1, 10); + Log log(&subs); + log.start(); + log.set_log_file("time_switch_log"); + log.reopen_log_file(); + int l = 10; + bool coarse = true; + for (auto i = 0U; i < 300; ++i) { + MutableEntry e(l, 1); + e.get_ostream() << "SQUID THEFT! PUNISHABLE BY DEATH!"; + log.submit_entry(std::move(e)); + if (i % 50) + log.set_coarse_timestamps(coarse = !coarse); + } + log.flush(); + log.stop(); +} + +TEST(Log, TimeFormat) +{ + static constexpr auto buflen = 128u; + char buf[buflen]; + ceph::logging::log_clock clock; + { + clock.coarsen(); + auto t = clock.now(); + ceph::logging::append_time(t, buf, buflen); + auto c = std::strrchr(buf, '.'); + ASSERT_NE(c, nullptr); + ASSERT_EQ(8u, strlen(c + 1)); + } + { + clock.refine(); + auto t = clock.now(); + ceph::logging::append_time(t, buf, buflen); + auto c = std::strrchr(buf, '.'); + ASSERT_NE(c, nullptr); + ASSERT_EQ(11u, std::strlen(c + 1)); + } +} + +#define dout_subsys ceph_subsys_context + +template <int depth, int x> struct do_log +{ + void log(CephContext* cct); +}; + +template <int x> struct do_log<12, x> +{ + void log(CephContext* cct); +}; + +template<int depth, int x> void do_log<depth,x>::log(CephContext* cct) +{ + ldout(cct, 20) << "Log depth=" << depth << " x=" << x << dendl; + if (rand() % 2) { + do_log<depth+1, x*2> log; + log.log(cct); + } else { + do_log<depth+1, x*2+1> log; + log.log(cct); + } +} + +std::string recursion(CephContext* cct) +{ + ldout(cct, 20) << "Preparing recursion string" << dendl; + return "here-recursion"; +} + +template<int x> void do_log<12, x>::log(CephContext* cct) +{ + if ((rand() % 16) == 0) { + ldout(cct, 20) << "End " << recursion(cct) << "x=" << x << dendl; + } else { + ldout(cct, 20) << "End x=" << x << dendl; + } +} + +TEST(Log, Speed_gather) +{ + do_log<0,0> start; + g_ceph_context->_conf->subsys.set_gather_level(ceph_subsys_context, 30); + g_ceph_context->_conf->subsys.set_log_level(ceph_subsys_context, 0); + for (int i=0; i<100000;i++) { + ldout(g_ceph_context, 20) << "Iteration " << i << dendl; + start.log(g_ceph_context); + } +} + +TEST(Log, Speed_nogather) +{ + do_log<0,0> start; + g_ceph_context->_conf->subsys.set_gather_level(ceph_subsys_context, 0); + g_ceph_context->_conf->subsys.set_log_level(ceph_subsys_context, 0); + for (int i=0; i<100000;i++) { + ldout(g_ceph_context, 20) << "Iteration " << i << dendl; + start.log(g_ceph_context); + } +} + +TEST(Log, GarbleRecovery) +{ + static const char* test_file="log_for_moment"; + + Log* saved = g_ceph_context->_log; + Log log(&g_ceph_context->_conf->subsys); + log.start(); + unlink(test_file); + log.set_log_file(test_file); + log.reopen_log_file(); + g_ceph_context->_log = &log; + + std::string long_message(1000,'c'); + ldout(g_ceph_context, 0) << long_message << dendl; + ldout(g_ceph_context, 0) << "Prologue" << (std::streambuf*)nullptr << long_message << dendl; + ldout(g_ceph_context, 0) << "Epitaph" << long_message << dendl; + + g_ceph_context->_log = saved; + log.flush(); + log.stop(); + struct stat file_status; + ASSERT_EQ(stat(test_file, &file_status), 0); + ASSERT_GT(file_status.st_size, 2000); +} + +int main(int argc, char **argv) +{ + auto args = argv_to_vec(argc, argv); + + auto cct = global_init(nullptr, args, CEPH_ENTITY_TYPE_CLIENT, + CODE_ENVIRONMENT_UTILITY, + CINIT_FLAG_NO_DEFAULT_CONFIG_FILE); + common_init_finish(g_ceph_context); + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} |