YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/Users/deen/code/yugabyte-db/src/yb/rocksdb/db/auto_roll_logger_test.cc
Line
Count
Source (jump to first uncovered line)
1
//  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
2
//  This source code is licensed under the BSD-style license found in the
3
//  LICENSE file in the root directory of this source tree. An additional grant
4
//  of patent rights can be found in the PATENTS file in the same directory.
5
//
6
// The following only applies to changes made to this file as part of YugaByte development.
7
//
8
// Portions Copyright (c) YugaByte, Inc.
9
//
10
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
11
// in compliance with the License.  You may obtain a copy of the License at
12
//
13
// http://www.apache.org/licenses/LICENSE-2.0
14
//
15
// Unless required by applicable law or agreed to in writing, software distributed under the License
16
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
17
// or implied.  See the License for the specific language governing permissions and limitations
18
// under the License.
19
//
20
21
#include <fstream>
22
#include <string>
23
#include <thread>
24
#include <vector>
25
26
#include <gtest/gtest.h>
27
28
#include "yb/rocksdb/db.h"
29
#include "yb/rocksdb/db/auto_roll_logger.h"
30
#include "yb/rocksdb/port/port.h"
31
#include "yb/rocksdb/util/sync_point.h"
32
#include "yb/rocksdb/util/testharness.h"
33
#include "yb/rocksdb/util/testutil.h"
34
35
#include "yb/util/test_macros.h"
36
37
namespace rocksdb {
38
39
class AutoRollLoggerTest : public RocksDBTest {
40
 public:
41
11
  static void InitTestDb() {
42
#ifdef OS_WIN
43
    // Replace all slashes in the path so windows CompSpec does not
44
    // become confused
45
    std::string testDir(kTestDir);
46
    std::replace_if(testDir.begin(), testDir.end(),
47
                    [](char ch) { return ch == '/'; }, '\\');
48
    std::string deleteCmd = "if exist " + testDir + " rd /s /q " + testDir;
49
#else
50
11
    std::string deleteCmd = "rm -rf " + kTestDir;
51
11
#endif
52
11
    ASSERT_EQ(system(deleteCmd.c_str()), 0);
53
11
    ASSERT_OK(Env::Default()->CreateDir(kTestDir));
54
11
  }
55
56
  void RollLogFileBySizeTest(AutoRollLogger* logger,
57
                             size_t log_max_size,
58
                             const string& log_message);
59
  uint64_t RollLogFileByTimeTest(AutoRollLogger* logger,
60
                                 size_t time,
61
                                 const string& log_message);
62
63
  static const string kSampleMessage;
64
  static const string kTestDir;
65
  static const string kLogFile;
66
  static Env* env;
67
};
68
69
const string AutoRollLoggerTest::kSampleMessage(
70
    "this is the message to be written to the log file!!");
71
const string AutoRollLoggerTest::kTestDir(test::TmpDir() + "/db_log_test");
72
const string AutoRollLoggerTest::kLogFile(test::TmpDir() + "/db_log_test/LOG");
73
Env* AutoRollLoggerTest::env = Env::Default();
74
75
// In this test we only want to Log some simple log message with
76
// no format. LogMessage() provides such a simple interface and
77
// avoids the [format-security] warning which occurs when you
78
// call Log(logger, log_message) directly.
79
namespace {
80
239
void LogMessage(Logger* logger, const char* message) {
81
239
  RLOG(logger, "%s", message);
82
239
}
83
84
void LogMessage(const InfoLogLevel log_level, Logger* logger,
85
36
                const char* message) {
86
36
  RLOG(log_level, logger, "%s", message);
87
36
}
88
}  // namespace
89
90
namespace {
91
92
8
uint64_t GetFileCreateTime(const std::string& fname) {
93
8
  struct stat s;
94
8
  if (stat(fname.c_str(), &s) == 0) {
95
8
    return static_cast<uint64_t>(s.st_ctime);
96
0
  } else {
97
0
    const string error_str(strerror(errno));
98
0
    std::cerr << "Failed to get creation time of '" << fname << "': " << error_str << std::endl;
99
0
    return 0;
100
0
  }
101
8
}
102
103
// Get the inode of the given file as a signed 64-bit integer, or -1 in case of failure.
104
44
int64_t GetFileInode(const std::string& fname) {
105
44
  struct stat s;
106
44
  if (stat(fname.c_str(), &s) == 0) {
107
44
    return int64_t(s.st_ino);
108
0
  } else {
109
0
    const string error_str(strerror(errno));
110
0
    std::cerr << "Failed to get inode of '" << fname << "': " << error_str << std::endl;
111
0
    return -1;
112
0
  }
113
44
}
114
115
}  // namespace
116
117
void AutoRollLoggerTest::RollLogFileBySizeTest(AutoRollLogger* logger,
118
                                               size_t log_max_size,
119
5
                                               const string& log_message) {
120
5
  logger->SetInfoLogLevel(InfoLogLevel::INFO_LEVEL);
121
  // measure the size of each message, which is supposed
122
  // to be equal or greater than log_message.size()
123
5
  LogMessage(logger, log_message.c_str());
124
5
  size_t message_size = logger->GetLogFileSize();
125
5
  size_t current_log_size = message_size;
126
127
  // Test the cases when the log file will not be rolled.
128
184
  while (current_log_size + message_size < log_max_size) {
129
179
    LogMessage(logger, log_message.c_str());
130
179
    current_log_size += message_size;
131
179
    ASSERT_EQ(current_log_size, logger->GetLogFileSize());
132
179
  }
133
134
  // Now the log file will be rolled
135
5
  LogMessage(logger, log_message.c_str());
136
  // Since rotation is checked before actual logging, we need to
137
  // trigger the rotation by logging another message.
138
5
  LogMessage(logger, log_message.c_str());
139
140
5
  ASSERT_TRUE(message_size == logger->GetLogFileSize());
141
5
}
142
143
uint64_t AutoRollLoggerTest::RollLogFileByTimeTest(
144
4
    AutoRollLogger* logger, size_t time, const string& log_message) {
145
4
  uint64_t total_log_size = 0;
146
4
  EXPECT_OK(env->GetFileSize(kLogFile, &total_log_size));
147
4
  const int64_t expected_inode = GetFileInode(kLogFile);
148
4
  logger->SetCallNowMicrosEveryNRecords(0);
149
4
  const uint64_t initial_create_time = GetFileCreateTime(kLogFile);
150
151
  // -- Write to the log for several times, which is supposed
152
  // to be finished before time.
153
44
  for (int i = 0; i < 10; ++i) {
154
40
     LogMessage(logger, log_message.c_str());
155
40
     EXPECT_OK(logger->GetStatus());
156
     // Make sure we always write to the same log file (by checking the inode).
157
40
     EXPECT_EQ(expected_inode, GetFileInode(kLogFile));
158
159
     // Also make sure the log size is increasing.
160
40
     EXPECT_GT(logger->GetLogFileSize(), total_log_size);
161
40
     total_log_size = logger->GetLogFileSize();
162
40
  }
163
164
  // -- Make the log file expire
165
#ifdef OS_WIN
166
  Sleep(static_cast<unsigned int>(time) * 1000);
167
#else
168
4
  sleep(static_cast<unsigned int>(time));
169
4
#endif
170
4
  LogMessage(logger, log_message.c_str());
171
172
  // At this time, the new log file should be created.
173
174
4
  const uint64_t actual_create_time = GetFileCreateTime(kLogFile);
175
4
  EXPECT_GT(actual_create_time, initial_create_time);
176
4
  EXPECT_LT(logger->GetLogFileSize(), total_log_size);
177
178
4
  return actual_create_time;
179
4
}
180
181
1
TEST_F(AutoRollLoggerTest, RollLogFileBySize) {
182
1
    InitTestDb();
183
1
    size_t log_max_size = 1024 * 5;
184
185
1
    AutoRollLogger logger(Env::Default(), kTestDir, "", log_max_size, 0);
186
187
1
    RollLogFileBySizeTest(&logger, log_max_size,
188
1
                          kSampleMessage + ":RollLogFileBySize");
189
1
}
190
191
1
TEST_F(AutoRollLoggerTest, RollLogFileByTime) {
192
1
    size_t time = 2;
193
1
    size_t log_size = 1024 * 5;
194
195
1
    InitTestDb();
196
    // -- Test the existence of file during the server restart.
197
1
    ASSERT_TRUE(env->FileExists(kLogFile).IsNotFound());
198
1
    AutoRollLogger logger(Env::Default(), kTestDir, "", log_size, time);
199
1
    ASSERT_OK(env->FileExists(kLogFile));
200
201
1
    RollLogFileByTimeTest(&logger, time, kSampleMessage + ":RollLogFileByTime");
202
1
}
203
204
1
TEST_F(AutoRollLoggerTest, OpenLogFilesMultipleTimesWithOptionLog_max_size) {
205
  // If only 'log_max_size' options is specified, then every time
206
  // when rocksdb is restarted, a new empty log file will be created.
207
1
  InitTestDb();
208
  // WORKAROUND:
209
  // avoid complier's complaint of "comparison between signed
210
  // and unsigned integer expressions" because literal 0 is
211
  // treated as "singed".
212
1
  size_t kZero = 0;
213
1
  size_t log_size = 1024;
214
215
1
  AutoRollLogger* logger = new AutoRollLogger(
216
1
    Env::Default(), kTestDir, "", log_size, 0);
217
218
1
  LogMessage(logger, kSampleMessage.c_str());
219
1
  ASSERT_GT(logger->GetLogFileSize(), kZero);
220
1
  delete logger;
221
222
  // reopens the log file and an empty log file will be created.
223
1
  logger = new AutoRollLogger(
224
1
    Env::Default(), kTestDir, "", log_size, 0);
225
1
  ASSERT_EQ(logger->GetLogFileSize(), kZero);
226
1
  delete logger;
227
1
}
228
229
1
TEST_F(AutoRollLoggerTest, CompositeRollByTimeAndSizeLogger) {
230
1
  size_t time = 2, log_max_size = 1024 * 5;
231
232
1
  InitTestDb();
233
234
1
  AutoRollLogger logger(Env::Default(), kTestDir, "", log_max_size, time);
235
236
  // Test the ability to roll by size
237
1
  RollLogFileBySizeTest(
238
1
      &logger, log_max_size,
239
1
      kSampleMessage + ":CompositeRollByTimeAndSizeLogger");
240
241
  // Test the ability to roll by Time
242
1
  RollLogFileByTimeTest( &logger, time,
243
1
      kSampleMessage + ":CompositeRollByTimeAndSizeLogger");
244
1
}
245
246
#ifndef OS_WIN
247
// TODO: does not build for Windows because of PosixLogger use below. Need to
248
// port
249
1
TEST_F(AutoRollLoggerTest, CreateLoggerFromOptions) {
250
1
  DBOptions options;
251
1
  shared_ptr<Logger> logger;
252
253
  // Normal logger
254
1
  ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger));
255
1
  ASSERT_TRUE(dynamic_cast<PosixLogger*>(logger.get()));
256
257
  // Only roll by size
258
1
  InitTestDb();
259
1
  options.max_log_file_size = 1024;
260
1
  ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger));
261
1
  AutoRollLogger* auto_roll_logger =
262
1
    dynamic_cast<AutoRollLogger*>(logger.get());
263
1
  ASSERT_TRUE(auto_roll_logger);
264
1
  RollLogFileBySizeTest(
265
1
      auto_roll_logger, options.max_log_file_size,
266
1
      kSampleMessage + ":CreateLoggerFromOptions - size");
267
268
  // Only roll by Time
269
1
  InitTestDb();
270
1
  options.max_log_file_size = 0;
271
1
  options.log_file_time_to_roll = 2;
272
1
  ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger));
273
1
  auto_roll_logger =
274
1
    dynamic_cast<AutoRollLogger*>(logger.get());
275
1
  RollLogFileByTimeTest(
276
1
      auto_roll_logger, options.log_file_time_to_roll,
277
1
      kSampleMessage + ":CreateLoggerFromOptions - time");
278
279
  // roll by both Time and size
280
1
  InitTestDb();
281
1
  options.max_log_file_size = 1024 * 5;
282
1
  options.log_file_time_to_roll = 2;
283
1
  ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger));
284
1
  auto_roll_logger =
285
1
    dynamic_cast<AutoRollLogger*>(logger.get());
286
1
  RollLogFileBySizeTest(
287
1
      auto_roll_logger, options.max_log_file_size,
288
1
      kSampleMessage + ":CreateLoggerFromOptions - both");
289
1
  RollLogFileByTimeTest(
290
1
      auto_roll_logger, options.log_file_time_to_roll,
291
1
      kSampleMessage + ":CreateLoggerFromOptions - both");
292
1
}
293
294
1
TEST_F(AutoRollLoggerTest, LogFlushWhileRolling) {
295
1
  DBOptions options;
296
1
  shared_ptr<Logger> logger;
297
298
1
  InitTestDb();
299
1
  options.max_log_file_size = 1024 * 5;
300
1
  ASSERT_OK(CreateLoggerFromOptions(kTestDir, options, &logger));
301
1
  AutoRollLogger* auto_roll_logger =
302
1
      dynamic_cast<AutoRollLogger*>(logger.get());
303
1
  ASSERT_TRUE(auto_roll_logger);
304
1
  std::thread flush_thread;
305
306
1
  rocksdb::SyncPoint::GetInstance()->LoadDependency({
307
      // Need to pin the old logger before beginning the roll, as rolling grabs
308
      // the mutex, which would prevent us from accessing the old logger.
309
1
      {"AutoRollLogger::Flush:PinnedLogger",
310
1
       "AutoRollLoggerTest::LogFlushWhileRolling:PreRollAndPostThreadInit"},
311
      // Need to finish the flush thread init before this callback because the
312
      // callback accesses flush_thread.get_id() in order to apply certain sync
313
      // points only to the flush thread.
314
1
      {"AutoRollLoggerTest::LogFlushWhileRolling:PreRollAndPostThreadInit",
315
1
       "AutoRollLoggerTest::LogFlushWhileRolling:FlushCallbackBegin"},
316
      // Need to reset logger at this point in Flush() to exercise a race
317
      // condition case, which is executing the flush with the pinned (old)
318
      // logger after the roll has cut over to a new logger.
319
1
      {"AutoRollLoggerTest::LogFlushWhileRolling:FlushCallback1",
320
1
       "AutoRollLogger::ResetLogger:BeforeNewLogger"},
321
1
      {"AutoRollLogger::ResetLogger:AfterNewLogger",
322
1
       "AutoRollLoggerTest::LogFlushWhileRolling:FlushCallback2"},
323
1
  });
324
1
  rocksdb::SyncPoint::GetInstance()->SetCallBack(
325
3
      "PosixLogger::Flush:BeginCallback", [&](void* arg) {
326
3
        TEST_SYNC_POINT(
327
3
            "AutoRollLoggerTest::LogFlushWhileRolling:FlushCallbackBegin");
328
3
        if (std::this_thread::get_id() == flush_thread.get_id()) {
329
1
          TEST_SYNC_POINT(
330
1
              "AutoRollLoggerTest::LogFlushWhileRolling:FlushCallback1");
331
1
          TEST_SYNC_POINT(
332
1
              "AutoRollLoggerTest::LogFlushWhileRolling:FlushCallback2");
333
1
        }
334
3
      });
335
1
  rocksdb::SyncPoint::GetInstance()->EnableProcessing();
336
337
1
  flush_thread = std::thread([&]() { auto_roll_logger->Flush(); });
338
1
  TEST_SYNC_POINT(
339
1
      "AutoRollLoggerTest::LogFlushWhileRolling:PreRollAndPostThreadInit");
340
1
  RollLogFileBySizeTest(auto_roll_logger, options.max_log_file_size,
341
1
                        kSampleMessage + ":LogFlushWhileRolling");
342
1
  flush_thread.join();
343
1
  rocksdb::SyncPoint::GetInstance()->DisableProcessing();
344
1
}
345
346
#endif  // OS_WIN
347
348
1
TEST_F(AutoRollLoggerTest, InfoLogLevel) {
349
1
  InitTestDb();
350
351
1
  size_t log_size = 8192;
352
1
  size_t log_lines = 0;
353
  // an extra-scope to force the AutoRollLogger to flush the log file when it
354
  // becomes out of scope.
355
1
  {
356
1
    AutoRollLogger logger(Env::Default(), kTestDir, "", log_size, 0);
357
1
    for (int log_level = InfoLogLevel::HEADER_LEVEL;
358
7
         log_level >= InfoLogLevel::DEBUG_LEVEL; log_level--) {
359
6
      logger.SetInfoLogLevel((InfoLogLevel)log_level);
360
6
      for (int log_type = InfoLogLevel::DEBUG_LEVEL;
361
42
           log_type <= InfoLogLevel::HEADER_LEVEL; log_type++) {
362
        // log messages with log level smaller than log_level will not be
363
        // logged.
364
36
        LogMessage((InfoLogLevel)log_type, &logger, kSampleMessage.c_str());
365
36
      }
366
6
      log_lines += InfoLogLevel::HEADER_LEVEL - log_level + 1;
367
6
    }
368
1
    for (int log_level = InfoLogLevel::HEADER_LEVEL;
369
7
         log_level >= InfoLogLevel::DEBUG_LEVEL; log_level--) {
370
6
      logger.SetInfoLogLevel((InfoLogLevel)log_level);
371
372
      // again, messages with level smaller than log_level will not be logged.
373
6
      RLOG(InfoLogLevel::HEADER_LEVEL, &logger, "%s", kSampleMessage.c_str());
374
6
      RDEBUG(&logger, "%s", kSampleMessage.c_str());
375
6
      RINFO(&logger, "%s", kSampleMessage.c_str());
376
6
      RWARN(&logger, "%s", kSampleMessage.c_str());
377
6
      RERROR(&logger, "%s", kSampleMessage.c_str());
378
6
      RFATAL(&logger, "%s", kSampleMessage.c_str());
379
6
      log_lines += InfoLogLevel::HEADER_LEVEL - log_level + 1;
380
6
    }
381
1
  }
382
1
  std::ifstream inFile(AutoRollLoggerTest::kLogFile.c_str());
383
1
  size_t lines = std::count(std::istreambuf_iterator<char>(inFile),
384
1
                         std::istreambuf_iterator<char>(), '\n');
385
1
  ASSERT_EQ(log_lines, lines);
386
1
  inFile.close();
387
1
}
388
389
// Test the logger Header function for roll over logs
390
// We expect the new logs creates as roll over to carry the headers specified
391
2
static std::vector<string> GetOldFileNames(const string& path) {
392
2
  std::vector<string> ret;
393
394
2
  const string dirname = path.substr(/*start=*/ 0, path.find_last_of("/"));
395
2
  const string fname = path.substr(path.find_last_of("/") + 1);
396
397
2
  std::vector<string> children;
398
2
  CHECK_OK(Env::Default()->GetChildren(dirname, &children));
399
400
  // We know that the old log files are named [path]<something>
401
  // Return all entities that match the pattern
402
10
  for (auto& child : children) {
403
10
    if (fname != child && child.find(fname) == 0) {
404
4
      ret.push_back(dirname + "/" + child);
405
4
    }
406
10
  }
407
408
2
  return ret;
409
2
}
410
411
// Return the number of lines where a given pattern was found in the file
412
4
static size_t GetLinesCount(const string& fname, const string& pattern) {
413
4
  std::stringstream ssbuf;
414
4
  string line;
415
4
  size_t count = 0;
416
417
4
  std::ifstream inFile(fname.c_str());
418
4
  ssbuf << inFile.rdbuf();
419
420
210
  while (getline(ssbuf, line)) {
421
206
    if (line.find(pattern) != std::string::npos) {
422
40
      count++;
423
40
    }
424
206
  }
425
426
4
  return count;
427
4
}
428
429
1
TEST_F(AutoRollLoggerTest, LogHeaderTest) {
430
1
  static const size_t MAX_HEADERS = 10;
431
1
  static const size_t LOG_MAX_SIZE = 1024 * 5;
432
1
  static const std::string HEADER_STR = "Log header line";
433
434
  // test_num == 0 -> standard call to Header()
435
  // test_num == 1 -> call to Log() with InfoLogLevel::HEADER_LEVEL
436
3
  for (int test_num = 0; test_num < 2; test_num++) {
437
438
2
    InitTestDb();
439
440
2
    AutoRollLogger logger(Env::Default(), kTestDir, /*db_log_dir=*/ "",
441
2
                          LOG_MAX_SIZE, /*log_file_time_to_roll=*/ 0);
442
443
2
    if (test_num == 0) {
444
      // Log some headers explicitly using Header()
445
11
      for (size_t i = 0; i < MAX_HEADERS; i++) {
446
10
        RHEADER(&logger, "%s %d", HEADER_STR.c_str(), i);
447
10
      }
448
1
    } else if (test_num == 1) {
449
      // HEADER_LEVEL should make this behave like calling Header()
450
11
      for (size_t i = 0; i < MAX_HEADERS; i++) {
451
10
        RLOG(InfoLogLevel::HEADER_LEVEL, &logger, "%s %d",
452
10
            HEADER_STR.c_str(), i);
453
10
      }
454
1
    }
455
456
2
    const string newfname = logger.TEST_log_fname();
457
458
    // Log enough data to cause a roll over
459
2
    int i = 0;
460
6
    for (size_t iter = 0; iter < 2; iter++) {
461
168
      while (logger.GetLogFileSize() < LOG_MAX_SIZE) {
462
164
        RINFO(&logger, (kSampleMessage + ":LogHeaderTest line %d").c_str(), i);
463
164
        ++i;
464
164
      }
465
466
4
      RINFO(&logger, "Rollover");
467
4
    }
468
469
    // Flush the log for the latest file
470
2
    LogFlush(&logger);
471
472
2
    const auto oldfiles = GetOldFileNames(newfname);
473
474
2
    ASSERT_EQ(oldfiles.size(), (size_t) 2);
475
476
4
    for (auto& oldfname : oldfiles) {
477
      // verify that the files rolled over
478
4
      ASSERT_NE(oldfname, newfname);
479
      // verify that the old log contains all the header logs
480
4
      ASSERT_EQ(GetLinesCount(oldfname, HEADER_STR), MAX_HEADERS);
481
4
    }
482
2
  }
483
1
}
484
485
1
TEST_F(AutoRollLoggerTest, LogFileExistence) {
486
1
  rocksdb::DB* db;
487
1
  rocksdb::Options options;
488
1
  string deleteCmd = "rm -rf " + kTestDir;
489
1
  ASSERT_EQ(system(deleteCmd.c_str()), 0);
490
1
  options.max_log_file_size = 100 * 1024 * 1024;
491
1
  options.create_if_missing = true;
492
1
  ASSERT_OK(rocksdb::DB::Open(options, kTestDir, &db));
493
1
  ASSERT_OK(env->FileExists(kLogFile));
494
1
  delete db;
495
1
}
496
497
}  // namespace rocksdb
498
499
13.2k
int main(int argc, char** argv) {
500
13.2k
  ::testing::InitGoogleTest(&argc, argv);
501
13.2k
  return RUN_ALL_TESTS();
502
13.2k
}